Rust Cheat Sheet
A quick reference for common Rust syntax and patterns. Each section shows compact, correct examples you can copy and adapt. For deeper explanations follow the links in the sidebar.
1. Variables
// Immutable binding (default) let x = 5; // Mutable binding let mut y = 5; y += 1; // Shadowing — rebind with the same name let x = x + 1; // x is now 6 let x = x * 2; // x is now 12 // Compile-time constant (type required, SCREAMING_SNAKE_CASE) const MAX_POINTS: u32 = 100_000; // Static — lives for the entire program static LOG_LEVEL: &str = "info";
2. Data Types
Category | Types | Notes |
|---|---|---|
Signed integers | i8, i16, i32, i64, i128, isize | Default integer is i32 |
Unsigned integers | u8, u16, u32, u64, u128, usize | usize matches pointer width |
Floating point | f32, f64 | Default float is f64 |
Boolean | bool | true or false |
Character | char | Unicode scalar value, 4 bytes, single quotes |
Tuple | (i32, f64, bool) | Fixed length, mixed types, access with .0 .1 … |
Array | [i32; 5] | Fixed length, same type, stack-allocated |
let t: (i32, f64, bool) = (500, 6.4, true);
let (a, b, c) = t; // destructure
println!("{}", t.1); // 6.4
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let first = arr[0];
let slice = &arr[1..3]; // &[2, 3]3. Strings
String | &str | |
|---|---|---|
Owned? | Yes | No (borrowed view) |
Mutable? | Yes (if mut) | No |
Stored on | Heap | Stack / data segment |
Common use | Build / own text | Read-only string data |
// Creating a String
let s1 = String::from("hello");
let s2 = "world".to_string();
let s3 = format!("{} {}", s1, s2); // "hello world"
// String slices
let hello: &str = &s1[0..5];
// Key methods
let len = s1.len();
let empty = s1.is_empty();
let upper = s1.to_uppercase();
let contains = s1.contains("ell");
let replaced = s1.replace("hello", "hi");
let trimmed = " hi ".trim();
// Appending
let mut s = String::from("foo");
s.push(' '); // single char
s.push_str("bar"); // string slice4. Control Flow
// if/else as an expression
let number = 7;
let description = if number % 2 == 0 { "even" } else { "odd" };
// loop with a break value
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 { break counter * 2; }
}; // result == 20
// while
let mut n = 3;
while n != 0 { println!("{}", n); n -= 1; }
// for over a range
for i in 1..=5 { print!("{} ", i); } // 1 2 3 4 5
// for over a collection
let v = vec![10, 20, 30];
for val in &v { println!("{}", val); }
// enumerate
for (i, val) in v.iter().enumerate() {
println!("{}: {}", i, val);
}5. Functions
// Named function — last expression is the implicit return value
fn add(x: i32, y: i32) -> i32 {
x + y // no semicolon = returned
}
// Explicit return (for early exit)
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { return None; }
Some(a / b)
}
// Closures
let double = |x: i32| x * 2;
let greet = |name: &str| format!("Hello, {}!", name);
let add_n = |n: i32| move |x: i32| x + n; // closure returning closure
fn apply(f: impl Fn(i32) -> i32, x: i32) -> i32 { f(x) }
println!("{}", apply(double, 5)); // 106. Ownership Rules
Each value in Rust has a single owner.
There can only be one owner at a time.
When the owner goes out of scope, the value is dropped (memory freed).
let s1 = String::from("hello");
let s2 = s1; // s1 is MOVED — s1 is no longer valid
// println!("{}", s1) // compile error
let s3 = s2.clone(); // deep copy — both s2 and s3 are valid
println!("{} {}", s2, s3);7. References & Borrowing
At any given time you can have either one mutable reference or any number of immutable references — but not both.
References must always be valid (no dangling references).
fn length(s: &String) -> usize { // immutable borrow
s.len()
}
fn append(s: &mut String) { // mutable borrow
s.push_str(", world");
}
fn main() {
let s = String::from("hello");
println!("{}", length(&s)); // borrow — s still owned here
let mut t = String::from("hello");
append(&mut t);
println!("{}", t); // "hello, world"
}8. Structs
// Named struct
struct Point { x: f64, y: f64 }
// Tuple struct
struct Color(u8, u8, u8);
// Unit struct (no fields)
struct Marker;
// impl block — methods take &self / &mut self / self
impl Point {
// Associated function (no self) — called with Point::new(…)
fn new(x: f64, y: f64) -> Self { Point { x, y } }
// Method — called with p.distance_from_origin()
fn distance_from_origin(&self) -> f64 {
(self.x * self.x + self.y * self.y).sqrt()
}
}
fn main() {
let p = Point::new(3.0, 4.0);
println!("{}", p.distance_from_origin()); // 5
let red = Color(255, 0, 0);
println!("r={}", red.0);
}9. Enums
// Basic enum
enum Direction { North, South, East, West }
// Enum with data
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
// Option<T> (in std — no import needed)
let some_number: Option<i32> = Some(5);
let no_number: Option<i32> = None;
// Result<T, E>
fn parse(s: &str) -> Result<i32, std::num::ParseIntError> {
s.trim().parse()
}10. Pattern Matching
let x = 5;
// match with literals, ranges, binding
match x {
1 => println!("one"),
2 | 3 => println!("two or three"),
4..=6 => println!("four through six"),
n => println!("other: {}", n),
}
// Destructuring a struct
let Point { x, y } = Point::new(1.0, 2.0);
// if let — match one pattern, ignore rest
if let Some(v) = some_number { println!("got {}", v); }
// let else — destructure or return/continue/break
let Some(v) = some_number else { return; };
// Match guard
let num = Some(4);
match num {
Some(n) if n < 5 => println!("less than five: {}", n),
Some(n) => println!("{}", n),
None => (),
}11. Traits
// Trait definition
trait Summary {
fn summarize(&self) -> String;
fn preview(&self) -> String { // default implementation
format!("{}...", &self.summarize()[..20])
}
}
// Implementing a trait
struct Article { title: String, body: String }
impl Summary for Article {
fn summarize(&self) -> String {
format!("{}: {}", self.title, self.body)
}
}
// impl Trait syntax in parameters
fn notify(item: &impl Summary) {
println!("{}", item.summarize());
}
// Derive macros (auto-implement traits)
#[derive(Debug, Clone, PartialEq)]
struct Config { debug: bool, level: u8 }12. Generics
use std::fmt::Display;
// Generic function
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut l = &list[0];
for item in list { if item > l { l = item; } }
l
}
// Generic struct
struct Wrapper<T> { value: T }
// where clause for complex bounds
fn print_pair<T, U>(a: T, b: U)
where
T: Display + Clone,
U: Display,
{
println!("{} {}", a, b);
}13. Error Handling
use std::fs;
use std::io;
// ? operator — propagates errors automatically
fn read_file(path: &str) -> Result<String, io::Error> {
let contents = fs::read_to_string(path)?;
Ok(contents.trim().to_string())
}
// Combinators
let n: i32 = "42".parse().unwrap_or(0);
let n2 = "x".parse::<i32>().unwrap_or_else(|_| -1);
let mapped = "5".parse::<i32>().map(|n| n * 2); // Ok(10)
// Boxed error (accept any error type)
fn flexible() -> Result<(), Box<dyn std::error::Error>> {
let _n: i32 = "42".parse()?;
Ok(())
}
// thiserror — derive Error on custom types (add to Cargo.toml first)
// use thiserror::Error;
// #[derive(Debug, Error)]
// enum AppError {
// #[error("not found: {0}")]
// NotFound(String),
// #[error(transparent)]
// Io(#[from] io::Error),
// }14. Iterators
let v = vec![1, 2, 3, 4, 5, 6];
// Lazy adaptor chain — no work until collect()
let result: Vec<i32> = v.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.collect(); // [4, 16, 36]
// Common consuming adaptors
let sum: i32 = v.iter().sum(); // 21
let prod: i32 = v.iter().product(); // 720
let count: usize = v.iter().filter(|&&x| x > 3).count(); // 3
let any = v.iter().any(|&x| x > 5); // true
let all = v.iter().all(|&x| x > 0); // true
let max = v.iter().max(); // Some(6)
let fold = v.iter().fold(0, |acc, &x| acc + x); // 21
// zip, enumerate, flat_map, chain, take, skip
let pairs: Vec<_> = v.iter().zip(v.iter().rev()).collect();15. Smart Pointers
Type | Purpose | Thread-safe? |
|---|---|---|
Box<T> | Heap allocation; own exactly one value | Yes (if T: Send) |
Rc<T> | Reference-counted shared ownership | No — single thread only |
Arc<T> | Atomic ref-counted shared ownership | Yes |
RefCell<T> | Interior mutability (borrow checked at runtime) | No |
Mutex<T> | Mutual exclusion; safe shared mutation across threads | Yes |
RwLock<T> | Multiple readers or one writer | Yes |
use std::rc::Rc;
use std::sync::{Arc, Mutex};
// Box — heap allocate a value
let b: Box<i32> = Box::new(5);
// Rc — shared ownership, single thread
let a = Rc::new(String::from("shared"));
let b = Rc::clone(&a);
println!("refs: {}", Rc::strong_count(&a)); // 2
// Arc + Mutex — shared mutation across threads
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
let data2 = Arc::clone(&data);
std::thread::spawn(move || {
data2.lock().unwrap().push(4);
}).join().unwrap();
println!("{:?}", data.lock().unwrap()); // [1, 2, 3, 4]16. Concurrency
use std::thread;
use std::sync::mpsc;
use std::sync::{Arc, Mutex};
// Spawn a thread and join it
let handle = thread::spawn(|| {
println!("from thread");
});
handle.join().unwrap();
// mpsc channel (multiple producer, single consumer)
let (tx, rx) = mpsc::channel();
let tx2 = tx.clone();
thread::spawn(move || tx.send("hello").unwrap());
thread::spawn(move || tx2.send("world").unwrap());
for msg in rx { println!("{}", msg); }
// Arc<Mutex<T>> shared counter
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..5 {
let c = Arc::clone(&counter);
handles.push(thread::spawn(move || { *c.lock().unwrap() += 1; }));
}
for h in handles { h.join().unwrap(); }
println!("counter: {}", *counter.lock().unwrap()); // 517. Async / Await
// Requires tokio in Cargo.toml:
// tokio = { version = "1", features = ["full"] }
use tokio::time::{sleep, Duration};
async fn fetch(id: u32) -> String {
sleep(Duration::from_millis(10)).await;
format!("data for {}", id)
}
#[tokio::main]
async fn main() {
// Sequential await
let result = fetch(1).await;
println!("{}", result);
// Concurrent with tokio::join!
let (a, b) = tokio::join!(fetch(2), fetch(3));
println!("{} {}", a, b);
// Spawn an independent task
let handle = tokio::spawn(async { fetch(4).await });
println!("{}", handle.await.unwrap());
}18. Common Cargo Commands
cargo new my_project # create new binary crate cargo new --lib my_lib # create new library crate cargo build # compile (debug) cargo build --release # compile (optimised) cargo run # build + run cargo run -- arg1 arg2 # pass arguments cargo test # run all tests cargo test my_test # run tests matching name cargo check # fast type-check without linking cargo clippy # linter cargo fmt # format code cargo doc --open # build + open docs in browser cargo add serde # add a dependency cargo add serde --features derive cargo update # update Cargo.lock cargo tree # print dependency tree cargo publish # publish crate to crates.io