Digest: functions
Core principles
- Rust requires you to specify the type of each parameter.
- The return type is declared after an
->, and the last expression is returned implicitly. - Functions can take ownership, borrow values immutably, or mutably.
- Top-level functions are declared with
fn, and methods can be attached to structs viaimpl.
Basic example
fn square(x: i32) -> i32 { x * x } fn main() { let result = square(4); println!("4 squared is {}", result); }
The function square takes an i32 and returns an i32.
The return is implicit: no semicolon after x * x.
With explicit return
#![allow(unused)] fn main() { fn square_explicit(x: i32) -> i32 { return x * x; } }
Use return only when you want to exit early or improve readability.
With references (borrowing)
#![allow(unused)] fn main() { fn first_letter(s: &String) -> Option<char> { s.chars().next() } }
This borrows s (does not take ownership).
The return type is Option<char> since .next() may return None.
With mutable references
#![allow(unused)] fn main() { fn push_zero(v: &mut Vec<i32>) { v.push(0); } }
The &mut tells the compiler that this function modifies the vector.
Rust enforces exclusive access to v while it is borrowed mutably.
Generic functions
Rust does not support function overloading. Instead, use generics:
#![allow(unused)] fn main() { fn identity<T>(x: T) -> T { x } }
This works with any type T, as long as the usage allows it. You can also restrict what kinds of types T can be by adding trait bounds like T: SomeTrait.
We will introduce traits and trait bounds in another digest. As an illustration, consider this function:
#![allow(unused)] fn main() { fn add<T: Sum>(x: T, y: T) -> T { x + y } }
Here, only types T that implement the Sum trait can be used.
Functions as values
Functions can be passed around like any other value:
fn greet(name: &str) { println!("Hello, {}!", name); } fn apply<F>(f: F) where F: Fn(&str), { f("world"); } fn main() { apply(greet); }
Functions implement the Fn, FnMut, or FnOnce traits.
You can pass closures or named functions.
Closures
Rust supports inline anonymous functions:
#![allow(unused)] fn main() { let add = |x: i32, y: i32| x + y; println!("{}", add(2, 3)); }
Closures capture variables from the environment.
Lifetimes in functions (advanced)
Sometimes you need to annotate lifetimes:
#![allow(unused)] fn main() { fn longer<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() > b.len() { a } else { b } } }
'a ensures the returned reference lives as long as both inputs.
Useful when dealing with borrowed values in more complex code.