Functions
Rust functions look close to C's, with one distinctly Rust habit: the last expression in a function body — with no semicolon — is automatically its return value, no return keyword required.
Declaring a function
fn add(a: i32, b: i32) -> i32 { a + b } fn main() { let sum = add(3, 4); println!("Sum: {}", sum); }
Sum: 7
Parameter types are required (Rust never infers them for you), and -> i32 declares the return type. Notice a + b has no semicolon and no return — that's what makes it the function's return value. Add a semicolon and the meaning changes completely:
fn add(a: i32, b: i32) -> i32 { a + b; }
error[E0308]: mismatched types
|
1 | fn add(a: i32, b: i32) -> i32 {
| --- expected `i32` because of return type
2 | a + b;
| ^ help: remove this semicolon to return this valueA semicolon turns a + b from an expression into a statement — a statement produces no value at all, so the function's body ends up with nothing to return, even though it declared -> i32. This trips up almost everyone coming from a semicolon-everywhere language at first.
You can still use return early
fn classify(n: i32) -> &'static str { if n < 0 { return "negative"; } if n == 0 { return "zero"; } "positive" } fn main() { println!("{}", classify(-5)); println!("{}", classify(0)); println!("{}", classify(5)); }
negative zero positive
Explicit return works exactly as it does elsewhere, and is normal for exiting early. The convention in Rust is to use the no-semicolon final expression for the "normal" return value, and return only for early exits — mixing styles for the same purpose in one function tends to read as inconsistent.
Passing ownership vs. borrowing into a function
This is where the Ownership lesson comes back directly: passing a String by value moves it into the function, while passing &String only borrows it.
fn shout(text: &String) -> String { text.to_uppercase() } fn main() { let message = String::from("hello"); let shouted = shout(&message); println!("{} -> {}", message, shouted); }
hello -> HELLO
&String, &str, &Vec<T>) for parameters that a function only needs to read, and only take ownership when the function genuinely needs to consume or store the value. This keeps the caller free to keep using their original variable afterward, same as shout leaves message usable above.