Bookmark this page. Use Ctrl+F (or Cmd+F on Mac) to find what you need. This cheat sheet covers Rust syntax, ownership, traits, error handling, and common patterns. Try examples at play.rust-lang.org.
Last updated: March 2026
Variables and Types
let name = "Alex"; // immutable (default)
let mut count = 0; // mutable
let age: i32 = 25; // explicit type
const MAX: u32 = 100; // compile-time constant
| Type | Description | Example |
|---|---|---|
i8, i16, i32, i64, i128 | Signed integers | let x: i32 = -42; |
u8, u16, u32, u64, u128 | Unsigned integers | let x: u32 = 42; |
f32, f64 | Floating point | let x: f64 = 3.14; |
bool | Boolean | true, false |
char | Unicode character | 'A', '🚀' |
&str | String slice (borrowed) | "hello" |
String | Owned string (heap) | String::from("hello") |
() | Unit type (void) | fn do_thing() { } |
[T; N] | Fixed array | [1, 2, 3] |
Vec<T> | Dynamic array | vec![1, 2, 3] |
(T, U) | Tuple | (42, "hello") |
Option<T> | Nullable value | Some(42) or None |
Result<T, E> | Success or error | Ok(42) or Err("fail") |
Type Conversions
let x: i32 = 42;
let y: f64 = x as f64; // 42.0
let s: String = x.to_string(); // "42"
let n: i32 = "42".parse().unwrap(); // 42
let n: i32 = "42".parse().unwrap_or(0); // 42 (or 0 on error)
Strings
// &str — string slice, borrowed, immutable
let greeting: &str = "hello";
// String — owned, heap-allocated, growable
let mut name = String::from("Alex");
name.push_str(" Smith"); // append
name.push('!'); // append char
// Conversions
let s: String = "hello".to_string();
let s: &str = &name; // String → &str (auto-deref)
// Common methods
name.len() // byte length
name.is_empty() // true if empty
name.contains("Alex") // substring check
name.starts_with("A")
name.to_uppercase()
name.to_lowercase()
name.trim() // remove whitespace
name.replace("Alex", "Sam")
name.split(" ") // iterator of parts
// Format
let msg = format!("Hello {name}, age {age}");
Ownership and Borrowing
// Ownership — each value has one owner
let s1 = String::from("hello");
let s2 = s1; // s1 is MOVED to s2
// println!("{s1}"); // ERROR: s1 no longer valid
// Clone — deep copy
let s1 = String::from("hello");
let s2 = s1.clone(); // both valid
// Borrowing — reference without taking ownership
fn print_len(s: &String) { // immutable borrow
println!("{}", s.len());
}
fn add_excl(s: &mut String) { // mutable borrow
s.push('!');
}
// Rules:
// 1. Many immutable references (&T) OR one mutable reference (&mut T)
// 2. References must always be valid (no dangling)
Structs
struct User {
name: String,
age: u32,
active: bool,
}
let user = User {
name: String::from("Alex"),
age: 25,
active: true,
};
// Access
println!("{}", user.name);
// Update syntax
let user2 = User { age: 26, ..user };
// Tuple struct
struct Point(f64, f64);
let p = Point(1.0, 2.0);
// Methods
impl User {
// Constructor (convention)
fn new(name: &str, age: u32) -> Self {
Self { name: name.to_string(), age, active: true }
}
// Method (takes &self)
fn greet(&self) -> String {
format!("Hi, I'm {}", self.name)
}
}
Enums and Pattern Matching
enum Direction { North, South, East, West }
// Enums with data
enum Shape {
Circle(f64), // radius
Rectangle(f64, f64), // width, height
Triangle { base: f64, height: f64 },
}
// Pattern matching with match
match shape {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle { base, height } => 0.5 * base * height,
}
// if let — match a single pattern
if let Some(value) = optional {
println!("Got: {value}");
}
// let else — match or diverge
let Some(value) = optional else {
return;
};
Option and Result
// Option<T> — value or nothing
let name: Option<&str> = Some("Alex");
let empty: Option<&str> = None;
name.unwrap() // "Alex" (panics if None)
name.unwrap_or("Unknown") // "Alex" (or default)
name.is_some() // true
name.is_none() // false
name.map(|n| n.len()) // Some(4)
// Result<T, E> — success or error
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("division by zero".to_string())
} else {
Ok(a / b)
}
}
// ? operator — propagate errors
fn read_file(path: &str) -> Result<String, std::io::Error> {
let content = std::fs::read_to_string(path)?; // returns Err early
Ok(content)
}
// Handle Result
match divide(10.0, 3.0) {
Ok(result) => println!("{result}"),
Err(e) => println!("Error: {e}"),
}
Traits
// Define a trait
trait Greet {
fn greet(&self) -> String;
// Default implementation
fn hello(&self) -> String {
format!("Hello from {}", self.greet())
}
}
// Implement for a type
impl Greet for User {
fn greet(&self) -> String {
self.name.clone()
}
}
// Trait as parameter
fn print_greeting(item: &impl Greet) {
println!("{}", item.greet());
}
// Trait bound syntax
fn print_greeting<T: Greet>(item: &T) {
println!("{}", item.greet());
}
// Common derive traits
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Point { x: i32, y: i32 }
Collections
// Vec — dynamic array
let mut v = vec![1, 2, 3];
v.push(4);
v.pop(); // Some(4)
v.len(); // 3
v[0]; // 1 (panics if out of bounds)
v.get(0); // Some(&1) (safe)
v.contains(&2); // true
v.iter().filter(|&&x| x > 1).collect::<Vec<_>>();
// HashMap
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert("name", "Alex");
map.get("name"); // Some(&"Alex")
map.contains_key("name"); // true
map.entry("age").or_insert("25");
for (key, value) in &map { }
// HashSet
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(1);
set.contains(&1); // true
Iterators
let nums = vec![1, 2, 3, 4, 5];
nums.iter().map(|x| x * 2).collect::<Vec<_>>(); // [2, 4, 6, 8, 10]
nums.iter().filter(|&&x| x > 2).collect::<Vec<_>>(); // [3, 4, 5]
nums.iter().sum::<i32>(); // 15
nums.iter().any(|&x| x > 3); // true
nums.iter().all(|&x| x > 0); // true
nums.iter().find(|&&x| x > 3); // Some(&4)
nums.iter().position(|&x| x == 3); // Some(2)
nums.iter().enumerate(); // (index, &value)
nums.iter().zip(other.iter()); // pair elements
nums.iter().take(3).collect::<Vec<_>>(); // [1, 2, 3]
nums.iter().skip(2).collect::<Vec<_>>(); // [3, 4, 5]
nums.iter().flat_map(|x| vec![x, x * 10]);
nums.iter().fold(0, |acc, &x| acc + x); // 15
Control Flow
// if / else (is an expression)
let status = if age >= 18 { "adult" } else { "minor" };
// loop (infinite, break with value)
let result = loop {
if condition { break 42; }
};
// while
while count > 0 { count -= 1; }
// for
for i in 0..5 { } // 0, 1, 2, 3, 4
for i in 0..=5 { } // 0, 1, 2, 3, 4, 5
for item in &vec { } // iterate by reference
for item in vec { } // iterate by value (moves)
Closures
let add = |a: i32, b: i32| -> i32 { a + b };
let double = |x| x * 2; // types inferred
let greet = || println!("Hello");
// Closures capture variables
let name = String::from("Alex");
let greet = || println!("Hello {name}"); // borrows name
let greet = move || println!("Hello {name}"); // takes ownership
Cargo Commands
| Command | Description |
|---|---|
cargo new my_app | Create a new project |
cargo run | Build and run |
cargo build | Build (debug) |
cargo build --release | Build (optimized) |
cargo test | Run tests |
cargo check | Fast compile check (no binary) |
cargo clippy | Lint your code |
cargo fmt | Auto-format code |
cargo add serde | Add a dependency |
cargo doc --open | Generate and open docs |
Lifetimes
// Lifetimes tell the compiler how long references are valid
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() { a } else { b }
}
// Struct with a reference needs a lifetime
struct Excerpt<'a> {
text: &'a str,
}
Rule: if a function returns a reference, it must come from one of the inputs (annotated with the same lifetime).
Async / Await
// async functions return a Future
async fn fetch_data(url: &str) -> Result<String, reqwest::Error> {
let body = reqwest::get(url).await?.text().await?;
Ok(body)
}
// Use tokio runtime
#[tokio::main]
async fn main() {
let data = fetch_data("https://api.example.com").await.unwrap();
println!("{data}");
}
// Run tasks in parallel
let (a, b) = tokio::join!(fetch_a(), fetch_b());
Common Mistakes
Fighting the borrow checker — If you are cloning everything to make the compiler happy, step back. Usually there is a better way to structure ownership. Use references (
&T,&mut T) first, clone only when needed.unwrap()in production —unwrap()panics onNoneorErr. Use?,unwrap_or(),unwrap_or_default(), or propermatch/if letin production code.Returning references to local variables — A function cannot return
&Stringif theStringwas created inside the function. The reference would outlive the data. ReturnString(owned) instead.
Related Resources
- Rust Tutorial Series — learn Rust from scratch
- Kotlin Cheat Sheet — Kotlin syntax reference
- Rust Playground — try Rust in the browser
- All Cheat Sheets