Rustlers Atom 1.7: Functions
April 18, 2026•348 words
Functions in Rust are explicit and predictable. You define a function with fn, name it in snake_case` and declare parameters with type annotations:
fn main() {
greet_to("Gioele");
}
fn greet_to(name: &str) {
println!("Hello, {name}!");
}
Rust requires type annotations for all parameters — they’re not optional. This makes functions self-documenting and ensures compile-time clarity. A function body is a block expression, and, just like before, the last expression without a semicolon becomes the return value.
The Return Arrow (->)
The arrow indicates the function’s output type. If a function returns nothing, it implicitly returns () (the unit type).
fn add(a: i32, b: i32) -> i32 {
a + b // returned automatically
}
fn print_sum(a: i32, b: i32) {
println!("Sum = {}", a + b); // returns `()`
}
You can also use the return keyword explicitly, especially for early exits or branching logic:
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
return None;
}
Some(a / b)
}
Functions can return tuples to bundle multiple values neatly:
fn split_sum(a: i32, b: i32) -> (i32, i32, i32) {
let sum = a + b;
(a, b, sum)
}
fn main() {
let (x, y, total) = split_sum(10, 20);
println!("{x} + {y} = {total}");
}
This is a common idiom for returning structured results without defining a full struct.
Parameters and Ownership
Each parameter in a function is a binding with its own ownership semantics:
- Passing a value like
Stringmoves ownership into the function. - Passing a reference (
&T) borrows it temporarily. - Passing a mutable reference (
&mut T) allows in-place modification.
fn main() {
let mut s = String::from("Hello");
append_world(&mut s);
println!("{s}");
}
fn append_world(s: &mut String) {
s.push_str(", world!");
}
The signature &mut String declares that the function can mutate the borrowed string, but it doesn’t own it. Once the function ends, the borrow ends too.