Rust Cheatsheet

2026-04-05 2026-04-05444 Words

Fundamental trait

fmt

Debug

For debug output, combine with {:?} or {:#?}(pretty format).

#[derive(Debug)]
struct Point {
    x: f64,
    y: f64,
}

fn main() {
    let p = Point {x: 1.0, y: 2.0};
    println!("{:?}", p);        // Point {x: 1.0, y: 2.0}
    println!("{:#?}", p);        // pretty format with multiline
}

Or impl it manually which we could control the format of output.

use std::fmt;

struct Color(u8, u8, u8);

impl fmt::Debug for Color {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "#{:02X}{:02X}{:02X}", self.0, self.1, self.2)
    }
}

fn main() {
    let c = Color(255, 128, 0);
    println!("{:?}", c);        // #FF8000
}

Display

For output information towards to user. Format with {}. Can not impl by derive, only impl manually.

use std::fmt;

struct Temperature {
    celsius: f64,
}

impl fmt::Display for Temperature {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:.1}°C", self.celsius)
    }
}

fn main() {
    let t = Temperature { celsius: 36.7};
    println!("{}", t);          // 36.7°C

    // will auto impl ToString when impl Display
    let s: String = t.to_string();
}

blanket impl: any type that impl Display will blanket impl ToString.

cmp

PartialEq / Eq

Use for == and != comparation. Eq provide extra reflexivity a==a always true.

#[derive(Debug, PartialEq)]
struct Point {
    x: f64,
    y: f64,
}

fn main() {
    let a = Point {x: 1.0, y: 2.0 };
    let b = Point {x: 1.0, y: 2.0 };
    let c = Point {x: 1.0, y: 4.0 };

    assert!(a == b);            // true
    assert!(a != c);            // true
}

f64 can not impl Eq.

fn main() {
    let nan = f64::NAN;
    println!("{}", nan == nan); // false NAN != NAN
}

Custom comparation:

struct CaseInsensitive(String);

impl PartialEq for CaseInsensitive {
    fn eq(&self, other: &Self) -> bool {
        self.0.to_lowercase() == other.0.to_lowercase()
    }
}

fn main() {
    let a = CaseInsensitive("Hello".into());
    let b = CaseInsensitive("hello".into());
    assert!(a == b);            // true
}

PartialOrd / Ord

PartialOrd: use for <, >, <=, >= comparation. The comparation result could not exist. Ord: all comparation, Must impl Eq first!

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Score {
    // First compare grade then name
    grade: u32,
    name: String,
}

fn main() {
    let mut scores = vec![
        Score { grade: 90, name: "Alice".into() },
        Score { grade: 85, name: "Bob".into() },
        Score { grade: 90, name: "Aaron".into() },
    ];
    scores.sort();              // Need impl Ord
    // Result: Bob(85), Aaron(90), Alice(90)
}

Manually impl:

use std::cmp::Ordering;

#[derive(Debug, PartialEq, Eq)]
struct Student {
    name: String,
    score: u32,
}

impl PartialOrd for Student {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Student {
    fn cmp(&self, other: &Self) -> Ordering {
        // descend by score then ascend by name
        other.score.cmp(&self.score).then(self.name.cmp(&other.name))
    }
}

Copy / Clone

Clone

Explicit deep copy by calling .clone() method.

#[derive(Debug, Clone)]
struct Config {
    name: String,               // String already impl Clone (include heap data)
    values: Vec<i32>,           // Vec also does this
}

fn main() {
    let c1 = Config {
        name: "prod".into(),
        values: vec![1, 2, 3],
    };
    let c2 = c1.clone();        // deep copy!

    // c1 and c2 are absolutely two different objects.
    println!("{:?}", c1);
    println!("{:?}", c2);
}

Could manually impl this triat:

#[derive(Debug)]
struct Connection {
    url: String,
    id: u64,                    // generate new id via .clone()
}

static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);

impl Clone for Connection {
    fn clone(&self) -> Self {
        Self {
            url: self.url.clone(),
            id: COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst),
        }
    }
}

Copy

Implicit bitwise copy, not transfer ownship by assignment or parameter pass. Must impl Clone First!

#[derive(Debug, Clone, Copy)]
struct Point {
    x: f64,
    y: f64,
}

fn main() {
    let a = Point {x: 1.0, y: 3.0};
    let b = a;                  // Copy, bitwise, a still valid.
    let c = a;
    println!("{:?} {:?} {:?}", a, b, c);
}

All scalar type could impl Copy, but the type which own its heap data can not impl Copy. Since bitwise only copy the stack memory, will cause two variable pointer to same heap data.(Double free). String, Vec<T>, Box<T> are all own heap data.

Copy vs Clone:

  • Copy is managed by compiler, bitwise(memcpy), zero cost.
  • Clone is explicitly call by programmer, could custom its implementation, could cost.

Default

Provide default value for type.

#[derive(Debug, Default)]
struct Config {
    host: String,               // ""
    port: u16,                  // 0
    debug: bool,                // false
    max_conn: usize,            // 0
}

fn main() {
    // all set default
    let c1 = Config::default();

    // partial default
    let c2 = Config {
        port: 8080,
        debug: true,
        ..Default::default()
    };
}

Hash

Use for key of HashMap or HashSet.

use std::collections::HashMap;

#[derive(Debug, PartialEq, Eq, Hash)]
struct StudentId {
    year: u32,
    number: u32,
}

fn main() {
    let mut grades: HashMap<StudentId, f64> = HashMap::new();
    grades.insert(StudentId{year: 2024, number: 1}, 95.0);
    grades.insert(StudentId{year: 2024, number: 2}, 85.0);

    let id = StudentId {year: 2024, number: 1};
    println!("{:?}", grades.get(&id)); // Some(95.0)
}

If a \== b, then must hash(a) == hash(b).

Type Convert

From / Into (TryFrom / TroInto)

Transfer bwteen values. Impl one will blanket impl another.

#[derive(Debug)]
struct Meters(f64);

#[derive(Debug)]
struct Kilometers(f64);

impl From<Kilometers> for Meters {
    fn from(km: Kilometers) -> Self {
        Meters(km * 1000.0)
    }
}

fn main() {
    let km = Kilometers(5.0);

    let m = Meters::from(Kilometers(5.0));
    let m = km.into();
}

stdlib:

&str -> String i32 -> i64 Vec<u8> -> String

common used for error handle:

use std::io;
use std::num::ParseIntError;

#[derive(Debug)]
enum AppError {
    Io(io::Error),
    Parse(ParseIntError),
}

impl From<io::Error> for AppError {
    fn from(e: io::Error) -> Self {
        AppError::Io(e)
    }
}

impl From<ParseIntError> for AppError {
    fn from(e: ParseIntError) -> Self {
        AppError::Parse(e)
    }
}

fn read_number(path: &str) -> Result<i32, AppError> {
    let content = std::fs::read_to_string(path)?; // auto into
    let num = content.trim().parse::<i32>()?;     // auto into
    Ok(num)
}

AsRef / AsMut

Zero cost for reference inner data for certain type. Or accept multiple type that looks like the same type.

Common usage:

pub fn read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String>;
// &str
// String
// &Path
// PathBuf

This trait simplify the the process of type convert.

Deref / DerefMut

Use for smart pointer type that allow automatically dereference.

use std::ops::Deref;

struct MyBox<T>(T);

impl<T> Deref for MyBox<T> {
    type target = T;
    fn deref(&self) -> &T {
        &self.0
    }
}

fn main() {
    let x = MyBox(40);
    assert_eq!(*x, 40);

    let s = MyBox(String::from("Hello"));
    fn takes_str(s: &str) { println!("{}", s); }
    takes_str(&s);              // compiler automatically dereference
}

stdlib:

// Box<T> -> T
// String -> str
// Vec<T> -> [T]
// Arc<T> -> T
// Rc<T>  -> T
// MuteGuard<T> -> T

Operator Overload (ops)

Add / Sub / Mul

use std::ops::Add;

#[derive(Debug, Clone, Copy)]
struct Vec2 {
    x: f64,
    y: f64,
}

impl Add for Vec2 {
    type Output = Vec2;
    fn add(self, other: Vec2) -> Vec2 {
        Vec2 {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

impl Add<f64> for Vec2 {
    type Output = Vec2;
    fn add(self, scalar: f64) -> Vec2 {
        Vec2 {
            x: self.x + scalar,
            y: self.y + scalar,
        }
    }
}

fn main() {
    let a = Vec2 {x: 1.0, y: 2.0 };
    let b = Vec2 {x: 2.0, y: 3.0 };
    let c = a + b;
    let d = a + 10.0;
}

Index / IndexMut

use [] access.

use std::ops::Index;

#[derive(Debug)]
struct Matrix {
    data: Vec<Vec<f64>>,
}

impl Index<(usize, usize)> for Matrix {
    type Output = f64;
    fn index(&self, idx: (usize, usize)) -> &f64 {
        &self.data[idx.0][idx.1]
    }
}

fn main() {
    let m = Matrix {
        data: vec![
            vec![1.0, 2.0],
            vec![2.0, 3.0],
        ],
    };
    println!("{}", m[(1, 1)]);  // 3.0
}

Iterator

Iterator

Only need impl next() method.

struct CountDown {
    value: u32,
}
impl CountDown {
    fn new(start: u32) -> Self {
        CountDown { value: start }
    }
}
impl Iterator for CountDown {
    type Item = u32;
    fn next(&mut self) -> Option<u32> {
        if self.value == 0 {
            None
        } else {
            self.value -= 1;
            Some(self.value + 1)
        }
    }
}

fn main() {
    for n in CountDown::new(5) { // NOTE: for loop inner call into_iter which is impl by IntoIterator, but All Iterator auto impl that.
        println!("{}", n);
    }
}

IntoIterator

for loop implementation is call .into_iter().

relationship comment
Iterator what type that will produce items by next()
IntoIterator what type could become Iterator by intoiter()
Iterator->IntoIterator auto impl (blanket impl)
IntoIterator->Iterator NO!
for loop call IntoIterator
function parameter priority for IntoIterator(more flexible)

Closure

Fn / FnMut / FnOnce

The difference between these is how to capture the variables.

// FnOnce consume the variable, call only once
fn call_once(f: impl FnOnce() -> String) -> String {
    f()
}

// FnMut capture with mut reference
fn call_mut(mut f: impl FnMut()) {
    f();
    f();
}

// Fn capture with reference
fn call_fn(f: impl Fn() -> i32) -> i32 {
    f() + f()
}

fn main() {
    let x = 1000;               // read reference
    let add = |y| x + y;        // impl Fn
    println!("{}", call_fn(|| add(5)));

    let mut count = 0;
    let mut increment = || {
        count += 1;
    };
    call_mut(&mut increment);

    let name = String::from("Alice");
    let greet = move || format!("Hello, {}!", name);
    println!("{}", call_once(greet));
    // greet can not call again
}

inherit:

FnOnce(most general)
  ↑
FnMut(subtrait)
  ↑
Fn(subtrait)

So: we could pass Fn and FnMut to which require FnOnce, but only Fn that require Fn.

Error Handle

Error

Need also impl Debug and Display.

use std::fmt;
use std::error::Error;

#[derive(Debug)]
enum DatabaseError {
    ConnectionFailed(String),
    QueryFailed { query: String, reson: String },
    Timeout,
}

impl fmt::Display for DatabaseError {
    fn fmt(&self, f: fmt::Formatter) -> fmt::Result {
        match self {
            Self::ConnectionFailed(addr) => {
                write!(f, "Could not connection to database: {}", addr),
            }
            Self::QueryFailed {query, reason} => {
                write!(f, "Query failed {}: {}", query, reason),
            }
            Self::Timeout => {
                write!(f, "Database Timeout"),
            }
        }
    }
}

impl Error for DatabaseError {} // source() return None default

Lifetime

Drop

Call automatically when that type's instance leave out the scope.

struct TempFile {
    path: String,
}
impl TempFile {
    fn new(path: &str) -> Self {
        Self {path: path.into()}
    }
}
impl Drop for TempFile {
    fn drop(&mut self) {
        println!("delete tempfile: {}", self.path);
        // Do actually delete
    }
}

Any type that impl Drop can not impl Copy too. Cause compiler does not know when to call drop.

Borrow / ToOwned

  • Borrow: Another way to get reference, but make sure that Hash and Eq and Ord consistency.
  • ToOwned: create ownship from reference. (more general clone)
use std::collections::HashMap;
use std::borrow::Borrow;

fn main() {
    let mut map: HashMap<String, i32> = HashMap::new();
    map.insert("hello".to_string(), 64);

    // the parameter of HashMap::get use Borrow:
    // fn get<Q>(&self, k: &Q) -> Option<&V> where K: Borrow<Q>
    // String: Borrow<str>, so can use &str for query
    let val = map.get("hello");

    let s: &str = "hello";
    let owned: String = s.to_owned();
    // Convension not Rule
    // hash(T) == hash(T.borrowed)
    // (T == other) <==> (borrowed == other)
    // T.cmp(..) has same order with T.borrowed.cmp(..)
}

Concurrence

  • Most type automatically impl Send + Sync.
  • No Send: Rc<T> (reference count operation is not atomic).
  • No Sync: Cell<T>, RefCell<T>

Creator: Emacs 31.0.50 (Org mode 10.0-pre)