Step 1 of 5 · Reading · ~3 min
Learn
Functions and Ownership
Functions
By the end of this lesson you can write your own Rust functions with typed parameters and a return value, return a result without the return keyword, and build one function out of calls to another.
The shape of a function
fn double(x: i64) -> i64 {
x * 2
}
fn doublenames the function. Names aresnake_case; the compiler warns otherwise.(x: i64)is the parameter list. Every parameter needs a type written out. Rust infers types inside a body, never in a signature: the signature is the contract callers rely on.-> i64is the return type, the type of the value the caller gets back.- The body is a block. Its last line,
x * 2, is the value the function hands back.
Calling it looks like every other language: double(21) evaluates to 42, and println!("{}", double(21)); prints 42. Functions can be defined above or below main; order in the file does not matter.
Expressions, statements, and the semicolon
An expression produces a value (x * 2, double(5), an if with both branches). A statement does something and produces nothing; a trailing ; turns an expression into a statement. "Nothing" in Rust is a real type, written () and called unit.
The last expression of a block, written without a semicolon, is the block's value. Add the semicolon and the value is thrown away:
fn double(x: i64) -> i64 {
x * 2; // statement: the block's value is now ()
}
// error[E0308]: mismatched types
// expected `i64`, found `()`
// help: remove this semicolon
When you see expected i64, found (), look at the last line first. A function with no -> at all returns (), which is right for a function that only prints:
fn show(label: &str, value: i64) {
println!("{} = {}", label, value);
}
return exists, and idiomatic Rust uses it for early exits, letting the final expression fall out of the bottom:
fn clamp_to_ten(x: i64) -> i64 {
if x > 10 {
return 10;
}
x
}
// clamp_to_ten(4) -> 4, clamp_to_ten(99) -> 10
Functions that call functions
A function body can call any other function, so small pieces combine into bigger ones. A rectangle's perimeter is two widths plus two heights:
fn perimeter(w: i64, h: i64) -> i64 {
double(w) + double(h)
}
fn main() {
show("perimeter", perimeter(3, 5));
}
Trace perimeter(3, 5): double(3) gives 6, double(5) gives 10, 6 + 10 is 16, and show prints perimeter = 16. Each call copies its integer arguments in; the callee changing its own parameter never touches the caller's variable.
The width trap
The parameter type decides how big a value can get. i32 stops at 2,147,483,647:
fn triple32(x: i32) -> i32 { x * 3 }
fn triple64(x: i64) -> i64 { x * 3 }
// triple32(1_000_000_000) -> panic: attempt to multiply with overflow
// triple64(1_000_000_000) -> 3000000000
Multiplying a number by itself grows fastest of all: anything above 46,340 squared no longer fits in an i32. Keep the i64 signatures you are given.
Common mistakes
- Doubling instead of multiplying the value by itself.
x * 2andx + xgive4for input2, the only value where they agree with a square. For7they print14instead of49. - Squaring the sum. Adding
aandbfirst and then squaring gives(7 + -9)^2 = 4, not49 + 81 = 130. The sum of squares is two separate squares added together. - Forgetting to square the second number inside the combined function, so
10and3give103instead of109. - Narrowing to
i32. Changing a signature toi32or casting withas i32breaks on3000000000, whose square is 9,000,000,000,000,000,000.
Your exercise
Fill in the bodies of square and sum_of_squares; the starter already reads both numbers and prints the three results. Each body returns its value as a final expression, and sum_of_squares should reuse square instead of repeating the arithmetic. The most likely slip is squaring the sum rather than summing the squares.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…