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 valid

Borrowing

Instead of transferring ownership, you can borrow a reference. Two rules:

  1. You can have many immutable references (&T) OR one mutable reference (&mut T), never both
  2. 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