Step 1 of 6
Your first closure
A closure is a function without a name that you write right where you need it. You can store it in a variable, pass it to another function, or return it, like any other value. Closures are everywhere in idiomatic Rust: sorting by a key, filtering a list, providing a default value, running code on another thread.
fn main() {
let double = |x| x * 2;
let greet = |name: &str| format!("Hello, {name}!");
let area = |width: u32, height: u32| -> u32 { width * height };
println!("{}", double(21));
println!("{}", greet("Ferris"));
println!("{}", area(3, 4));
}
42
Hello, Ferris!
12
Piece by piece:
|x| x * 2: the parameters go between two pipes| |instead of parentheses, and the body follows. A body that is a single expression needs no braces.let double = ...: a closure is a value, so you can keep it in a variable. You call it like a function:double(21).|name: &str|: you may write parameter types, like in afn. Usually you don't have to: the compiler infers them from how the closure is used.|width: u32, height: u32| -> u32 { ... }: the long form, with types, a return type and a block body. If you write a return type, the body must be a block in braces.- A closure with no parameters has empty pipes:
|| println!("ready").
Side by side with a function:
fn add_one_fn(x: i32) -> i32 { x + 1 } // a function: every type is required
let add_one_v1 = |x: i32| -> i32 { x + 1 }; // a closure, fully annotated
let add_one_v2 = |x: i32| x + 1; // parameter type only
let add_one_v3 = |x| x + 1; // everything inferred from the first call
Why not just write a fn?
A fn must spell out every type and lives on its own, away from the code that uses it. A closure is short, sits right next to where it's used, and (next step) it can use the variables around it, which a fn can't. Rule of thumb: a reusable, named piece of logic is a fn; a small piece of behavior you hand to someone else (a sort key, a filter test, a callback) is a closure.
Common mistakes
- Calling one closure with two different types. Inference fixes the types the first time the closure is used. A closure is not generic:
fn main() {
let echo = |x| x;
let s = echo(String::from("crab"));
let n = echo(5);
}
error[E0308]: mismatched types
The first call decided that x is a String, so echo(5) is rejected (expected String, found integer). If you need both, write two closures or a generic fn.
- A return type without braces. Once you write
-> i32, the body must be a block:
fn main() {
let add_one = |x| -> i32 x + 1;
println!("{}", add_one(1));
}
error: expected `{`, found `x`
Write |x| -> i32 { x + 1 }, or leave the return type out: |x| x + 1.
Your task
Fill in the blanks: the parameter list of square (a closure that multiplies its parameter by itself) and the body of add, so the program prints 49 and then 5.
Challenge 2
Write a closure to_fahrenheit that converts a Celsius temperature (f64) to Fahrenheit with c * 9.0 / 5.0 + 32.0. Use it to print these three lines, with one decimal place:
0.0 C = 32.0 F
100.0 C = 212.0 F
-40.0 C = -40.0 F
Challenge 3
A closure can have a block body with several statements, like a function. Write a closure grade that takes a score (u32) and returns a char:
'A'for 90 and above'B'for 75 to 89'C'for 50 to 74'F'below 50
Then print the grade of each score in [95, 82, 50, 49], one per line, like 95 -> A.