Rust gives you the performance of C/C++ with memory safety guarantees at compile time. No garbage collector, no runtime overhead. The trade-off is a steeper learning curve — the compiler is strict, but once it compiles, a whole class of bugs simply can’t exist.
Why Rust
- Memory safety without a garbage collector
- Zero-cost abstractions — you don’t pay for what you don’t use
- Fearless concurrency — data races are caught at compile time
- Great tooling — cargo, clippy, rustfmt are all excellent
- Growing ecosystem for CLI tools, WebAssembly, embedded, and systems programming
- Compiles to a single static binary (like Go)
When to Use It
- Performance-critical systems (game engines, databases, OS components)
- CLI tools — fast startup, small binaries, no runtime dependencies
- WebAssembly — first-class WASM support
- Embedded systems — no_std for bare-metal
- When correctness matters more than speed of development
Core Concepts
Ownership
Every value has exactly one owner. When the owner goes out of scope, the value is dropped. This is how Rust manages memory without a GC.
let s1 = String::from("hello");
let s2 = s1; // s1 is MOVED to s2, s1 is no longer validBorrowing
Instead of transferring ownership, you can borrow a reference. Two rules:
- You can have many immutable references (
&T) OR one mutable reference (&mut T), never both - References must always be valid (no dangling pointers)
fn calculate_length(s: &String) -> usize {
s.len() // borrows s, doesn't take ownership
}Lifetimes
Lifetimes tell the compiler how long references are valid. Usually inferred, but sometimes you need to annotate them explicitly:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}Error Handling
No exceptions. Use Result<T, E> for recoverable errors and panic! for unrecoverable ones. The ? operator propagates errors cleanly:
fn read_file(path: &str) -> Result<String, io::Error> {
let content = fs::read_to_string(path)?;
Ok(content)
}Pattern Matching
match is exhaustive — the compiler ensures you handle every case:
match status_code {
200 => println!("OK"),
404 => println!("Not found"),
500..=599 => println!("Server error"),
_ => println!("Other"),
}Traits
Similar to interfaces in Go, but more powerful. Define shared behaviour:
trait Summary {
fn summarise(&self) -> String;
}Ecosystem
- blessed.rs — curated list of recommended crates for common tasks
- serde — serialisation/deserialisation (like encoding/json but for everything)
- tokio — async runtime
- clap — CLI argument parsing
- axum — web framework from the tokio team
- sqlx — async SQL with compile-time query checking
Resources
- The Rust Book — start here
- Rust by Example
- Rusty Ownership and the Lifecycle’s Stone
- Zero to Production in Rust — building a real backend
- Rust Atomics and Locks — concurrency deep dive
- 101 Course
- Concepts I Wish I Learned Earlier
- Rust Curious