Getting Started with Rust
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:
- Each value has exactly one owner
- When the owner goes out of scope, the value is dropped
- 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!
}
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.