Rust Cheatsheet

Functions

Use this Rust reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Basic Function Syntax

fn function_name(param1: Type1, param2: Type2) -> ReturnType {
    // body
    return_value   // last expression, no semicolon
}

fn add(x: i32, y: i32) -> i32 {
    x + y  // implicit return
}

fn greet(name: &str) {
    println!("Hello, {}!", name);
}  // implicitly returns ()
  • Parameters must have explicit types.
  • Return type follows ->. Omit for ().
  • Early return with return expr;.

Return Values

// Implicit return — last expression without semicolon
fn square(x: i32) -> i32 {
    x * x
}

// Explicit return
fn max_positive(x: i32, y: i32) -> i32 {
    if x < 0 && y < 0 { return 0; }
    if x > y { x } else { y }
}

// Return unit ()
fn nothing() -> () { }

// Return tuple (multiple values)
fn min_max(v: &[i32]) -> (i32, i32) {
    (*v.iter().min().unwrap(), *v.iter().max().unwrap())
}
let (lo, hi) = min_max(&[3, 1, 4, 1, 5]);

Closures

Anonymous functions that capture their environment.

let double = |x: i32| x * 2;                    // single expression
let add = |x: i32, y: i32| -> i32 { x + y };   // explicit return type
let greet = |name: &str| { println!("Hi, {}!", name); }; // block body

double(5)   // 10
add(3, 4)   // 7
greet("Alice");

// Type inference — usually no annotations needed
let double = |x| x * 2;   // i32 inferred from usage

Capturing:

let factor = 3;

// Capture by reference (default, least restrictive)
let multiply = |x| x * factor;

// Capture by mutable reference
let mut count = 0;
let mut increment = || { count += 1; };
increment();

// Capture by value (move)
let name = String::from("Alice");
let greet = move || println!("Hello, {}!", name);
// name is moved into closure; not available here

Closure traits:

TraitCan captureCallable
FnOnceBy value (move)Once
FnMutBy mutable refMultiple, mut context
FnBy refMultiple, any context

Every closure implements FnOnce. If it doesn't consume captured values, it also implements FnMut. If it doesn't mutate them, also Fn.

Higher-Order Functions

// Accept a closure
fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
    f(x)
}

let result = apply(|x| x * 2, 5);  // 10

// Also accepted via trait objects (dynamic dispatch)
fn apply_dyn(f: &dyn Fn(i32) -> i32, x: i32) -> i32 {
    f(x)
}

// Return a closure
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n
}
let add5 = make_adder(5);
add5(10)   // 15

// Return a boxed closure (when return type is not known at compile time)
fn make_adder_boxed(n: i32) -> Box<dyn Fn(i32) -> i32> {
    Box::new(move |x| x + n)
}

Function Pointers

fn add(x: i32, y: i32) -> i32 { x + y }
fn sub(x: i32, y: i32) -> i32 { x - y }

let op: fn(i32, i32) -> i32 = add;
op(3, 4)   // 7

// Function pointers implement all three Fn traits
// Pass function pointers where closures are expected
let v = vec![1, 2, 3];
let doubled: Vec<_> = v.iter().map(|x| x * 2).collect();
// or use a named fn directly:
fn double(x: &i32) -> i32 { x * 2 }
let doubled: Vec<_> = v.iter().map(double).collect();

Generic Functions

fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest { largest = item; }
    }
    largest
}

fn first<T>(list: &[T]) -> Option<&T> {
    list.first()
}

// Multiple bounds
fn print_info<T: std::fmt::Debug + std::fmt::Display>(x: T) {
    println!("{} / {:?}", x, x);
}

// where clause (cleaner for complex bounds)
fn complex<T, U>(t: T, u: U) -> String
where
    T: std::fmt::Display + Clone,
    U: std::fmt::Debug,
{
    format!("{} {:?}", t, u)
}

Methods (impl blocks)

struct Rectangle {
    width: f64,
    height: f64,
}

impl Rectangle {
    // Associated function (no self — like a static method)
    fn new(width: f64, height: f64) -> Self {
        Rectangle { width, height }
    }

    // Method — takes self by reference
    fn area(&self) -> f64 {
        self.width * self.height
    }

    // Mutable method
    fn scale(&mut self, factor: f64) {
        self.width *= factor;
        self.height *= factor;
    }

    // Consumes self
    fn into_square(self) -> Rectangle {
        let side = self.width.min(self.height);
        Rectangle { width: side, height: side }
    }
}

let rect = Rectangle::new(4.0, 3.0);
rect.area()               // 12.0
let mut r = rect;
r.scale(2.0);             // width=8, height=6
let sq = r.into_square(); // r consumed

Diverging Functions

fn panic_now() -> ! {
    panic!("always panics");
}

fn infinite() -> ! {
    loop {}
}

fn exit_program() -> ! {
    std::process::exit(1);
}

Variadic and Overloading

Rust has no variadic functions (except macros) and no function overloading. Use macros or trait methods for variadic-style APIs.

// Overloading via trait
use std::ops::Add;

fn sum<T: Add<Output = T>>(a: T, b: T) -> T {
    a + b
}

sum(1i32, 2)     // 3
sum(1.5f64, 2.5) // 4.0

Recursion

fn factorial(n: u64) -> u64 {
    if n <= 1 { 1 } else { n * factorial(n - 1) }
}

// Rust does NOT guarantee tail-call optimization (TCO)
// For deep recursion, use iteration or an explicit stack
fn fib(n: u64) -> u64 {
    let (mut a, mut b) = (0, 1);
    for _ in 0..n { (a, b) = (b, a + b); }
    a
}

Default Parameter Values

Rust has no default parameters. Use the builder pattern or Option:

fn connect(host: &str, port: Option<u16>) {
    let port = port.unwrap_or(8080);
    println!("{}:{}", host, port);
}
connect("localhost", None);     // uses 8080
connect("example.com", Some(443));

// Or builder pattern
struct ConnectConfig {
    host: String,
    port: u16,
    timeout: u64,
}
impl Default for ConnectConfig {
    fn default() -> Self {
        ConnectConfig { host: "localhost".into(), port: 8080, timeout: 30 }
    }
}

impl Trait in Function Position

// Return an opaque type that implements a trait
fn make_greeting(name: &str) -> impl std::fmt::Display {
    format!("Hello, {}!", name)
}

// Accept any iterator of i32
fn sum_iter(iter: impl Iterator<Item = i32>) -> i32 {
    iter.sum()
}

// Cannot return different types with impl Trait — use Box<dyn Trait> for that
fn make_iter(use_range: bool) -> Box<dyn Iterator<Item = i32>> {
    if use_range {
        Box::new(0..10)
    } else {
        Box::new(vec![1, 2, 3].into_iter())
    }
}

Attributes on Functions

#[inline]              // hint: inline this fn
#[inline(always)]      // force inline
#[inline(never)]       // never inline
#[must_use]            // warn if return value is unused
#[must_use = "msg"]
#[allow(dead_code)]    // suppress unused warning
#[deprecated]          // mark as deprecated
#[cold]                // hint: infrequently called (error paths)
#[cfg(test)]           // only compile in test mode
#[test]                // mark as unit test
fn my_test() { assert_eq!(1 + 1, 2); }

Async Functions

use tokio; // common async runtime

async fn fetch_data(url: &str) -> Result<String, reqwest::Error> {
    let response = reqwest::get(url).await?;
    response.text().await
}

#[tokio::main]
async fn main() {
    let data = fetch_data("https://example.com").await.unwrap();
    println!("{}", data);
}

// Async closures (nightly) — use async blocks as workaround
let f = |x: i32| async move { x * 2 };