Lesson 6.4 · 9 min
Small functions on the spot
You have been reading |m| m.text.len() as "given m, produce this". That is a closure: a small function written exactly where you need it. With one, Chatterbox can answer "show me the long messages".
Adds to ChatterboxA closure-powered /long command.
A function with no name
A closure is a small function you write in the middle of other code, without a name. The inputs go between two bars, then the result: |n| n + 1 means "given n, give back n plus one". If it is handy to reuse, put it in a let like any value and call it with brackets.
$ cargo run
42The part that makes closures worth learning: they can use variables from the code around them. Rust calls that capturing. A regular fn cannot see the variables of the function that calls it; a closure can.
What does this program print?
473
Reveal the answer
The program prints: 7 The closure never declared bonus. It reached out to the one in main and used it. Capturing is by borrow when it can be: the closure only looks at bonus, so bonus is still yours.
Closures meet lists
The usual job for a closure is to say what to do with each item to a method that does the walking. Two you need now:
filter(|m| …)keeps only the items for which the closure saystrue. Likemap, it is lazy, so add.collect()to get a Vec.sort_by_key(|m| …)sorts a Vec by whatever number or text the closure returns for each item. Equal items keep their original order.
Here is a "long messages" filter. The variable limit lives outside the closure; the closure captures it, so changing the limit is a one-place edit. Two small details in the program below: the list holds &str items and iter() borrows them, so each closure argument is really a reference to a reference (&&str), and Rust lets you call .len() on it anyway. And .copied() turns those borrowed references into plain &str values so collect() can build a Vec<&str>.
$ cargo run
["tell me about closures", "and what about iterators then"]Chatterbox v0.6: /long
Add a /long command. It filters the history for messages longer than limit characters, sorts them shortest first, and prints them. The filter closure uses limit from main, so the threshold lives in one named place. The history is only borrowed, so Chatterbox goes on taking messages afterwards.
Chatterbox now keeps a history, counts messages per user, and answers /history, /stats and /long.
$ cargo run
ada: 1
sam: 2
long (40 chars): yes, the context window got a lot bigger
long (44 chars): has anyone read the new model release notes?Write long_messages. Return the text of every message longer than limit characters (strictly longer), shortest first. Use a filter closure and sort_by_key.
Write long_messages. Return the text of every message longer than limit characters (strictly longer), shortest first. Use a filter closure and sort_by_key.
Hidden tests (4): empty_history drops_short_messages exactly_the_limit_is_not_long shortest_first
This exercise runs real Rust in your browser through the official Rust Playground, so it needs JavaScript.
What you have now
A closure is a function you write where you use it, and it can read the variables around it. Paired with filter, map and sort_by_key it replaces most hand-written loops over a list. Chapter 6 is done: Chatterbox has a history, counts, and a way to ask questions of both.
Say it out loud, then reveal
What does |n| n + 1 mean?Reveal
What does it mean for a closure to capture a variable?Reveal
What does filter(|m| m.text.len() > limit) keep?Reveal
That is lesson 6.4. Tick it off so the course remembers where you are.
Questions people ask
What is a closure in Rust?
A function without a name, written inline with |inputs| result, that can use variables from the code around it.
Is a closure the same as a Python lambda?
It is the nearest idea. A Rust closure can also have a full block body in braces, and it is typed: the compiler knows its inputs and result.
Does sort_by_key keep equal items in order?
Yes. It is a stable sort, so items with equal keys stay in their original order.
Sources The Rust Book: closures