Emergency server help: get in touch

Rust Cheat Sheet: Cargo, Ownership, Enums and Errors

A compact reference for current stable Rust covering Cargo, ownership, pattern matching, collections, error handling, traits and threads.

Published 7 min read

Short answer: Create a project with cargo new app, run it with cargo run and ship cargo build --release. Variables are immutable unless marked mut; each value has one owner, and you lend it with & (shared) or &mut (exclusive). Model states with enum and match, return Result<T, E> from anything that can fail and propagate errors with ?. Use Vec, HashMap and iterator chains such as .iter().map().filter().collect().

The examples in this Rust cheat sheet were run with Rust 1.99.0 and Cargo 1.99.0 (edition 2024) on Linux on 9 October 2026, and checked with cargo clippy.

rustup and cargo commands

CommandWhat it does
rustup update stableInstall or update the stable toolchain
cargo new app / cargo new --lib mylibNew binary or library project (edition 2024 by default)
cargo run -- --verboseBuild and run; arguments after -- go to your program
cargo build --releaseOptimised binary in target/release/
cargo checkType-check without producing a binary (fast)
cargo testRun unit, integration and doc tests
cargo clippyLints for common mistakes
cargo fmtFormat the code with rustfmt
cargo add serde --features deriveAdd a dependency to Cargo.toml
cargo updateUpdate Cargo.lock within allowed versions
cargo doc --openBuild and open documentation for your crate and dependencies
rustup target add x86_64-unknown-linux-muslAdd a target for static Linux binaries (cargo build --target ...)

Variables, types and control flow

SyntaxWhat it doesExample
let x = 5;Immutable bindinglet name = "web01";
let mut y = 10;Mutable bindingy += 1;
let x = x * 2;Shadowing: a new variable with the same name
i32, u16, u64, usize, f64, bool, charScalar typeslet port: u16 = 443;
(i32, &str) / [u8; 4]Tuple / fixed-size arrayt.0, [0u8; 4]
const MAX: u32 = 100;Compile-time constant (type required)
if as an expressionReturns a valuelet k = if y > 10 { "big" } else { "small" };
loop { break value; }Infinite loop that can return a value
for i in 0..3 / 0..=3 / (1..=3).rev()Exclusive, inclusive and reversed ranges
while cond { }While loop
match code { 200 => .., 400..=499 => .., _ => .. }Exhaustive pattern match
println!("{x} {:?} {:#?}", v, s)Inline names, Debug and pretty Debug output

Ownership, borrowing and lifetimes

Rule or syntaxMeaning
One owner per valueWhen the owner goes out of scope, the value is dropped
let s3 = s1;Move: s1 can no longer be used (for String, Vec and other heap types)
s1.clone()Explicit deep copy
Copy types (i32, bool, char, …)Copied, not moved
&TShared borrow; any number at once
&mut TExclusive borrow; only one, and no shared borrows at the same time
String vs &strOwned, growable text vs a borrowed string slice. Take &str in parameters
fn longest<'a>(a: &'a str, b: &'a str) -> &'a strLifetime: the result lives no longer than both inputs
fn first_word(s: &str) -> &str {
    s.split_whitespace().next().unwrap_or("")
}

let mut s = String::from("ab");
{
    let r = &mut s;      // exclusive borrow
    r.push('c');
}                        // borrow ends here
println!("{} {}", s, first_word("hello world"));   // abc hello

Structs, enums and pattern matching

#[derive(Debug, Clone, PartialEq)]
struct Server {
    name: String,
    port: u16,
}

impl Server {
    fn new(name: &str, port: u16) -> Self {
        Self { name: name.to_string(), port }
    }
    fn set_port(&mut self, port: u16) { self.port = port; }
}

enum Event {
    Login { user: String, ok: bool },
    Restart(String),
    Quit,
}

for e in &events {
    match e {
        Event::Login { user, ok: false } => println!("failed login {user}"),
        Event::Login { user, .. } => println!("login {user}"),
        Event::Restart(svc) => println!("restart {svc}"),
        Event::Quit => println!("quit"),
    }
}
SyntaxWhat it does
Option<T>: Some(v) / NoneA value that may be missing; Rust has no null
if let Some(p) = maybe { }Match one pattern
if let Some(p) = maybe && p < 1024 { }let chains (edition 2024, Rust 1.88+)
let Some(p) = maybe else { return; };let–else: bind or leave early
opt.unwrap_or(0), opt.map(|p| p + 1)Default value / transform the inner value
#[derive(Debug, Clone, PartialEq)]Generate common trait implementations

Collections and iterators

SyntaxWhat it does
let mut v = vec![5, 3, 8];Growable vector
v.push(1), v.pop(), v.len()Add, remove last, length
v[0] / v.get(10)Index (panics if out of range) / returns Option
v.sort(), v.sort_by_key(|s| s.port)Sort in place
v.retain(|&n| n != 3)Keep only matching items
v.iter().map(|n| n * n).filter(|n| *n > 1).collect::<Vec<_>>()Lazy iterator chain, collected into a new Vec
v.iter().sum::<i32>(), .count(), .max()Common consumers
for (i, x) in v.iter().enumerate()Index and value
HashMap::new() + m.insert(k, v) + m.get(&k)Hash map from std::collections
*m.entry(k).or_insert(0) += 1;Counting with the entry API

Error handling with Result and ?

use std::{error::Error, fs};

fn parse_port(s: &str) -> Result<u16, std::num::ParseIntError> {
    let p: u16 = s.trim().parse()?;     // ? returns the error early
    Ok(p)
}

fn run() -> Result<(), Box<dyn Error>> {
    let text = fs::read_to_string("/etc/hostname")?;
    println!("{}", text.trim());
    Ok(())
}

fn main() {
    if let Err(e) = run() {
        eprintln!("error: {e}");
        std::process::exit(1);
    }
}
SyntaxUse it for
Result<T, E>: Ok(v) / Err(e)Any operation that can fail
?Return the error to the caller, converting it with From if needed
Box<dyn Error>Any error type; fine for binaries and scripts
Custom enum + impl Display + impl ErrorTyped errors for libraries
.unwrap() / .expect("msg")Panic on error; acceptable in tests and prototypes
.ok_or(err)Turn an Option into a Result
panic!("...")Unrecoverable bug; unwinds the thread

Traits, generics and threads

trait Check {
    fn name(&self) -> String;
    fn healthy(&self) -> bool { true }   // default method
}

impl Check for Server {
    fn name(&self) -> String { format!("{}:{}", self.name, self.port) }
}

fn report(c: &impl Check) -> String { /* static dispatch */ c.name() }
fn largest<T: PartialOrd + Copy>(items: &[T]) -> T {   // trait bounds
    items.iter().copied().fold(items[0], |m, x| if x > m { x } else { m })
}
let checks: Vec<Box<dyn Check>> = vec![Box::new(srv)];  // dynamic dispatch
SyntaxWhat it does
thread::spawn(move || { ... })New OS thread; move transfers captured values
handle.join().unwrap()Wait for a thread
thread::scope(|s| { s.spawn(|| ...); })Threads that may borrow local data
Arc::new(Mutex::new(0))Shared, mutable state across threads
*c.lock().unwrap() += 1;Lock, modify, unlock at end of statement
let (tx, rx) = mpsc::channel();Message passing between threads

The standard library has no async runtime. For async network services, most projects add a runtime crate such as Tokio with cargo add.

Files, arguments and a log example

For server admins: count failed SSH logins per source IP, reading the log line by line so large files stay cheap.

use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io::{self, BufRead, BufReader};

fn main() -> io::Result<()> {
    let path = env::args().nth(1).unwrap_or_else(|| "/var/log/secure".into());
    let reader = BufReader::new(File::open(&path)?);

    let mut hits: HashMap<String, u32> = HashMap::new();
    for line in reader.lines() {
        let line = line?;
        if let Some(rest) = line.split("Failed password").nth(1)
            && let Some(ip) = rest.split(" from ").nth(1)
        {
            let ip = ip.split_whitespace().next().unwrap_or("?");
            *hits.entry(ip.to_string()).or_insert(0) += 1;
        }
    }
    let mut top: Vec<_> = hits.into_iter().collect();
    top.sort_by_key(|(_, n)| std::cmp::Reverse(*n));
    for (ip, n) in top.iter().take(10) {
        println!("{n:>7} {ip}");
    }
    Ok(())
}
SyntaxWhat it does
fs::read_to_string(path)?Whole file as a String
fs::write(path, data)?Create or overwrite a file
env::args().nth(1)First command-line argument
env::var("HOME")Environment variable as Result<String, _>
Command::new("systemctl").args(["is-active", "nginx"]).output()?Run a command without a shell (std::process::Command)
#[cfg(test)] mod tests { #[test] fn t() { assert_eq!(2 + 2, 4); } }Unit test, run by cargo test

Official documentation (checked 9 October 2026): The Rust Programming Language (the book) · Rust standard library · The Rust Reference · The Cargo Book · Edition Guide: Rust 2024 · Rust Release Notes

Rust Cheat Sheet at a glance

Rust Cheat Sheet – short answer
In short: A compact reference for current stable Rust covering Cargo, ownership, pattern matching, collections, error handling, traits and threads.
Rust Cheat Sheet – what this guide covers
Rust Cheat Sheet: what this guide covers.

Related guides: C Cheat Sheet: C17 and C23 Syntax, Memory and gcc Flags · C++ Cheat Sheet: C++20 and C++23 Syntax, STL and Ranges · Go Cheat Sheet: Syntax, Slices, Errors and Goroutines.

Reading a lot of our guides? Go ad-free with srvScripts Pro for $6 a month. It also pays for new guides. Cancel any time.

Share this guide

Free website test

Is your website set up right?

Check SSL, security headers, redirects, robots.txt, sitemap, llms.txt and security.txt in one test. It takes about 30 seconds.