โ† Back to all guides

Rust TLDR

A rapid reference guide to the Rust programming language. Memory safety without garbage collection.

v1.91.1 โ€” December 2025

What is Rust?

Rust is a systems programming language focused on safety, speed, and concurrency. It achieves memory safety without garbage collection through its ownership system.

Memory Safety

Guarantees memory safety at compile time. No null pointers, no dangling references, no data races.

Zero-Cost Abstractions

High-level features compile to efficient low-level code. You don't pay for what you don't use.

Fearless Concurrency

The type system prevents data races. Write concurrent code with confidence.

Great Tooling

Cargo for package management, rustfmt for formatting, clippy for linting. Batteries included.

Basics

Hello World

fn main() {
    println!("Hello, world!");
}

Variables

// Immutable by default
let x: i32 = 42;
let y = 42;         // type inferred

// Mutable variables
let mut z = 0;
z += 1;

// Constants (must have type annotation)
const MAX_POINTS: u32 = 100_000;

// Shadowing
let x = 5;
let x = x + 1;  // x is now 6

Functions

fn add(a: i32, b: i32) -> i32 {
    a + b  // no semicolon = return value
}

// With explicit return
fn divide(a: f64, b: f64) -> f64 {
    if b == 0.0 {
        return 0.0;
    }
    a / b
}

// No return value (returns unit type)
fn greet(name: &str) {
    println!("Hello, {}!", name);
}

Control Flow

// if is an expression
let result = if x > 0 { "positive" } else { "non-positive" };

// loop (infinite, can return value)
let result = loop {
    if condition { break 42; }
};

// while
while x < 10 {
    x += 1;
}

// for (iterate over iterator)
for i in 0..10 {
    println!("{}", i);
}

for item in &items {
    println!("{}", item);
}

Types

Primitive Types

Category Types Notes
Signed Integers i8, i16, i32, i64, i128, isize isize is pointer-sized
Unsigned Integers u8, u16, u32, u64, u128, usize usize for indexing
Floats f32, f64 Default is f64
Boolean bool true or false
Character char 4 bytes, Unicode scalar
Unit () Empty tuple, like void

Compound Types

// Tuples (fixed size, mixed types)
let tup: (i32, f64, char) = (500, 6.4, 'z');
let (x, y, z) = tup;        // destructuring
let first = tup.0;          // index access

// Arrays (fixed size, same type)
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0; 5];        // [0, 0, 0, 0, 0]
let first = arr[0];        // bounds-checked!

Strings

// String slice (borrowed, immutable)
let s: &str = "hello";

// String (owned, growable)
let mut s = String::from("hello");
s.push_str(", world");
s.push('!');

// Conversion
let s: String = "hello".to_string();
let s: &str = &my_string;  // deref coercion

// Concatenation
let s = format!("{} {}", "hello", "world");

Ownership & Borrowing

Rust's core feature. Every value has a single owner, and when the owner goes out of scope, the value is dropped.

Ownership Rules

// 1. Each value has one owner
let s1 = String::from("hello");

// 2. Move: ownership transfers
let s2 = s1;          // s1 is now invalid!
// println!("{}", s1); // ERROR: value moved

// 3. Clone: explicit deep copy
let s1 = String::from("hello");
let s2 = s1.clone();  // s1 still valid

// 4. Copy types (stack-only) don't move
let x = 5;
let y = x;            // x still valid (Copy trait)

References & Borrowing

let s = String::from("hello");

// Immutable reference (can have many)
let r1 = &s;
let r2 = &s;

// Mutable reference (only one at a time)
let mut s = String::from("hello");
let r = &mut s;
r.push_str(", world");

// Function with reference
fn len(s: &String) -> usize {
    s.len()
}

// Mutable reference parameter
fn append(s: &mut String) {
    s.push_str("!");
}

Slices

let s = String::from("hello world");

// String slices
let hello: &str = &s[0..5];
let world: &str = &s[6..];

// Array slices
let arr = [1, 2, 3, 4, 5];
let slice: &[i32] = &arr[1..3];  // [2, 3]

Lifetimes

// Lifetime annotation: 'a
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

// Struct with lifetime
struct Excerpt<'a> {
    part: &'a str,
}

// Static lifetime (lives forever)
let s: &'static str = "I live forever";
References must always be valid. The borrow checker ensures you never have dangling references.

Structs & Enums

Structs

// Define struct
struct User {
    username: String,
    email: String,
    active: bool,
}

// Create instance
let user = User {
    username: String::from("alice"),
    email: String::from("alice@example.com"),
    active: true,
};

// Field init shorthand
fn build_user(email: String) -> User {
    User {
        email,  // shorthand
        username: String::from("anonymous"),
        active: true,
    }
}

// Struct update syntax
let user2 = User {
    email: String::from("bob@example.com"),
    ..user  // rest from user
};

Tuple Structs & Unit Structs

// Tuple struct
struct Color(u8, u8, u8);
let black = Color(0, 0, 0);

// Unit struct (no fields)
struct Marker;

Methods (impl blocks)

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // Associated function (no self) - constructor
    fn new(width: u32, height: u32) -> Self {
        Self { width, height }
    }

    // Method (takes &self)
    fn area(&self) -> u32 {
        self.width * self.height
    }

    // Mutable method
    fn scale(&mut self, factor: u32) {
        self.width *= factor;
        self.height *= factor;
    }
}

let rect = Rectangle::new(30, 50);
println!("Area: {}", rect.area());

Enums

// Simple enum
enum Direction {
    North,
    South,
    East,
    West,
}

// Enum with data
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}

let msg = Message::Write(String::from("hello"));

Pattern Matching

// match is exhaustive
match msg {
    Message::Quit => println!("Quit"),
    Message::Move { x, y } => println!("Move to {}, {}", x, y),
    Message::Write(text) => println!("Text: {}", text),
    Message::ChangeColor(r, g, b) => println!("RGB: {}, {}, {}", r, g, b),
}

// if let (single pattern)
if let Message::Write(text) = msg {
    println!("Got text: {}", text);
}

// while let
while let Some(item) = stack.pop() {
    println!("{}", item);
}

Option & Pattern Guards

// Option<T> - nullable type
let some_number: Option<i32> = Some(5);
let no_number: Option<i32> = None;

// Pattern guards
match x {
    Some(n) if n > 0 => println!("positive"),
    Some(n) if n < 0 => println!("negative"),
    Some(_) => println!("zero"),
    None => println!("nothing"),
}

Traits

Traits define shared behavior. Similar to interfaces in other languages.

Defining & Implementing Traits

// Define trait
trait Summary {
    fn summarize(&self) -> String;

    // Default implementation
    fn preview(&self) -> String {
        format!("Read more: {}", self.summarize())
    }
}

// Implement trait
impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{} by {}", self.title, self.author)
    }
}

Trait Bounds

// Function with trait bound
fn notify(item: &impl Summary) {
    println!("Breaking: {}", item.summarize());
}

// Generic with trait bound
fn notify<T: Summary>(item: &T) {
    println!("Breaking: {}", item.summarize());
}

// Multiple bounds
fn process<T: Summary + Clone>(item: &T) { }

// where clause (cleaner)
fn process<T>(item: &T)
where
    T: Summary + Clone,
{
    // ...
}

Common Traits

// Derive common traits
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Point {
    x: i32,
    y: i32,
}

// Debug: {:?} formatting
println!("{:?}", point);

// Display: {} formatting (manual impl)
impl std::fmt::Display for Point {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}
Clone
Explicit deep copy via .clone()
Copy
Implicit bitwise copy (stack-only types)
Debug
Format with {:?}
Default
Create default value via Default::default()
PartialEq/Eq
Equality comparison (==)
PartialOrd/Ord
Ordering comparison (<, >)

Error Handling

Rust has no exceptions. Errors are values, handled via Result and Option types.

Result Type

// Result<T, E> - success or error
enum Result<T, E> {
    Ok(T),
    Err(E),
}

fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("division by zero"))
    } else {
        Ok(a / b)
    }
}

Handling Results

// match
match divide(10.0, 2.0) {
    Ok(result) => println!("Result: {}", result),
    Err(e) => println!("Error: {}", e),
}

// unwrap (panics on Err)
let result = divide(10.0, 2.0).unwrap();

// expect (panics with message)
let result = divide(10.0, 2.0).expect("division failed");

// unwrap_or (default value)
let result = divide(10.0, 0.0).unwrap_or(0.0);

// unwrap_or_else (closure for default)
let result = divide(10.0, 0.0).unwrap_or_else(|e| {
    println!("Error: {}", e);
    0.0
});

The ? Operator

// Propagate errors with ?
fn read_file() -> Result<String, std::io::Error> {
    let mut file = File::open("file.txt")?;  // returns Err if fails
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

// Also works with Option (returns None)
fn first_char(s: &str) -> Option<char> {
    s.lines().next()?.chars().next()
}

Custom Error Types

use std::error::Error;
use std::fmt;

#[derive(Debug)]
struct MyError {
    message: String,
}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl Error for MyError {}
Use anyhow crate for application code, thiserror for library code to simplify error handling.

Collections

Vec (Dynamic Array)

// Create
let mut v: Vec<i32> = Vec::new();
let v = vec![1, 2, 3];

// Modify
v.push(4);
let last = v.pop();  // Option<i32>

// Access
let third = &v[2];           // panics if out of bounds
let third = v.get(2);        // Option<&i32>

// Iterate
for item in &v {
    println!("{}", item);
}

// Mutate while iterating
for item in &mut v {
    *item += 10;
}

HashMap

use std::collections::HashMap;

// Create
let mut scores = HashMap::new();

// Insert
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Red"), 50);

// Access
let score = scores.get("Blue");  // Option<&i32>

// Insert only if key doesn't exist
scores.entry(String::from("Yellow")).or_insert(30);

// Update based on old value
let count = scores.entry(String::from("Blue")).or_insert(0);
*count += 1;

// Iterate
for (key, value) in &scores {
    println!("{}: {}", key, value);
}

HashSet

use std::collections::HashSet;

let mut set = HashSet::new();
set.insert(1);
set.insert(2);

let contains = set.contains(&1);  // true
let removed = set.remove(&1);    // true

Iterators & Closures

Closures

// Basic closure
let add = |a, b| a + b;
let result = add(2, 3);

// With type annotations
let add = |a: i32, b: i32| -> i32 { a + b };

// Capturing environment
let x = 4;
let equals_x = |z| z == x;  // captures x

// move keyword (take ownership)
let s = String::from("hello");
let closure = move || println!("{}", s);

Iterator Methods

let v = vec![1, 2, 3, 4, 5];

// map: transform each element
let doubled: Vec<_> = v.iter().map(|x| x * 2).collect();

// filter: keep matching elements
let evens: Vec<_> = v.iter().filter(|x| *x % 2 == 0).collect();

// fold: reduce to single value
let sum: i32 = v.iter().fold(0, |acc, x| acc + x);

// find: first matching element
let first_even = v.iter().find(|x| *x % 2 == 0);

// Chain methods
let result: i32 = v.iter()
    .filter(|x| *x % 2 == 0)
    .map(|x| x * 2)
    .sum();

Iterator Types

// iter(): borrows elements (&T)
for item in v.iter() { }

// iter_mut(): mutable borrow (&mut T)
for item in v.iter_mut() { }

// into_iter(): takes ownership (T)
for item in v.into_iter() { }

// Ranges
for i in 0..10 { }     // 0 to 9
for i in 0..=10 { }    // 0 to 10 (inclusive)

Cargo & Project Structure

Common Commands

# Create new project
cargo new myproject
cargo new --lib mylibrary

# Build
cargo build
cargo build --release

# Run
cargo run
cargo run --release

# Test
cargo test
cargo test test_name

# Check (fast compile check)
cargo check

# Format & lint
cargo fmt
cargo clippy

# Documentation
cargo doc --open

# Add dependency
cargo add serde
cargo add tokio --features full

Cargo.toml

[package]
name = "myproject"
version = "0.1.0"
edition = "2024"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }

[dev-dependencies]
criterion = "0.5"

Project Structure

myproject/
โ”œโ”€โ”€ Cargo.toml
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ main.rs      # Binary entry point
โ”‚   โ”œโ”€โ”€ lib.rs       # Library root
โ”‚   โ””โ”€โ”€ module/
โ”‚       โ””โ”€โ”€ mod.rs
โ”œโ”€โ”€ tests/           # Integration tests
โ”œโ”€โ”€ benches/         # Benchmarks
โ””โ”€โ”€ examples/        # Example programs

Modules

// In lib.rs or main.rs
mod utils;           // loads utils.rs or utils/mod.rs
pub mod api;         // public module

// Use items
use crate::utils::helper;
use std::collections::HashMap;
use serde::{Serialize, Deserialize};

// Re-export
pub use self::types::Config;