Getting Started with Rust

· 2 min read
#rust #programming

Rust is one of those languages that makes you a better programmer even if you never ship a line of Rust code. The concepts it teaches — ownership, borrowing, lifetimes — change how you think about all code.

Why Rust?

The pitch is simple: memory safety without garbage collection, performance without footguns, and concurrency without data races. But the real reason to learn Rust is more subtle: it makes you think before you allocate, mutate, or share. (This "forced thinking" is what some people find frustrating about Rust. But it's also what makes Rust code so reliable once it compiles. The compiler is essentially a very pedantic code reviewer.)

Your First Program

Let’s start with something more interesting than “Hello, World”:

use std::collections::HashMap;

fn word_count(text: &str) -> HashMap<&str, usize> {
    let mut counts = HashMap::new();
    for word in text.split_whitespace() {
        let word = word.trim_matches(|c: char| !c.is_alphanumeric());
        *counts.entry(word).or_insert(0) += 1;
    }
    counts
}

fn main() {
    let text = "the quick brown fox jumps over the lazy dog the fox";
    let counts = word_count(text);

    let mut sorted: Vec<_> = counts.iter().collect();
    sorted.sort_by(|a, b| b.1.cmp(a.1));

    for (word, count) in sorted {
        println!("{word:>10}: {count}");
    }
}

Notice a few things: &str means we’re borrowing the string (not owning it), mut explicitly marks mutable state, and the compiler ensures we can’t accidentally create dangling references.

Understanding Ownership

Ownership is Rust’s central innovation. The rules are deceptively simple:

  1. Each value has exactly one owner
  2. When the owner goes out of scope, the value is dropped
  3. You can borrow a value (immutably or mutably), but not both at once

Here’s where it gets interesting:

fn take_ownership(s: String) {
    println!("I own this now: {s}");
    // s is dropped here
}

fn borrow_only(s: &str) {
    println!("Just looking: {s}");
    // s is NOT dropped — we only borrowed it
}

fn main() {
    let greeting = String::from("hello");

    borrow_only(&greeting);    // Fine — we still own greeting
    println!("{greeting}");     // Fine — greeting is still valid

    take_ownership(greeting);   // Ownership moves into the function
    // println!("{greeting}");  // ERROR: greeting has been moved!
}
Warning
The borrow checker is your friend, not your enemy. When you fight it, you lose. When you listen to it, you write better code. If the compiler rejects your code, it's almost always catching a real bug — even if it doesn't feel like it at first.

Error Handling

Rust doesn’t have exceptions. Instead, it uses the Result type — which forces you to handle errors explicitly:

use std::fs;
use std::io;

fn read_config(path: &str) -> Result<String, io::Error> {
    fs::read_to_string(path)
}

fn main() {
    match read_config("config.toml") {
        Ok(contents) => println!("Config loaded: {} bytes", contents.len()),
        Err(e) => eprintln!("Failed to read config: {e}"),
    }
}

No null pointer exceptions. No uncaught exceptions crashing your program at 3 AM. Just honest, explicit error handling.

Where to Go Next

Once you’re comfortable with the basics:

  • The Rust Book — the official guide, and genuinely one of the best programming books ever written
  • Rustlings — small exercises to practice concepts
  • Advent of Code — solve puzzles in Rust to build fluency
  • A small CLI tool — nothing teaches like building something real

Rust has a steep learning curve, but it’s not a cliff. It’s more like a mountain with excellent trail markers. Take it one step at a time, and enjoy the view.