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:
| Trait | Can capture | Callable |
|---|---|---|
FnOnce | By value (move) | Once |
FnMut | By mutable ref | Multiple, mut context |
Fn | By ref | Multiple, 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 };