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.
- Immutable borrow:
- 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
Droptrait. - 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>andOption<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
Sendto move between threads,Syncto 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.awaitto 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
Futurethat 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 (
letvs.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.