Rust Cheatsheet
Rust is a systems programming language focused on safety, speed, and concurrency. Created by Graydon Hoare at Mozilla Research and first released in 2015 with version 1.0, Rust guarantees memory safety without a garbage collector through its ownership system — checked entirely at compile time. It consistently ranks as the most loved programming language in developer surveys. This cheatsheet is a comprehensive reference for writing real applications in Rust.
Getting Started
Section titled “Getting Started”Installing Rust
Section titled “Installing Rust”Rust is installed and managed through rustup, the official toolchain installer. It handles the compiler (rustc), the package manager (cargo), and the standard library.
macOS / Linux:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh# Verify:rustc --versioncargo --versionWindows: Download and run rustup-init.exe or use:
winget install Rustlang.RustupToolchain Components
Section titled “Toolchain Components”| Tool | Purpose |
|---|---|
rustc | The Rust compiler |
cargo | Package manager and build system |
rustup | Toolchain installer and version manager |
rustfmt | Code formatter (cargo fmt) |
clippy | Linter with helpful suggestions (cargo clippy) |
rust-analyzer | Language server for IDE support |
Compiling and Running Your First Program
Section titled “Compiling and Running Your First Program”Create a file called hello.rs:
fn main() { println!("Hello, World!");}Compile and run directly:
rustc hello.rs # compile./hello # run# Output: Hello, World!In practice, you almost always use Cargo instead of calling rustc directly.
Rust Editions
Section titled “Rust Editions”| Edition | Year | Key Features |
|---|---|---|
| 2015 | 2015 | Original stable release, ownership, traits, pattern matching |
| 2018 | 2018 | async/await, module system simplification, NLL borrow checker |
| 2021 | 2021 | Disjoint capture in closures, IntoIterator for arrays, new prelude |
| 2024 | 2024 | gen blocks, unsafe_op_in_unsafe_fn lint, RPIT lifetime capture rules |
Editions are opt-in per crate (set in Cargo.toml) and are fully interoperable — a 2021-edition crate can depend on a 2018-edition crate without issues.
RFC 959, Section Getting Started — "doc.rust-lang.org/book"
Program Structure
Section titled “Program Structure”Every Rust program starts at the fn main() function. There is no header file system — the module system handles code organization. The use keyword brings items into scope.
use std::io;
fn main() { println!("Enter your name:");
let mut name = String::new(); io::stdin() .read_line(&mut name) .expect("Failed to read line");
println!("Hello, {}!", name.trim());}Key observations:
fn main()is the entry point — no return type needed (defaults to()).letdeclares variables.mutmakes them mutable.println!is a macro (note the!), not a function.- Strings require explicit allocation (
String::new()). - Error handling is explicit —
.expect()panics with a message on failure.
RFC 959, Section Common Programming Concepts — "doc.rust-lang.org/book"
Comments
Section titled “Comments”Comments are non-executable annotations. Rust supports three forms:
// Single-line comment
/* Multi-line comment */
/// Documentation comment (generates docs via rustdoc)/// Supports **Markdown** formatting.fn documented_function() {}Documentation comments (///) are special — cargo doc compiles them into HTML documentation. Use //! for module-level documentation at the top of a file.
RFC 959, Section Comments — "doc.rust-lang.org/book"
Project Structure and Cargo
Section titled “Project Structure and Cargo”Cargo is Rust’s build system and package manager. It handles compiling code, downloading dependencies (called crates), and running tests.
Creating a Project
Section titled “Creating a Project”cargo new my_project # binary project (has main.rs)cargo new my_lib --lib # library project (has lib.rs)Typical Project Layout
Section titled “Typical Project Layout”my_project/├── Cargo.toml # Project manifest (dependencies, metadata)├── Cargo.lock # Exact dependency versions (commit this for binaries)├── src/│ ├── main.rs # Binary entry point│ ├── lib.rs # Library root (if applicable)│ └── module_name/ # Submodule directory│ └── mod.rs├── tests/ # Integration tests│ └── integration_test.rs├── benches/ # Benchmarks├── examples/ # Example programs│ └── demo.rs└── target/ # Build output (gitignored)Cargo.toml
Section titled “Cargo.toml”[package]name = "my_project"version = "0.1.0"edition = "2021"
[dependencies]serde = { version = "1.0", features = ["derive"] }tokio = { version = "1", features = ["full"] }
[dev-dependencies]criterion = "0.5"Cargo Commands
Section titled “Cargo Commands”| Command | Purpose |
|---|---|
cargo build | Compile the project |
cargo build --release | Compile with optimizations |
cargo run | Build and run |
cargo test | Run all tests |
cargo check | Type-check without producing a binary (faster) |
cargo fmt | Format code with rustfmt |
cargo clippy | Run the linter |
cargo doc --open | Generate and open documentation |
cargo add serde | Add a dependency to Cargo.toml |
cargo update | Update dependencies to latest compatible versions |
RFC 959, Section Hello, Cargo! — "doc.rust-lang.org/book"
Variables and Mutability
Section titled “Variables and Mutability”Variables in Rust are immutable by default. This is a deliberate design choice — immutability prevents accidental state changes and makes code easier to reason about.
let x = 5; // immutable// x = 6; // ERROR: cannot assign twice to immutable variable
let mut y = 5; // mutabley = 6; // OKShadowing
Section titled “Shadowing”Rust allows shadowing — redeclaring a variable with the same name. Unlike mutation, shadowing creates a new variable and can change the type.
let x = 5;let x = x + 1; // x is now 6 (new variable, same name)let x = "hello"; // x is now a &str (type changed — only possible with shadowing)Constants
Section titled “Constants”Constants are always immutable and must have a type annotation. They can be declared in any scope, including global scope.
const MAX_POINTS: u32 = 100_000;const PI: f64 = 3.141592653589793;Constants differ from let bindings: they must be known at compile time, cannot be mut, and are inlined wherever they are used.
Static Variables
Section titled “Static Variables”Static variables have a fixed memory address for the entire program lifetime. They can be mutable, but accessing mutable statics is unsafe.
static GREETING: &str = "Hello";static mut COUNTER: u32 = 0;
// Mutable statics require unsafeunsafe { COUNTER += 1;}RFC 959, Section Variables and Mutability — "doc.rust-lang.org/book"
Data Types
Section titled “Data Types”Rust is statically typed — every value has a type known at compile time. The compiler can usually infer types, but sometimes you need explicit annotations.
Integer Types
Section titled “Integer Types”| Type | Size | Range |
|---|---|---|
i8 | 1 byte | -128 to 127 |
i16 | 2 bytes | -32,768 to 32,767 |
i32 | 4 bytes | -2,147,483,648 to 2,147,483,647 |
i64 | 8 bytes | -9.2 × 10¹⁸ to 9.2 × 10¹⁸ |
i128 | 16 bytes | -1.7 × 10³⁸ to 1.7 × 10³⁸ |
u8 | 1 byte | 0 to 255 |
u16 | 2 bytes | 0 to 65,535 |
u32 | 4 bytes | 0 to 4,294,967,295 |
u64 | 8 bytes | 0 to 1.8 × 10¹⁹ |
u128 | 16 bytes | 0 to 3.4 × 10³⁸ |
isize | Pointer-sized | Architecture-dependent (32 or 64 bit) |
usize | Pointer-sized | Architecture-dependent — used for indexing |
i32 is the default integer type. Use usize for collection indices and lengths.
Integer Literals
Section titled “Integer Literals”let decimal = 98_222; // underscores for readabilitylet hex = 0xff;let octal = 0o77;let binary = 0b1111_0000;let byte = b'A'; // u8 onlylet typed: u64 = 42u64; // type suffixFloating-Point Types
Section titled “Floating-Point Types”| Type | Size | Precision |
|---|---|---|
f32 | 4 bytes | ~6-7 decimal digits |
f64 | 8 bytes | ~15-16 decimal digits |
f64 is the default — modern CPUs handle it at roughly the same speed as f32.
let x = 2.0; // f64 (default)let y: f32 = 3.0; // f32 (explicit)Boolean
Section titled “Boolean”let t = true;let f: bool = false;Character
Section titled “Character”Rust’s char is 4 bytes and represents a Unicode scalar value — it can hold any Unicode character, including emoji.
let c = 'z';let heart = '❤';let crab = '🦀';Tuples
Section titled “Tuples”Tuples group values of different types into a single compound type. They have a fixed length.
let tup: (i32, f64, u8) = (500, 6.4, 1);
// Destructuringlet (x, y, z) = tup;
// Index accesslet five_hundred = tup.0;let six_point_four = tup.1;The empty tuple () is called the unit type — it represents an empty value or empty return type.
Arrays
Section titled “Arrays”Arrays have a fixed length and contain elements of the same type. They are stack-allocated.
let a = [1, 2, 3, 4, 5];let a: [i32; 5] = [1, 2, 3, 4, 5]; // explicit typelet zeros = [0; 5]; // [0, 0, 0, 0, 0]
let first = a[0];let len = a.len();Array access is bounds-checked at runtime — out-of-bounds access panics instead of causing undefined behavior.
Type Aliases
Section titled “Type Aliases”type Kilometers = i32;type Result<T> = std::result::Result<T, std::io::Error>;Type Casting
Section titled “Type Casting”Rust does not perform implicit type conversions. Use as for primitive casts:
let x: i32 = 42;let y: f64 = x as f64;let z: u8 = 255u16 as u8; // truncates if out of rangeFor fallible conversions, use the TryFrom / TryInto traits:
let x: i32 = 256;let y: Result<u8, _> = u8::try_from(x); // Err — 256 doesn't fit in u8RFC 959, Section Data Types — "doc.rust-lang.org/book"
Input and Output
Section titled “Input and Output”Printing
Section titled “Printing”println!("Hello, World!"); // with newlineprint!("No newline"); // without newlineeprintln!("Error message"); // to stderr
// Formattingprintln!("x = {}, y = {}", 10, 20); // positionalprintln!("x = {x}, y = {y}", x = 10, y = 20); // namedprintln!("{:?}", vec![1, 2, 3]); // debug formatprintln!("{:#?}", vec![1, 2, 3]); // pretty debug formatprintln!("{:>10}", "right"); // right-aligned, width 10println!("{:<10}", "left"); // left-alignedprintln!("{:0>5}", 42); // zero-padded: "00042"println!("{:.2}", 3.14159); // 2 decimal places: "3.14"println!("{:#b}", 42); // binary: "0b101010"println!("{:#x}", 255); // hex: "0xff"Reading Input
Section titled “Reading Input”use std::io;
let mut input = String::new();io::stdin() .read_line(&mut input) .expect("Failed to read line");
let number: i32 = input.trim().parse().expect("Not a number");RFC 959, Section Common Programming Concepts — "doc.rust-lang.org/book"
Conditional Statements
Section titled “Conditional Statements”if Expression
Section titled “if Expression”if in Rust is an expression — it returns a value. Conditions must be bool (no implicit truthy/falsy conversions).
let number = 7;
if number < 5 { println!("less than 5");} else if number < 10 { println!("between 5 and 9");} else { println!("10 or greater");}if as an Expression
Section titled “if as an Expression”Because if is an expression, you can use it on the right side of a let statement:
let condition = true;let number = if condition { 5 } else { 6 };Both arms must return the same type.
if let
Section titled “if let”if let is syntactic sugar for matching a single pattern — useful when you only care about one variant of an enum.
let some_value: Option<i32> = Some(42);
if let Some(value) = some_value { println!("Got: {value}");} else { println!("Got nothing");}let-else
Section titled “let-else”let-else (Rust 1.65+) binds a pattern or diverges. Useful for early returns:
fn process(value: Option<i32>) { let Some(x) = value else { println!("No value, returning early"); return; }; println!("Processing {x}");}RFC 959, Section Control Flow — "doc.rust-lang.org/book"
Pattern Matching
Section titled “Pattern Matching”Pattern matching is one of Rust’s most powerful features. The match expression compares a value against a series of patterns and executes the code for the first match.
match Expression
Section titled “match Expression”let number = 3;
match number { 1 => println!("one"), 2 => println!("two"), 3 | 4 => println!("three or four"), // multiple patterns 5..=10 => println!("five through ten"), // range pattern _ => println!("something else"), // wildcard (catch-all)}match is exhaustive — you must handle every possible value. The compiler enforces this.
Matching Enums
Section titled “Matching Enums”enum Coin { Penny, Nickel, Dime, Quarter(String), // variant with data}
fn value_in_cents(coin: &Coin) -> u32 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter(state) => { println!("Quarter from {state}"); 25 } }}Destructuring in Patterns
Section titled “Destructuring in Patterns”let point = (3, 5);
match point { (0, 0) => println!("origin"), (x, 0) => println!("on x-axis at {x}"), (0, y) => println!("on y-axis at {y}"), (x, y) => println!("at ({x}, {y})"),}Match Guards
Section titled “Match Guards”let num = Some(4);
match num { Some(x) if x < 5 => println!("less than five: {x}"), Some(x) => println!("{x}"), None => println!("nothing"),}@ Bindings
Section titled “@ Bindings”Bind a value to a name while also testing it against a pattern:
match age { n @ 0..=12 => println!("child aged {n}"), n @ 13..=17 => println!("teenager aged {n}"), n => println!("adult aged {n}"),}RFC 959, Section Patterns and Matching — "doc.rust-lang.org/book"
An infinite loop — break with break. Loops can return values:
let mut counter = 0;
let result = loop { counter += 1; if counter == 10 { break counter * 2; // returns 20 }};Loop Labels
Section titled “Loop Labels”Labels disambiguate nested loops:
'outer: loop { 'inner: loop { break 'outer; // breaks the outer loop }}let mut number = 3;
while number != 0 { println!("{number}"); number -= 1;}while let
Section titled “while let”Loop while a pattern matches:
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() { println!("{top}");}// Prints: 3, 2, 1The for loop iterates over anything that implements IntoIterator:
let a = [10, 20, 30, 40, 50];
for element in a { println!("{element}");}
// With indexfor (i, val) in a.iter().enumerate() { println!("{i}: {val}");}
// Rangefor number in 1..=5 { println!("{number}"); // 1, 2, 3, 4, 5}
for number in (1..4).rev() { println!("{number}"); // 3, 2, 1}RFC 959, Section Loops — "doc.rust-lang.org/book"
Ownership and Borrowing
Section titled “Ownership and Borrowing”Ownership is Rust’s core mechanism for memory safety without garbage collection. It is enforced entirely at compile time with zero runtime cost.
The Three Rules
Section titled “The Three Rules”- Each value in Rust has exactly one owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value is dropped (freed).
{ let s = String::from("hello"); // s owns the String // s is valid here} // s goes out of scope — String is dropped and memory is freedMove Semantics
Section titled “Move Semantics”Assignment transfers ownership for heap-allocated types. The original variable becomes invalid.
let s1 = String::from("hello");let s2 = s1; // s1 is MOVED to s2// println!("{s1}"); // ERROR: s1 is no longer validprintln!("{s2}"); // OKTypes that implement the Copy trait (integers, floats, booleans, characters, tuples of Copy types) are copied instead of moved:
let x = 5;let y = x; // x is COPIED (i32 implements Copy)println!("{x}"); // OK — x is still validFor heap-allocated types, use .clone() to explicitly create a deep copy:
let s1 = String::from("hello");let s2 = s1.clone();println!("{s1} {s2}"); // both validReferences and Borrowing
Section titled “References and Borrowing”A reference borrows a value without taking ownership. References are created with &.
fn calculate_length(s: &String) -> usize { s.len()} // s goes out of scope, but since it doesn't own the String, nothing is dropped
let s1 = String::from("hello");let len = calculate_length(&s1); // borrow s1println!("{s1} has length {len}"); // s1 is still validMutable References
Section titled “Mutable References”Mutable references (&mut) allow modifying borrowed data — but with a restriction: you can have either one mutable reference or any number of immutable references, but not both at the same time.
fn push_world(s: &mut String) { s.push_str(", world");}
let mut s = String::from("hello");push_world(&mut s);println!("{s}"); // "hello, world"let mut s = String::from("hello");
let r1 = &s; // OK — immutable borrowlet r2 = &s; // OK — multiple immutable borrows// let r3 = &mut s; // ERROR: cannot borrow as mutable while immutable borrows exist
println!("{r1} {r2}");// r1 and r2 are no longer used after this point (NLL)
let r3 = &mut s; // OK — previous borrows have endedprintln!("{r3}");Dangling References
Section titled “Dangling References”The compiler prevents dangling references — you cannot return a reference to data that will be dropped:
// fn dangle() -> &String { // ERROR: returns reference to dropped value// let s = String::from("hello");// &s// }
fn no_dangle() -> String { let s = String::from("hello"); s // ownership is moved out — no dangling reference}RFC 959, Section Understanding Ownership — "doc.rust-lang.org/book"
Slices
Section titled “Slices”A slice is a reference to a contiguous sequence of elements in a collection. Slices don’t own data — they borrow a portion of it.
String Slices
Section titled “String Slices”let s = String::from("hello world");
let hello = &s[0..5]; // "hello"let world = &s[6..11]; // "world"let hello = &s[..5]; // same — start from 0let world = &s[6..]; // same — go to endlet whole = &s[..]; // entire stringThe type of a string slice is &str. String literals ("hello") are also &str — they are slices pointing into the binary.
Array Slices
Section titled “Array Slices”let a = [1, 2, 3, 4, 5];let slice = &a[1..3]; // &[i32] containing [2, 3]RFC 959, Section The Slice Type — "doc.rust-lang.org/book"
Strings
Section titled “Strings”Rust has two main string types:
| Type | Ownership | Mutability | Storage |
|---|---|---|---|
String | Owned | Growable | Heap-allocated |
&str | Borrowed | Immutable view | Stack pointer + length |
Creating Strings
Section titled “Creating Strings”let s1 = String::new(); // emptylet s2 = String::from("hello"); // from literallet s3 = "hello".to_string(); // from &strlet s4 = format!("{}-{}", "hello", "world"); // format macroString Operations
Section titled “String Operations”let mut s = String::from("hello");
s.push(' '); // push a chars.push_str("world"); // push a string sliceprintln!("{s}"); // "hello world"
let s1 = String::from("Hello, ");let s2 = String::from("world!");let s3 = s1 + &s2; // s1 is moved, s2 is borrowed// println!("{s1}"); // ERROR: s1 was moved
println!("{}", s3.len()); // byte lengthprintln!("{}", s3.is_empty()); // falseprintln!("{}", s3.contains("world")); // trueprintln!("{}", s3.to_uppercase()); // "HELLO, WORLD!"
// Iteratingfor c in "hello".chars() { println!("{c}");}
for b in "hello".bytes() { println!("{b}");}String Slicing
Section titled “String Slicing”Rust strings are UTF-8 encoded. Indexing by byte position can panic if it lands in the middle of a multi-byte character:
let hello = String::from("Здравствуйте");// let s = &hello[0..1]; // PANIC: byte index 1 is not a char boundarylet s = &hello[0..4]; // OK: "Зд" (each Cyrillic char is 2 bytes)RFC 959, Section Storing UTF-8 Encoded Text with Strings — "doc.rust-lang.org/book"
Vectors
Section titled “Vectors”A Vec<T> is a growable, heap-allocated array. It is the most commonly used collection in Rust.
let v: Vec<i32> = Vec::new(); // empty, with type annotationlet v = vec![1, 2, 3]; // macro shorthand
let mut v = Vec::new();v.push(5);v.push(6);v.push(7);Accessing Elements
Section titled “Accessing Elements”let v = vec![1, 2, 3, 4, 5];
let third = &v[2]; // panics if out of boundslet third = v.get(2); // returns Option<&i32>
match v.get(10) { Some(val) => println!("{val}"), None => println!("No element at index 10"),}Common Operations
Section titled “Common Operations”let mut v = vec![1, 2, 3];
v.push(4); // appendv.pop(); // remove last → Some(4)v.insert(0, 0); // insert at indexv.remove(0); // remove at indexv.len(); // lengthv.is_empty(); // check emptyv.contains(&2); // searchv.sort(); // sort in placev.dedup(); // remove consecutive duplicatesv.retain(|&x| x > 1); // keep elements matching predicatev.extend([4, 5, 6]); // append from iteratorIterating
Section titled “Iterating”let v = vec![100, 32, 57];
for val in &v { println!("{val}");}
// Mutable iterationlet mut v = vec![100, 32, 57];for val in &mut v { *val += 50;}RFC 959, Section Storing Lists of Values with Vectors — "doc.rust-lang.org/book"
Functions
Section titled “Functions”fn add(x: i32, y: i32) -> i32 { x + y // no semicolon = implicit return (expression)}
fn print_value(x: i32) { println!("{x}"); // no return type = returns ()}Statements vs Expressions
Section titled “Statements vs Expressions”In Rust, almost everything is an expression (returns a value). Statements perform an action but don’t return a value. The last expression in a block (without a semicolon) is the block’s return value.
let y = { let x = 3; x + 1 // expression — this is the block's value};// y == 4Adding a semicolon turns an expression into a statement (returns ()).
Function Parameters
Section titled “Function Parameters”All parameters require type annotations:
fn greet(name: &str, times: u32) { for _ in 0..times { println!("Hello, {name}!"); }}Returning Multiple Values
Section titled “Returning Multiple Values”Use tuples to return multiple values:
fn swap(a: i32, b: i32) -> (i32, i32) { (b, a)}
let (x, y) = swap(1, 2);Early Return
Section titled “Early Return”Use return for explicit early returns:
fn find_first_positive(numbers: &[i32]) -> Option<i32> { for &n in numbers { if n > 0 { return Some(n); } } None // implicit return}Diverging Functions
Section titled “Diverging Functions”Functions that never return have the return type ! (the never type):
fn forever() -> ! { loop { // runs forever }}RFC 959, Section Functions — "doc.rust-lang.org/book"
Closures
Section titled “Closures”Closures are anonymous functions that can capture variables from their enclosing scope.
let add = |a, b| a + b;println!("{}", add(2, 3)); // 5
let x = 10;let add_x = |a| a + x; // captures x from the environmentprintln!("{}", add_x(5)); // 15Closure Type Annotations
Section titled “Closure Type Annotations”let add = |a: i32, b: i32| -> i32 { a + b };Capture Modes
Section titled “Capture Modes”Closures capture variables in the least restrictive way possible:
| Mode | Syntax | Behavior |
|---|---|---|
Borrow (&T) | automatic | Reads the captured variable |
Mutable borrow (&mut T) | automatic | Modifies the captured variable |
Move (T) | move || | Takes ownership of the captured variable |
let mut list = vec![1, 2, 3];
// Immutable borrowlet print_list = || println!("{list:?}");print_list();
// Mutable borrowlet mut push_to = || list.push(4);push_to();
// Move — transfers ownership into the closurelet list = vec![1, 2, 3];let owns_list = move || println!("{list:?}");// println!("{list:?}"); // ERROR: list was movedowns_list();Closure Traits
Section titled “Closure Traits”| Trait | Description | Can be called |
|---|---|---|
Fn | Borrows captured values immutably | Multiple times |
FnMut | Borrows captured values mutably | Multiple times |
FnOnce | Takes ownership of captured values | Once |
Every closure implements FnOnce. If it doesn’t move captured values, it also implements FnMut. If it doesn’t mutate, it also implements Fn.
fn apply<F: Fn(i32) -> i32>(f: F, val: i32) -> i32 { f(val)}
let double = |x| x * 2;println!("{}", apply(double, 5)); // 10RFC 959, Section Closures — "doc.rust-lang.org/book"
Iterators
Section titled “Iterators”Iterators are lazy — they produce values one at a time and do nothing until consumed.
Creating Iterators
Section titled “Creating Iterators”let v = vec![1, 2, 3];
let iter = v.iter(); // yields &Tlet iter = v.iter_mut(); // yields &mut Tlet iter = v.into_iter(); // yields T (consumes the vec)Iterator Adaptors (Lazy)
Section titled “Iterator Adaptors (Lazy)”Adaptors transform an iterator into another iterator. They are lazy and must be consumed.
let v = vec![1, 2, 3, 4, 5];
let result: Vec<i32> = v.iter() .filter(|&&x| x > 2) .map(|&x| x * 10) .collect();// [30, 40, 50]Common Adaptors
Section titled “Common Adaptors”| Method | Description |
|---|---|
.map(f) | Transform each element |
.filter(f) | Keep elements where predicate returns true |
.enumerate() | Yield (index, value) pairs |
.zip(other) | Pair elements from two iterators |
.chain(other) | Concatenate two iterators |
.take(n) | Take first n elements |
.skip(n) | Skip first n elements |
.flatten() | Flatten nested iterators |
.peekable() | Allow peeking at the next element |
.rev() | Reverse (requires DoubleEndedIterator) |
.cloned() | Clone each element (from &T to T) |
Consuming Methods
Section titled “Consuming Methods”| Method | Description |
|---|---|
.collect() | Gather into a collection |
.sum() | Sum all elements |
.product() | Multiply all elements |
.count() | Count elements |
.any(f) | True if any element satisfies the predicate |
.all(f) | True if all elements satisfy the predicate |
.find(f) | First element satisfying the predicate |
.position(f) | Index of first element satisfying the predicate |
.min() / .max() | Minimum / maximum element |
.fold(init, f) | Reduce to a single value |
.for_each(f) | Apply a function to each element (like for) |
let sum: i32 = (1..=100).sum(); // 5050let factorial: u64 = (1..=10).product(); // 3628800
let v = vec![1, 2, 3, 4, 5];let sum = v.iter().fold(0, |acc, &x| acc + x); // 15
let evens: Vec<i32> = (1..=10).filter(|x| x % 2 == 0).collect();// [2, 4, 6, 8, 10]Implementing Iterator
Section titled “Implementing Iterator”struct Counter { count: u32, max: u32,}
impl Counter { fn new(max: u32) -> Self { Counter { count: 0, max } }}
impl Iterator for Counter { type Item = u32;
fn next(&mut self) -> Option<Self::Item> { if self.count < self.max { self.count += 1; Some(self.count) } else { None } }}
let sum: u32 = Counter::new(5).sum(); // 15RFC 959, Section Iterators — "doc.rust-lang.org/book"
Structs
Section titled “Structs”Structs are custom data types that group related values together.
Defining and Instantiating
Section titled “Defining and Instantiating”struct User { username: String, email: String, active: bool, sign_in_count: u64,}
let user1 = User { email: String::from("user@example.com"), username: String::from("user123"), active: true, sign_in_count: 1,};Field Init Shorthand
Section titled “Field Init Shorthand”When variable names match field names:
fn build_user(email: String, username: String) -> User { User { email, // shorthand for email: email username, // shorthand for username: username active: true, sign_in_count: 1, }}Struct Update Syntax
Section titled “Struct Update Syntax”Create a new instance from an existing one, overriding specific fields:
let user2 = User { email: String::from("another@example.com"), ..user1 // remaining fields from user1 (user1.username is MOVED)};Tuple Structs
Section titled “Tuple Structs”Named tuples — useful for creating distinct types:
struct Color(i32, i32, i32);struct Point(i32, i32, i32);
let black = Color(0, 0, 0);let origin = Point(0, 0, 0);// black and origin are different types even though they have the same field typesUnit Structs
Section titled “Unit Structs”Structs with no fields — useful for implementing traits on a type with no data:
struct AlwaysEqual;
impl PartialEq for AlwaysEqual { fn eq(&self, _other: &Self) -> bool { true }}Methods and Associated Functions
Section titled “Methods and Associated Functions”struct Rectangle { width: f64, height: f64,}
impl Rectangle { // Associated function (constructor) — no &self fn new(width: f64, height: f64) -> Self { Rectangle { width, height } }
fn square(size: f64) -> Self { Rectangle { width: size, height: size } }
// Method — takes &self fn area(&self) -> f64 { self.width * self.height }
fn perimeter(&self) -> f64 { 2.0 * (self.width + self.height) }
fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height }}
let rect = Rectangle::new(30.0, 50.0);println!("Area: {}", rect.area()); // 1500println!("Perimeter: {}", rect.perimeter()); // 160Deriving Common Traits
Section titled “Deriving Common Traits”#[derive(Debug, Clone, PartialEq)]struct Point { x: f64, y: f64,}
let p = Point { x: 1.0, y: 2.0 };println!("{p:?}"); // Debug outputlet p2 = p.clone(); // Cloneassert_eq!(p, p2); // PartialEqRFC 959, Section Using Structs to Structure Related Data — "doc.rust-lang.org/book"
Enums define a type that can be one of several variants. Each variant can optionally carry data.
enum IpAddr { V4(u8, u8, u8, u8), V6(String),}
let home = IpAddr::V4(127, 0, 0, 1);let loopback = IpAddr::V6(String::from("::1"));Enums with Different Data Types
Section titled “Enums with Different Data Types”enum Message { Quit, // no data Move { x: i32, y: i32 }, // named fields (like a struct) Write(String), // single value ChangeColor(i32, i32, i32), // tuple}Methods on Enums
Section titled “Methods on Enums”impl Message { fn call(&self) { match self { Message::Quit => println!("Quitting"), Message::Move { x, y } => println!("Moving to ({x}, {y})"), Message::Write(text) => println!("Writing: {text}"), Message::ChangeColor(r, g, b) => println!("Color: ({r}, {g}, {b})"), } }}Option
Section titled “Option”Option<T> is Rust’s replacement for null. A value is either present (Some(T)) or absent (None).
let some_number: Option<i32> = Some(5);let no_number: Option<i32> = None;
// You MUST handle the None case to use the inner valuematch some_number { Some(n) => println!("Got {n}"), None => println!("Got nothing"),}
// Convenience methodslet x: Option<i32> = Some(5);x.unwrap(); // 5 (panics on None)x.unwrap_or(0); // 5 (returns 0 if None)x.unwrap_or_default(); // 5 (returns type's default if None)x.is_some(); // truex.is_none(); // falsex.map(|v| v * 2); // Some(10)x.and_then(|v| if v > 3 { Some(v) } else { None }); // Some(5)RFC 959, Section Enums and Pattern Matching — "doc.rust-lang.org/book"
Error Handling
Section titled “Error Handling”Rust has two categories of errors: unrecoverable (panics) and recoverable (Result<T, E>).
panic! immediately terminates the current thread with an error message:
panic!("Something went terribly wrong");In practice, panics are reserved for programmer errors (bugs), not expected failures.
Result
Section titled “Result”Result<T, E> is the standard way to handle recoverable errors:
enum Result<T, E> { Ok(T), Err(E),}use std::fs::File;use std::io::Read;
fn read_file(path: &str) -> Result<String, std::io::Error> { let mut file = File::open(path)?; // ? propagates the error let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents)}The ? Operator
Section titled “The ? Operator”The ? operator is shorthand for propagating errors. If the Result is Ok, it unwraps the value. If it is Err, it returns the error from the enclosing function.
// These are equivalent:let file = File::open("hello.txt")?;
let file = match File::open("hello.txt") { Ok(f) => f, Err(e) => return Err(e),};? also works with Option<T> — it returns None on failure.
Result Methods
Section titled “Result Methods”let x: Result<i32, &str> = Ok(5);
x.unwrap(); // 5 (panics on Err)x.expect("custom message"); // 5 (panics with message on Err)x.unwrap_or(0); // 5 (returns 0 on Err)x.unwrap_or_else(|e| { println!("Error: {e}"); 0});x.is_ok(); // truex.is_err(); // falsex.map(|v| v * 2); // Ok(10)x.and_then(|v| if v > 3 { Ok(v) } else { Err("too small") });x.ok(); // Some(5) — converts to OptionCustom Error Types
Section titled “Custom Error Types”use std::fmt;
#[derive(Debug)]enum AppError { NotFound(String), PermissionDenied, ParseError(std::num::ParseIntError),}
impl fmt::Display for AppError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { AppError::NotFound(name) => write!(f, "Not found: {name}"), AppError::PermissionDenied => write!(f, "Permission denied"), AppError::ParseError(e) => write!(f, "Parse error: {e}"), } }}
impl From<std::num::ParseIntError> for AppError { fn from(e: std::num::ParseIntError) -> Self { AppError::ParseError(e) }}With From implemented, ? automatically converts the error type:
fn parse_and_double(s: &str) -> Result<i32, AppError> { let n: i32 = s.parse()?; // ParseIntError → AppError via From Ok(n * 2)}RFC 959, Section Error Handling — "doc.rust-lang.org/book"
Traits
Section titled “Traits”Traits define shared behavior — similar to interfaces in other languages. A trait declares a set of methods that types can implement.
Defining and Implementing Traits
Section titled “Defining and Implementing Traits”trait Summary { fn summarize(&self) -> String;
// Default implementation fn preview(&self) -> String { format!("{}...", &self.summarize()[..20]) }}
struct Article { title: String, author: String, content: String,}
impl Summary for Article { fn summarize(&self) -> String { format!("{}, by {}", self.title, self.author) } // preview() uses the default implementation}Traits as Parameters
Section titled “Traits as Parameters”// Shorthandfn notify(item: &impl Summary) { println!("Breaking: {}", item.summarize());}
// Trait bound syntax (equivalent)fn notify<T: Summary>(item: &T) { println!("Breaking: {}", item.summarize());}
// Multiple trait boundsfn notify(item: &(impl Summary + std::fmt::Display)) { println!("{item}: {}", item.summarize());}
// where clause (cleaner for complex bounds)fn process<T>(item: &T) -> Stringwhere T: Summary + Clone + std::fmt::Debug,{ item.summarize()}Returning Types That Implement Traits
Section titled “Returning Types That Implement Traits”fn create_summarizable() -> impl Summary { Article { title: String::from("Breaking News"), author: String::from("Reporter"), content: String::from("Something happened"), }}Common Standard Library Traits
Section titled “Common Standard Library Traits”| Trait | Purpose | Derivable |
|---|---|---|
Debug | Format with {:?} | Yes |
Clone | Explicit deep copy | Yes |
Copy | Implicit bitwise copy (stack only) | Yes |
PartialEq / Eq | Equality comparison | Yes |
PartialOrd / Ord | Ordering comparison | Yes |
Hash | Hashing (for HashMap keys) | Yes |
Default | Default value | Yes |
Display | User-facing formatting with {} | No |
From / Into | Type conversions | No |
Iterator | Iteration protocol | No |
Drop | Custom destructor logic | No |
Deref / DerefMut | Smart pointer dereferencing | No |
AsRef / AsMut | Cheap reference conversions | No |
Send | Safe to transfer between threads | Auto |
Sync | Safe to share references between threads | Auto |
Trait Objects (Dynamic Dispatch)
Section titled “Trait Objects (Dynamic Dispatch)”When you need a collection of different types that implement the same trait, use trait objects with dyn:
fn print_all(items: &[&dyn Summary]) { for item in items { println!("{}", item.summarize()); }}
// Or with Box for owned trait objectsfn get_items() -> Vec<Box<dyn Summary>> { vec![ Box::new(Article { /* ... */ }), Box::new(Tweet { /* ... */ }), ]}Trait objects use dynamic dispatch (vtable lookup at runtime) — there is a small performance cost compared to generics (static dispatch).
Supertraits
Section titled “Supertraits”A trait can require another trait as a prerequisite:
trait PrettyPrint: std::fmt::Display { fn pretty_print(&self) { println!("=== {} ===", self); // can use Display methods }}RFC 959, Section Traits — "doc.rust-lang.org/book"
Generics
Section titled “Generics”Generics allow writing code that works with any type, resolved at compile time with zero runtime cost (monomorphization).
Generic Functions
Section titled “Generic Functions”fn largest<T: PartialOrd>(list: &[T]) -> &T { let mut largest = &list[0]; for item in &list[1..] { if item > largest { largest = item; } } largest}
let numbers = vec![34, 50, 25, 100, 65];println!("Largest: {}", largest(&numbers));
let chars = vec!['y', 'm', 'a', 'q'];println!("Largest: {}", largest(&chars));Generic Structs
Section titled “Generic Structs”struct Point<T> { x: T, y: T,}
impl<T> Point<T> { fn new(x: T, y: T) -> Self { Point { x, y } }}
// Method only for f64 pointsimpl Point<f64> { fn distance_from_origin(&self) -> f64 { (self.x.powi(2) + self.y.powi(2)).sqrt() }}
let integer_point = Point::new(5, 10);let float_point = Point::new(1.0, 4.0);Multiple Generic Types
Section titled “Multiple Generic Types”struct Point<T, U> { x: T, y: U,}
let mixed = Point { x: 5, y: 4.0 };Generic Enums
Section titled “Generic Enums”// Option and Result are generic enums from the standard libraryenum Option<T> { Some(T), None,}
enum Result<T, E> { Ok(T), Err(E),}RFC 959, Section Generic Types, Traits, and Lifetimes — "doc.rust-lang.org/book"
Lifetimes
Section titled “Lifetimes”Lifetimes are Rust’s way of ensuring that references are always valid. Every reference has a lifetime — the scope for which it is valid. Most of the time, lifetimes are inferred. When they are ambiguous, you annotate them explicitly.
Lifetime Annotations
Section titled “Lifetime Annotations”Lifetime annotations don’t change how long references live — they describe the relationships between lifetimes of multiple references.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}This says: the returned reference will live at least as long as the shorter of the two input lifetimes.
Lifetime Elision Rules
Section titled “Lifetime Elision Rules”The compiler applies three rules to infer lifetimes so you don’t have to write them everywhere:
- Each reference parameter gets its own lifetime.
- If there is exactly one input lifetime, it is assigned to all output lifetimes.
- If one of the parameters is
&selfor&mut self, its lifetime is assigned to all output lifetimes.
// These are equivalent — the compiler infers the lifetime:fn first_word(s: &str) -> &str { /* ... */ }fn first_word<'a>(s: &'a str) -> &'a str { /* ... */ }Lifetimes in Structs
Section titled “Lifetimes in Structs”A struct that holds a reference must have a lifetime annotation:
struct Excerpt<'a> { part: &'a str,}
impl<'a> Excerpt<'a> { fn level(&self) -> i32 { 3 }
fn announce(&self, announcement: &str) -> &str { println!("Attention: {announcement}"); self.part }}The ‘static Lifetime
Section titled “The ‘static Lifetime”'static means the reference can live for the entire duration of the program. All string literals have a 'static lifetime.
let s: &'static str = "I live forever";RFC 959, Section Validating References with Lifetimes — "doc.rust-lang.org/book"
Collections
Section titled “Collections”HashMap
Section titled “HashMap”use std::collections::HashMap;
let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Red"), 50);
// Accesslet score = scores.get("Blue"); // Option<&i32>let score = scores["Blue"]; // panics if missing
// Insert only if key is absentscores.entry(String::from("Yellow")).or_insert(25);
// Update based on old valuelet text = "hello world wonderful world";let mut word_count = HashMap::new();for word in text.split_whitespace() { let count = word_count.entry(word).or_insert(0); *count += 1;}
// Iterationfor (key, value) in &scores { println!("{key}: {value}");}
// From iteratorslet teams = vec!["Blue", "Red"];let scores = vec![10, 50];let score_map: HashMap<_, _> = teams.into_iter().zip(scores.into_iter()).collect();HashSet
Section titled “HashSet”use std::collections::HashSet;
let mut books = HashSet::new();books.insert("The Hobbit");books.insert("Dune");books.insert("The Hobbit"); // duplicate — ignored
println!("{}", books.len()); // 2println!("{}", books.contains("Dune")); // true
// Set operationslet a: HashSet<i32> = [1, 2, 3].into();let b: HashSet<i32> = [2, 3, 4].into();
let union: HashSet<_> = a.union(&b).collect(); // {1, 2, 3, 4}let intersection: HashSet<_> = a.intersection(&b).collect(); // {2, 3}let difference: HashSet<_> = a.difference(&b).collect(); // {1}BTreeMap and BTreeSet
Section titled “BTreeMap and BTreeSet”Sorted alternatives to HashMap and HashSet. Keys are kept in sorted order.
use std::collections::BTreeMap;
let mut map = BTreeMap::new();map.insert(3, "c");map.insert(1, "a");map.insert(2, "b");
for (k, v) in &map { println!("{k}: {v}"); // prints in order: 1: a, 2: b, 3: c}VecDeque
Section titled “VecDeque”A double-ended queue built on a growable ring buffer:
use std::collections::VecDeque;
let mut deque = VecDeque::new();deque.push_back(1);deque.push_back(2);deque.push_front(0);// [0, 1, 2]
deque.pop_front(); // Some(0)deque.pop_back(); // Some(2)RFC 959, Section Common Collections — "doc.rust-lang.org/book"
Modules and Crates
Section titled “Modules and Crates”Module System
Section titled “Module System”Rust’s module system controls visibility and organization:
mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} }
mod serving { // private module fn take_order() {} }}
pub fn eat_at_restaurant() { // Absolute path crate::front_of_house::hosting::add_to_waitlist();
// Relative path front_of_house::hosting::add_to_waitlist();}Visibility Rules
Section titled “Visibility Rules”| Modifier | Visibility |
|---|---|
| (default) | Private to the current module and its children |
pub | Public — accessible from anywhere |
pub(crate) | Public within the current crate only |
pub(super) | Public to the parent module |
pub(in path) | Public within the specified path |
File-Based Modules
Section titled “File-Based Modules”Modules can live in separate files:
src/├── main.rs├── garden.rs # mod garden└── garden/ └── vegetables.rs # mod garden::vegetablesmod garden;
fn main() { garden::plant();}
// src/garden.rspub mod vegetables;
pub fn plant() { vegetables::harvest();}
// src/garden/vegetables.rspub fn harvest() { println!("Harvesting!");}use Keyword
Section titled “use Keyword”use std::collections::HashMap;use std::io::{self, Write}; // import io and io::Writeuse std::collections::*; // glob import (discouraged in production)
// Re-exportpub use crate::front_of_house::hosting;External Crates
Section titled “External Crates”Add dependencies to Cargo.toml, then use them:
[dependencies]rand = "0.8"use rand::Rng;
let n: u32 = rand::thread_rng().gen_range(1..=100);RFC 959, Section Managing Growing Projects with Packages, Crates, and Modules — "doc.rust-lang.org/book"
Smart Pointers
Section titled “Smart Pointers”Smart pointers are data structures that act like pointers but also have additional metadata and capabilities.
Box<T>
Section titled “Box<T>”Box<T> allocates data on the heap. Use it for recursive types, large data you don’t want to copy, or trait objects.
// Recursive type — impossible without indirectionenum List { Cons(i32, Box<List>), Nil,}
let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Cons(3, Box::new(List::Nil))))));
// Heap allocationlet b = Box::new(5);println!("{b}"); // automatically dereferencedRc<T> (Reference Counting)
Section titled “Rc<T> (Reference Counting)”Rc<T> enables multiple ownership through reference counting. Single-threaded only.
use std::rc::Rc;
let a = Rc::new(String::from("hello"));let b = Rc::clone(&a); // increment reference count (cheap)let c = Rc::clone(&a);
println!("count: {}", Rc::strong_count(&a)); // 3Arc<T> (Atomic Reference Counting)
Section titled “Arc<T> (Atomic Reference Counting)”Arc<T> is the thread-safe version of Rc<T>. Use it when sharing data across threads.
use std::sync::Arc;use std::thread;
let data = Arc::new(vec![1, 2, 3]);
let handles: Vec<_> = (0..3).map(|_| { let data = Arc::clone(&data); thread::spawn(move || { println!("{data:?}"); })}).collect();
for handle in handles { handle.join().unwrap();}RefCell<T> (Interior Mutability)
Section titled “RefCell<T> (Interior Mutability)”RefCell<T> enforces borrowing rules at runtime instead of compile time. Use it when you need to mutate data behind an immutable reference.
use std::cell::RefCell;
let data = RefCell::new(vec![1, 2, 3]);
data.borrow_mut().push(4); // mutable borrow at runtimeprintln!("{:?}", data.borrow()); // immutable borrow at runtime// Two mutable borrows at the same time → panic at runtimeCombining Rc and RefCell
Section titled “Combining Rc and RefCell”Rc<RefCell<T>> gives you multiple owners that can all mutate the inner value:
use std::cell::RefCell;use std::rc::Rc;
let shared = Rc::new(RefCell::new(0));
let a = Rc::clone(&shared);let b = Rc::clone(&shared);
*a.borrow_mut() += 10;*b.borrow_mut() += 20;
println!("{}", shared.borrow()); // 30Smart Pointer Summary
Section titled “Smart Pointer Summary”| Type | Ownership | Thread-safe | Mutability | Use case |
|---|---|---|---|---|
Box<T> | Single | Yes | Through owner | Heap allocation, recursive types |
Rc<T> | Multiple | No | Immutable | Shared ownership (single thread) |
Arc<T> | Multiple | Yes | Immutable | Shared ownership (multi-thread) |
RefCell<T> | Single | No | Interior mutability | Runtime borrow checking |
Mutex<T> | Multiple (with Arc) | Yes | Interior mutability | Thread-safe mutation |
Cell<T> | Single | No | Interior mutability | Copy types, no borrowing |
Cow<'a, T> | Clone-on-write | Yes | Clone when mutated | Avoid unnecessary cloning |
RFC 959, Section Smart Pointers — "doc.rust-lang.org/book"
Concurrency
Section titled “Concurrency”Rust’s ownership system prevents data races at compile time — you get fearless concurrency.
Spawning Threads
Section titled “Spawning Threads”use std::thread;use std::time::Duration;
let handle = thread::spawn(|| { for i in 1..10 { println!("spawned thread: {i}"); thread::sleep(Duration::from_millis(1)); }});
for i in 1..5 { println!("main thread: {i}"); thread::sleep(Duration::from_millis(1));}
handle.join().unwrap(); // wait for the spawned thread to finishMoving Data into Threads
Section titled “Moving Data into Threads”Use move closures to transfer ownership into the thread:
let v = vec![1, 2, 3];
let handle = thread::spawn(move || { println!("{v:?}"); // v is now owned by this thread});
handle.join().unwrap();Message Passing with Channels
Section titled “Message Passing with Channels”Channels provide a way to send messages between threads. Rust’s standard library provides a multiple producer, single consumer (mpsc) channel.
use std::sync::mpsc;use std::thread;
let (tx, rx) = mpsc::channel();
// Clone tx for multiple producerslet tx2 = tx.clone();
thread::spawn(move || { tx.send(String::from("hello from thread 1")).unwrap();});
thread::spawn(move || { tx2.send(String::from("hello from thread 2")).unwrap();});
// Receive — blocks until a message arrivesfor received in rx { println!("Got: {received}");}Shared State with Mutex
Section titled “Shared State with Mutex”Mutex<T> provides mutual exclusion — only one thread can access the data at a time.
use std::sync::{Arc, Mutex};use std::thread;
let counter = Arc::new(Mutex::new(0));let mut handles = vec![];
for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle);}
for handle in handles { handle.join().unwrap();}
println!("Result: {}", *counter.lock().unwrap()); // 10RwLock
Section titled “RwLock”RwLock<T> allows multiple readers or one writer — more efficient than Mutex when reads are frequent:
use std::sync::RwLock;
let lock = RwLock::new(5);
// Multiple readers allowed simultaneously{ let r1 = lock.read().unwrap(); let r2 = lock.read().unwrap(); println!("{} {}", *r1, *r2);}
// Single writer — exclusive access{ let mut w = lock.write().unwrap(); *w += 1;}Send and Sync
Section titled “Send and Sync”Two marker traits govern thread safety:
Send: a type can be transferred between threads. Almost all types areSend. Notable exception:Rc<T>.Sync: a type can be shared between threads via references. A typeTisSyncif&TisSend. Notable exception:RefCell<T>.
These traits are automatically implemented — you rarely need to implement them manually.
Concurrency Summary
Section titled “Concurrency Summary”| Primitive | Purpose |
|---|---|
thread::spawn | Create a new OS thread |
mpsc::channel | Message passing (multiple producers, single consumer) |
Mutex<T> | Mutual exclusion — one accessor at a time |
RwLock<T> | Multiple readers or single writer |
Arc<T> | Thread-safe reference counting |
Barrier | Synchronization point for multiple threads |
Condvar | Condition variable — wait for a notification |
atomic types | Lock-free thread-safe primitives |
RFC 959, Section Fearless Concurrency — "doc.rust-lang.org/book"
Async/Await
Section titled “Async/Await”Rust’s async/await provides zero-cost asynchronous programming. Async code compiles to state machines — no runtime garbage collector or OS thread per task.
Basics
Section titled “Basics”async fn fetch_data() -> String { // simulated async work String::from("data")}
async fn process() { let data = fetch_data().await; println!("{data}");}Async functions return a Future — they do nothing until .awaited or polled by a runtime.
Runtime
Section titled “Runtime”Rust has no built-in async runtime. The most popular is Tokio:
[dependencies]tokio = { version = "1", features = ["full"] }#[tokio::main]async fn main() { let result = fetch_data().await; println!("{result}");}Concurrent Tasks
Section titled “Concurrent Tasks”use tokio::task;
#[tokio::main]async fn main() { let handle1 = task::spawn(async { // task 1 42 });
let handle2 = task::spawn(async { // task 2 "hello" });
let (r1, r2) = tokio::join!(handle1, handle2); println!("{} {}", r1.unwrap(), r2.unwrap());}RFC 959, Section Async and Await — "doc.rust-lang.org/book"
Macros
Section titled “Macros”Macros generate code at compile time. Rust has two kinds: declarative macros (macro_rules!) and procedural macros.
Declarative Macros
Section titled “Declarative Macros”macro_rules! say_hello { () => { println!("Hello!"); }; ($name:expr) => { println!("Hello, {}!", $name); };}
say_hello!(); // "Hello!"say_hello!("World"); // "Hello, World!"A Practical Example: vec!
Section titled “A Practical Example: vec!”A simplified version of how vec! works:
macro_rules! my_vec { ( $( $x:expr ),* ) => { { let mut temp_vec = Vec::new(); $( temp_vec.push($x); )* temp_vec } };}
let v = my_vec![1, 2, 3];Fragment Types
Section titled “Fragment Types”| Fragment | Matches |
|---|---|
$x:expr | An expression |
$x:ident | An identifier |
$x:ty | A type |
$x:pat | A pattern |
$x:stmt | A statement |
$x:block | A block |
$x:item | An item (function, struct, etc.) |
$x:literal | A literal value |
$x:tt | A single token tree |
Derive Macros (Procedural)
Section titled “Derive Macros (Procedural)”The most common procedural macros are derive macros — they generate trait implementations:
#[derive(Debug, Clone, PartialEq, Eq, Hash)]struct User { name: String, age: u32,}Popular derive macros from the ecosystem:
| Crate | Derive | Purpose |
|---|---|---|
serde | Serialize, Deserialize | JSON/TOML/etc. serialization |
thiserror | Error | Custom error types with Display |
clap | Parser | Command-line argument parsing |
sqlx | FromRow | Database row mapping |
Attribute Macros
Section titled “Attribute Macros”// From the rocket web framework#[get("/hello/<name>")]fn hello(name: &str) -> String { format!("Hello, {name}!")}RFC 959, Section Macros — "doc.rust-lang.org/book"
Testing
Section titled “Testing”Rust has built-in testing support. No external framework needed.
Unit Tests
Section titled “Unit Tests”Unit tests live in the same file as the code they test, inside a #[cfg(test)] module:
pub fn add(left: u64, right: u64) -> u64 { left + right}
#[cfg(test)]mod tests { use super::*;
#[test] fn it_adds() { assert_eq!(add(2, 2), 4); }
#[test] fn it_works_with_zero() { assert_eq!(add(0, 5), 5); }
#[test] #[should_panic(expected = "overflow")] fn it_panics_on_overflow() { // test that expects a panic panic!("overflow"); }
#[test] fn it_returns_result() -> Result<(), String> { if add(2, 2) == 4 { Ok(()) } else { Err(String::from("two plus two is not four")) } }}Assertion Macros
Section titled “Assertion Macros”| Macro | Purpose |
|---|---|
assert!(expr) | Asserts expression is true |
assert_eq!(left, right) | Asserts equality (shows both values on failure) |
assert_ne!(left, right) | Asserts inequality |
debug_assert!(expr) | Only checked in debug builds |
All assertion macros accept an optional format string:
assert!(value > 0, "value should be positive, got {value}");Integration Tests
Section titled “Integration Tests”Integration tests live in the tests/ directory and test the public API:
use my_crate::add;
#[test]fn it_adds_from_outside() { assert_eq!(add(3, 4), 7);}Running Tests
Section titled “Running Tests”cargo test # run all testscargo test test_name # run tests matching a namecargo test -- --nocapture # show println! outputcargo test -- --test-threads=1 # run tests sequentiallycargo test --lib # unit tests onlycargo test --test integration_test # specific integration test fileRFC 959, Section Writing Automated Tests — "doc.rust-lang.org/book"
File Handling
Section titled “File Handling”use std::fs;use std::io::{self, Read, Write, BufRead, BufReader};
// Read entire file to stringlet contents = fs::read_to_string("hello.txt")?;
// Read file to byteslet bytes = fs::read("image.png")?;
// Write string to file (creates or overwrites)fs::write("output.txt", "Hello, World!")?;
// Append to fileuse std::fs::OpenOptions;let mut file = OpenOptions::new() .append(true) .open("log.txt")?;writeln!(file, "New log entry")?;
// Read line by line (buffered)let file = fs::File::open("large.txt")?;let reader = BufReader::new(file);for line in reader.lines() { println!("{}", line?);}
// Check if file existsif fs::metadata("file.txt").is_ok() { println!("File exists");}
// Create directoryfs::create_dir_all("path/to/dir")?;
// Remove file / directoryfs::remove_file("temp.txt")?;fs::remove_dir_all("temp_dir")?;
// List directory contentsfor entry in fs::read_dir(".")? { let entry = entry?; println!("{}", entry.path().display());}RFC 959, Section File I/O — "doc.rust-lang.org/std/fs"
Command Line Arguments
Section titled “Command Line Arguments”Using std::env
Section titled “Using std::env”use std::env;
fn main() { let args: Vec<String> = env::args().collect();
println!("Program: {}", args[0]); for arg in &args[1..] { println!("Arg: {arg}"); }}cargo run -- hello world# Arg: hello# Arg: worldUsing clap (Recommended for Real Apps)
Section titled “Using clap (Recommended for Real Apps)”[dependencies]clap = { version = "4", features = ["derive"] }use clap::Parser;
#[derive(Parser)]#[command(name = "myapp", about = "A sample CLI app")]struct Cli { /// Name of the person to greet name: String,
/// Number of times to greet #[arg(short, long, default_value_t = 1)] count: u8,}
fn main() { let cli = Cli::parse();
for _ in 0..cli.count { println!("Hello, {}!", cli.name); }}cargo run -- --count 3 Alice# Hello, Alice!# Hello, Alice!# Hello, Alice!RFC 959, Section Command Line Arguments — "doc.rust-lang.org/book"
Useful Attributes
Section titled “Useful Attributes”| Attribute | Purpose |
|---|---|
#[derive(...)] | Auto-implement traits |
#[allow(unused)] | Suppress unused warnings |
#[cfg(test)] | Compile only during testing |
#[cfg(target_os = "linux")] | Conditional compilation by OS |
#[inline] | Hint to inline a function |
#[must_use] | Warn if return value is ignored |
#[deprecated] | Mark item as deprecated |
#[non_exhaustive] | Prevent external exhaustive matching |
#![forbid(unsafe_code)] | Disallow unsafe in the entire crate |
RFC 959, Section Attributes — "doc.rust-lang.org/reference"
Source & Further Reading
Section titled “Source & Further Reading”The Rust Programming Language
A comprehensive reference combining the official Rust Book, the Rust Reference, and community resources.