Functions in Rust
Functions declare inputs and outputs with explicit types. Rust has expression-based returns and strong inference at call sites.
Signatures and return values
fn add(a: i32, b: i32) -> i32 {
a + b // last expression returns
}
fn greet(name: &str) -> String {
format!("Hello, {name}!")
}
Use return expr; to return early.
Ownership and borrowing parameters
Choose by-value or by-reference based on ownership.
fn len(s: &str) -> usize { s.len() } // borrow
fn take(v: Vec<i32>) { /* consumes */ } // move ownership
Prefer borrowing &T/&mut T to avoid unnecessary moves.
Lifetimes (when needed)
Elision covers many cases; add lifetimes to relate inputs/outputs.
fn first<'a>(s: &'a str) -> &'a str { &s[..1] }
Generics and trait bounds
fn max<T: Ord>(a: T, b: T) -> T { if a >= b { a } else { b } }
fn print_all<T: std::fmt::Display>(xs: &[T]) {
for x in xs { println!("{x}"); }
}
Use where for readability with many bounds.
Result and error propagation
Return Result for fallible functions and use ? to propagate.
use std::fs::File; use std::io::{self, Read};
fn read_all(path: &str) -> io::Result<String> {
let mut s = String::new();
File::open(path)?.read_to_string(&mut s)?;
Ok(s)
}
Associated functions vs methods
Associated functions live in impl blocks; self, &self, or &mut self are method receivers.
struct Counter { n: u64 }
impl Counter {
fn new() -> Self { Self { n: 0 } } // associated
fn inc(&mut self) { self.n += 1 } // method
}
Diverging functions and never type
Use -> ! for functions that never return.
fn fail(msg: &str) -> ! { panic!("{msg}") }
Closures (preview)
Inline anonymous functions capture environment by reference or move.
let add = |x: i32, y: i32| x + y;
Summary
- Choose ownership vs borrowing in parameters deliberately
- Use generics and trait bounds to write reusable functions
- Return
Resultand use?; implement methods/associated fns inimpl