Skip to content

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.

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:

Terminal window
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Verify:
rustc --version
cargo --version

Windows: Download and run rustup-init.exe or use:

Terminal window
winget install Rustlang.Rustup
ToolPurpose
rustcThe Rust compiler
cargoPackage manager and build system
rustupToolchain installer and version manager
rustfmtCode formatter (cargo fmt)
clippyLinter with helpful suggestions (cargo clippy)
rust-analyzerLanguage server for IDE support

Create a file called hello.rs:

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

Compile and run directly:

Terminal window
rustc hello.rs # compile
./hello # run
# Output: Hello, World!

In practice, you almost always use Cargo instead of calling rustc directly.

EditionYearKey Features
20152015Original stable release, ownership, traits, pattern matching
20182018async/await, module system simplification, NLL borrow checker
20212021Disjoint capture in closures, IntoIterator for arrays, new prelude
20242024gen 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"

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 ()).
  • let declares variables. mut makes 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 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"

Cargo is Rust’s build system and package manager. It handles compiling code, downloading dependencies (called crates), and running tests.

Terminal window
cargo new my_project # binary project (has main.rs)
cargo new my_lib --lib # library project (has lib.rs)
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)
[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"
CommandPurpose
cargo buildCompile the project
cargo build --releaseCompile with optimizations
cargo runBuild and run
cargo testRun all tests
cargo checkType-check without producing a binary (faster)
cargo fmtFormat code with rustfmt
cargo clippyRun the linter
cargo doc --openGenerate and open documentation
cargo add serdeAdd a dependency to Cargo.toml
cargo updateUpdate dependencies to latest compatible versions

RFC 959, Section Hello, Cargo! — "doc.rust-lang.org/book"

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; // mutable
y = 6; // OK

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 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 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 unsafe
unsafe {
COUNTER += 1;
}

RFC 959, Section Variables and Mutability — "doc.rust-lang.org/book"

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.

NUMERIC Integers & floats i32 4 bytes 10, -5, 0 f64 8 bytes 3.14, -0.5 u8 1 byte 0..255 isize Pointer-sized indexing TEXT & LOGIC Characters, booleans & strings bool 1 byte true, false char 4 bytes (Unicode) 'A', '🦀' &str Borrowed slice "hello" String Owned, growable String::from()
TypeSizeRange
i81 byte-128 to 127
i162 bytes-32,768 to 32,767
i324 bytes-2,147,483,648 to 2,147,483,647
i648 bytes-9.2 × 10¹⁸ to 9.2 × 10¹⁸
i12816 bytes-1.7 × 10³⁸ to 1.7 × 10³⁸
u81 byte0 to 255
u162 bytes0 to 65,535
u324 bytes0 to 4,294,967,295
u648 bytes0 to 1.8 × 10¹⁹
u12816 bytes0 to 3.4 × 10³⁸
isizePointer-sizedArchitecture-dependent (32 or 64 bit)
usizePointer-sizedArchitecture-dependent — used for indexing

i32 is the default integer type. Use usize for collection indices and lengths.

let decimal = 98_222; // underscores for readability
let hex = 0xff;
let octal = 0o77;
let binary = 0b1111_0000;
let byte = b'A'; // u8 only
let typed: u64 = 42u64; // type suffix
TypeSizePrecision
f324 bytes~6-7 decimal digits
f648 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)
let t = true;
let f: bool = false;

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 group values of different types into a single compound type. They have a fixed length.

let tup: (i32, f64, u8) = (500, 6.4, 1);
// Destructuring
let (x, y, z) = tup;
// Index access
let 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 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 type
let 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 Kilometers = i32;
type Result<T> = std::result::Result<T, std::io::Error>;

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 range

For 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 u8

RFC 959, Section Data Types — "doc.rust-lang.org/book"

println!("Hello, World!"); // with newline
print!("No newline"); // without newline
eprintln!("Error message"); // to stderr
// Formatting
println!("x = {}, y = {}", 10, 20); // positional
println!("x = {x}, y = {y}", x = 10, y = 20); // named
println!("{:?}", vec![1, 2, 3]); // debug format
println!("{:#?}", vec![1, 2, 3]); // pretty debug format
println!("{:>10}", "right"); // right-aligned, width 10
println!("{:<10}", "left"); // left-aligned
println!("{: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"
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"

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");
}

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 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 (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 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.

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.

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
}
}
}
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})"),
}
let num = Some(4);
match num {
Some(x) if x < 5 => println!("less than five: {x}"),
Some(x) => println!("{x}"),
None => println!("nothing"),
}

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
}
};

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;
}

Loop while a pattern matches:

let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{top}");
}
// Prints: 3, 2, 1

The for loop iterates over anything that implements IntoIterator:

let a = [10, 20, 30, 40, 50];
for element in a {
println!("{element}");
}
// With index
for (i, val) in a.iter().enumerate() {
println!("{i}: {val}");
}
// Range
for 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 is Rust’s core mechanism for memory safety without garbage collection. It is enforced entirely at compile time with zero runtime cost.

  1. Each value in Rust has exactly one owner.
  2. There can only be one owner at a time.
  3. 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 freed

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 valid
println!("{s2}"); // OK

Types 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 valid

For heap-allocated types, use .clone() to explicitly create a deep copy:

let s1 = String::from("hello");
let s2 = s1.clone();
println!("{s1} {s2}"); // both valid

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 s1
println!("{s1} has length {len}"); // s1 is still valid

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 borrow
let 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 ended
println!("{r3}");

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"

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.

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 0
let world = &s[6..]; // same — go to end
let whole = &s[..]; // entire string

The type of a string slice is &str. String literals ("hello") are also &str — they are slices pointing into the binary.

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"

Rust has two main string types:

TypeOwnershipMutabilityStorage
StringOwnedGrowableHeap-allocated
&strBorrowedImmutable viewStack pointer + length
let s1 = String::new(); // empty
let s2 = String::from("hello"); // from literal
let s3 = "hello".to_string(); // from &str
let s4 = format!("{}-{}", "hello", "world"); // format macro
let mut s = String::from("hello");
s.push(' '); // push a char
s.push_str("world"); // push a string slice
println!("{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 length
println!("{}", s3.is_empty()); // false
println!("{}", s3.contains("world")); // true
println!("{}", s3.to_uppercase()); // "HELLO, WORLD!"
// Iterating
for c in "hello".chars() {
println!("{c}");
}
for b in "hello".bytes() {
println!("{b}");
}

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 boundary
let 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"

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 annotation
let v = vec![1, 2, 3]; // macro shorthand
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
let v = vec![1, 2, 3, 4, 5];
let third = &v[2]; // panics if out of bounds
let third = v.get(2); // returns Option<&i32>
match v.get(10) {
Some(val) => println!("{val}"),
None => println!("No element at index 10"),
}
let mut v = vec![1, 2, 3];
v.push(4); // append
v.pop(); // remove last → Some(4)
v.insert(0, 0); // insert at index
v.remove(0); // remove at index
v.len(); // length
v.is_empty(); // check empty
v.contains(&2); // search
v.sort(); // sort in place
v.dedup(); // remove consecutive duplicates
v.retain(|&x| x > 1); // keep elements matching predicate
v.extend([4, 5, 6]); // append from iterator
let v = vec![100, 32, 57];
for val in &v {
println!("{val}");
}
// Mutable iteration
let 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"

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 ()
}

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 == 4

Adding a semicolon turns an expression into a statement (returns ()).

All parameters require type annotations:

fn greet(name: &str, times: u32) {
for _ in 0..times {
println!("Hello, {name}!");
}
}

Use tuples to return multiple values:

fn swap(a: i32, b: i32) -> (i32, i32) {
(b, a)
}
let (x, y) = swap(1, 2);

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
}

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 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 environment
println!("{}", add_x(5)); // 15
let add = |a: i32, b: i32| -> i32 { a + b };

Closures capture variables in the least restrictive way possible:

ModeSyntaxBehavior
Borrow (&T)automaticReads the captured variable
Mutable borrow (&mut T)automaticModifies the captured variable
Move (T)move ||Takes ownership of the captured variable
let mut list = vec![1, 2, 3];
// Immutable borrow
let print_list = || println!("{list:?}");
print_list();
// Mutable borrow
let mut push_to = || list.push(4);
push_to();
// Move — transfers ownership into the closure
let list = vec![1, 2, 3];
let owns_list = move || println!("{list:?}");
// println!("{list:?}"); // ERROR: list was moved
owns_list();
TraitDescriptionCan be called
FnBorrows captured values immutablyMultiple times
FnMutBorrows captured values mutablyMultiple times
FnOnceTakes ownership of captured valuesOnce

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)); // 10

RFC 959, Section Closures — "doc.rust-lang.org/book"

Iterators are lazy — they produce values one at a time and do nothing until consumed.

let v = vec![1, 2, 3];
let iter = v.iter(); // yields &T
let iter = v.iter_mut(); // yields &mut T
let iter = v.into_iter(); // yields T (consumes the vec)

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]
MethodDescription
.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)
MethodDescription
.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(); // 5050
let 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]
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(); // 15

RFC 959, Section Iterators — "doc.rust-lang.org/book"

Structs are custom data types that group related values together.

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,
};

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,
}
}

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)
};

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 types

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
}
}
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()); // 1500
println!("Perimeter: {}", rect.perimeter()); // 160
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: f64,
y: f64,
}
let p = Point { x: 1.0, y: 2.0 };
println!("{p:?}"); // Debug output
let p2 = p.clone(); // Clone
assert_eq!(p, p2); // PartialEq

RFC 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"));
enum Message {
Quit, // no data
Move { x: i32, y: i32 }, // named fields (like a struct)
Write(String), // single value
ChangeColor(i32, i32, i32), // tuple
}
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<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 value
match some_number {
Some(n) => println!("Got {n}"),
None => println!("Got nothing"),
}
// Convenience methods
let 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(); // true
x.is_none(); // false
x.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"

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<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 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.

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(); // true
x.is_err(); // false
x.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 Option
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 define shared behavior — similar to interfaces in other languages. A trait declares a set of methods that types can implement.

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
}
// Shorthand
fn notify(item: &impl Summary) {
println!("Breaking: {}", item.summarize());
}
// Trait bound syntax (equivalent)
fn notify<T: Summary>(item: &T) {
println!("Breaking: {}", item.summarize());
}
// Multiple trait bounds
fn notify(item: &(impl Summary + std::fmt::Display)) {
println!("{item}: {}", item.summarize());
}
// where clause (cleaner for complex bounds)
fn process<T>(item: &T) -> String
where
T: Summary + Clone + std::fmt::Debug,
{
item.summarize()
}
fn create_summarizable() -> impl Summary {
Article {
title: String::from("Breaking News"),
author: String::from("Reporter"),
content: String::from("Something happened"),
}
}
TraitPurposeDerivable
DebugFormat with {:?}Yes
CloneExplicit deep copyYes
CopyImplicit bitwise copy (stack only)Yes
PartialEq / EqEquality comparisonYes
PartialOrd / OrdOrdering comparisonYes
HashHashing (for HashMap keys)Yes
DefaultDefault valueYes
DisplayUser-facing formatting with {}No
From / IntoType conversionsNo
IteratorIteration protocolNo
DropCustom destructor logicNo
Deref / DerefMutSmart pointer dereferencingNo
AsRef / AsMutCheap reference conversionsNo
SendSafe to transfer between threadsAuto
SyncSafe to share references between threadsAuto

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 objects
fn 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).

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 allow writing code that works with any type, resolved at compile time with zero runtime cost (monomorphization).

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));
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 points
impl 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);
struct Point<T, U> {
x: T,
y: U,
}
let mixed = Point { x: 5, y: 4.0 };
// Option and Result are generic enums from the standard library
enum 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 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 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.

The compiler applies three rules to infer lifetimes so you don’t have to write them everywhere:

  1. Each reference parameter gets its own lifetime.
  2. If there is exactly one input lifetime, it is assigned to all output lifetimes.
  3. If one of the parameters is &self or &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 { /* ... */ }

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
}
}

'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"

use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Red"), 50);
// Access
let score = scores.get("Blue"); // Option<&i32>
let score = scores["Blue"]; // panics if missing
// Insert only if key is absent
scores.entry(String::from("Yellow")).or_insert(25);
// Update based on old value
let 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;
}
// Iteration
for (key, value) in &scores {
println!("{key}: {value}");
}
// From iterators
let teams = vec!["Blue", "Red"];
let scores = vec![10, 50];
let score_map: HashMap<_, _> = teams.into_iter().zip(scores.into_iter()).collect();
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()); // 2
println!("{}", books.contains("Dune")); // true
// Set operations
let 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}

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
}

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"

Rust’s module system controls visibility and organization:

src/lib.rs
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();
}
ModifierVisibility
(default)Private to the current module and its children
pubPublic — 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

Modules can live in separate files:

src/
├── main.rs
├── garden.rs # mod garden
└── garden/
└── vegetables.rs # mod garden::vegetables
src/main.rs
mod garden;
fn main() {
garden::plant();
}
// src/garden.rs
pub mod vegetables;
pub fn plant() {
vegetables::harvest();
}
// src/garden/vegetables.rs
pub fn harvest() {
println!("Harvesting!");
}
use std::collections::HashMap;
use std::io::{self, Write}; // import io and io::Write
use std::collections::*; // glob import (discouraged in production)
// Re-export
pub use crate::front_of_house::hosting;

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 are data structures that act like pointers but also have additional metadata and capabilities.

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 indirection
enum 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 allocation
let b = Box::new(5);
println!("{b}"); // automatically dereferenced

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)); // 3

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> 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 runtime
println!("{:?}", data.borrow()); // immutable borrow at runtime
// Two mutable borrows at the same time → panic at runtime

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()); // 30
TypeOwnershipThread-safeMutabilityUse case
Box<T>SingleYesThrough ownerHeap allocation, recursive types
Rc<T>MultipleNoImmutableShared ownership (single thread)
Arc<T>MultipleYesImmutableShared ownership (multi-thread)
RefCell<T>SingleNoInterior mutabilityRuntime borrow checking
Mutex<T>Multiple (with Arc)YesInterior mutabilityThread-safe mutation
Cell<T>SingleNoInterior mutabilityCopy types, no borrowing
Cow<'a, T>Clone-on-writeYesClone when mutatedAvoid unnecessary cloning

RFC 959, Section Smart Pointers — "doc.rust-lang.org/book"

Rust’s ownership system prevents data races at compile time — you get fearless concurrency.

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 finish

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();

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 producers
let 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 arrives
for received in rx {
println!("Got: {received}");
}

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()); // 10

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;
}

Two marker traits govern thread safety:

  • Send: a type can be transferred between threads. Almost all types are Send. Notable exception: Rc<T>.
  • Sync: a type can be shared between threads via references. A type T is Sync if &T is Send. Notable exception: RefCell<T>.

These traits are automatically implemented — you rarely need to implement them manually.

PrimitivePurpose
thread::spawnCreate a new OS thread
mpsc::channelMessage 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
BarrierSynchronization point for multiple threads
CondvarCondition variable — wait for a notification
atomic typesLock-free thread-safe primitives

RFC 959, Section Fearless Concurrency — "doc.rust-lang.org/book"

Rust’s async/await provides zero-cost asynchronous programming. Async code compiles to state machines — no runtime garbage collector or OS thread per task.

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.

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}");
}
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 generate code at compile time. Rust has two kinds: declarative macros (macro_rules!) and procedural macros.

macro_rules! say_hello {
() => {
println!("Hello!");
};
($name:expr) => {
println!("Hello, {}!", $name);
};
}
say_hello!(); // "Hello!"
say_hello!("World"); // "Hello, World!"

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];
FragmentMatches
$x:exprAn expression
$x:identAn identifier
$x:tyA type
$x:patA pattern
$x:stmtA statement
$x:blockA block
$x:itemAn item (function, struct, etc.)
$x:literalA literal value
$x:ttA single token tree

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:

CrateDerivePurpose
serdeSerialize, DeserializeJSON/TOML/etc. serialization
thiserrorErrorCustom error types with Display
clapParserCommand-line argument parsing
sqlxFromRowDatabase row mapping
// 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"

Rust has built-in testing support. No external framework needed.

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"))
}
}
}
MacroPurpose
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 live in the tests/ directory and test the public API:

tests/integration_test.rs
use my_crate::add;
#[test]
fn it_adds_from_outside() {
assert_eq!(add(3, 4), 7);
}
Terminal window
cargo test # run all tests
cargo test test_name # run tests matching a name
cargo test -- --nocapture # show println! output
cargo test -- --test-threads=1 # run tests sequentially
cargo test --lib # unit tests only
cargo test --test integration_test # specific integration test file

RFC 959, Section Writing Automated Tests — "doc.rust-lang.org/book"

use std::fs;
use std::io::{self, Read, Write, BufRead, BufReader};
// Read entire file to string
let contents = fs::read_to_string("hello.txt")?;
// Read file to bytes
let bytes = fs::read("image.png")?;
// Write string to file (creates or overwrites)
fs::write("output.txt", "Hello, World!")?;
// Append to file
use 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 exists
if fs::metadata("file.txt").is_ok() {
println!("File exists");
}
// Create directory
fs::create_dir_all("path/to/dir")?;
// Remove file / directory
fs::remove_file("temp.txt")?;
fs::remove_dir_all("temp_dir")?;
// List directory contents
for entry in fs::read_dir(".")? {
let entry = entry?;
println!("{}", entry.path().display());
}

RFC 959, Section File I/O — "doc.rust-lang.org/std/fs"

use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("Program: {}", args[0]);
for arg in &args[1..] {
println!("Arg: {arg}");
}
}
target/debug/my_app
cargo run -- hello world
# Arg: hello
# Arg: world
[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);
}
}
Terminal window
cargo run -- --count 3 Alice
# Hello, Alice!
# Hello, Alice!
# Hello, Alice!

RFC 959, Section Command Line Arguments — "doc.rust-lang.org/book"

AttributePurpose
#[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"

The Rust Programming Language

A comprehensive reference combining the official Rust Book, the Rust Reference, and community resources.