Rust is a modern systems programming language focused on safety, speed, and concurrency. It achieves these goals through a unique ownership model, a strong static type system, and zero-cost abstractions.


1. Type System

Rust’s type system is static and strong, catching many errors at compile time.

  • Primitive types: i32, u32, f64, bool, char
  • Compound types: Tuples ((i32, f64)), arrays ([i32; 3])
  • Structs: Custom data types with named fields.
    struct Point { x: i32, y: i32 }
  • Enums: Algebraic data types for variants.
    enum Result<T, E> { Ok(T), Err(E) }
  • Pattern matching: Exhaustive, ensures all cases are handled.
    match result {
        Ok(val) => println!("{}", val),
        Err(e) => println!("Error: {}", e),
    }
  • Traits: Define shared behavior (like interfaces).
    trait Drawable { fn draw(&self); }

See Type Systems and Rust Programming Language for more.


2. Ownership, Borrowing, and Lifetimes

Rust’s memory safety is enforced at compile time through ownership and borrowing.

  • Ownership: Each value has a single owner. When the owner goes out of scope, the value is dropped (memory freed).
    let s = String::from("hello"); // s owns the String
  • Move semantics: Assigning or passing a value moves ownership.
    let s1 = String::from("hi");
    let s2 = s1; // s1 is now invalid, s2 owns the String
  • Borrowing: References allow access without taking ownership.
    • Immutable borrow: fn foo(s: &String)
    • Mutable borrow: fn bar(s: &mut String)
    • Cannot have mutable and immutable borrows at the same time.
  • Lifetimes: The compiler checks that references never outlive the data they point to.
    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { ... }

See Resource Ownership and Memory Management for more.


3. Memory Management

  • No garbage collector: Memory is managed through ownership and the Drop trait.
  • Stack vs. Heap: Simple types and fixed-size data live on the stack; dynamic data (like String, Vec<T>) live on the heap.
  • Box, Rc, Arc:
    • Box<T>: Heap allocation for single ownership.
    • Rc<T>: Reference-counted pointer for shared ownership (single-threaded).
    • Arc<T>: Atomic reference-counted pointer for shared ownership (multi-threaded).
  • Automatic cleanup: When a value goes out of scope, its destructor (Drop) is called.

4. Error Handling

  • No exceptions: Rust uses Result<T, E> and Option<T> for error handling.
    fn divide(x: f64, y: f64) -> Option<f64> {
        if y == 0.0 { None } else { Some(x / y) }
    }
  • The ? operator: Propagates errors easily.
    fn read_file(path: &str) -> Result<String, std::io::Error> {
        let mut file = File::open(path)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        Ok(contents)
    }

5. Concurrency

Rust enables fearless concurrency by enforcing rules at compile time.

  • Threads: Spawned with std::thread::spawn.
    std::thread::spawn(|| println!("Hello from a thread!"));
  • Send and Sync traits: Types must be Send to move between threads, Sync to be shared.
  • Channels: For message passing between threads.
    let (tx, rx) = std::sync::mpsc::channel();
    tx.send(42).unwrap();
    println!("{}", rx.recv().unwrap());
  • No data races: The borrow checker and ownership system prevent simultaneous mutable access.

See Concurrency and Multicore Programming for more.


6. Asynchronous Programming

Rust supports async programming for efficient I/O and concurrency.

  • Async/await: Mark functions as async, use .await to yield.
    async fn fetch_url(url: &str) -> Result<String, reqwest::Error> {
        let body = reqwest::get(url).await?.text().await?;
        Ok(body)
    }
  • Futures: Async functions return a Future that is polled by an executor (e.g., tokio, async-std).
  • No built-in runtime: You choose an async runtime.

See Coroutines and Asynchronous Programming for more.


7. Type-Driven and Safe Design

  • Type-driven development: Design APIs and systems around types, letting the compiler enforce invariants.
  • State machines: Model states as enums or structs, transitions as methods that consume and return new states.
  • PhantomData and zero-sized types: Used for compile-time guarantees.

See Type-Driven Development for more.


8. Idioms and Best Practices

  • Prefer immutability by default (let vs. let mut).
  • Use pattern matching for control flow.
  • Leverage the type system to encode invariants.
  • Use crates.io for libraries and cargo for package management.
  • Write tests with #[test] functions.

9. Why Use Rust?

  • Eliminates entire classes of bugs (use-after-free, data races, buffer overflows).
  • No runtime or garbage collector overhead.
  • Modern tooling and package management (cargo).
  • Growing ecosystem and community.

See Also