Closures
In Rust, closures are akin to "anonymous functions" in other programming languages. You can define a closure and assign it to a Rust variable. After you've defined the variable pointing to a closure, you can invoke that function later on.
Just like regular functions, closures support input arguments and return types.
The syntax for defining a closure is somewhat different to defining a regular Rust function. The key differences are that:
- Closure input arguments are surrounded by the pipe character
| |
, instead of parentheses - Closures don't require the use of curly braces to surround the function body
- Closure return types don't need to be declared
Basic Closure
Here's a basic version of a closure. The empty pipe characters mean that there are no input arguments defined. The function body only has a single statement, and doesn't require curly braces surrounding it.
#![allow(unused)] fn main() { let myfn = || println!("Hello from Rust"); // Define the closure myfn(); // Call the closure }
Input Arguments
If you want to define input arguments on a closure, add them between the pipe characters.
#![allow(unused)] fn main() { let fn_full_name = | first_name: String, last_name: String | println!("{} {}", first_name, last_name); fn_full_name("Trevor".to_string(), "Sullivan".to_string()); }
Closure Return Values
If you need to return a value from a closure, you don't need to specify the return
keyword.
The following example will return the result from the format!
macro.
#![allow(unused)] fn main() { let fn_full_name = | first_name: String, last_name: String | format!("{} {}", first_name, last_name); let full_name = fn_full_name("Trevor".to_string(), "Sullivan".to_string()); println!("{full_name}"); }
Multi-line Closure Body
While the previous closure examples only contain a single statement, Rust closures can support complex bodies as well. Simply use curly braces to denote the closure body, just like you would with functions.
#![allow(unused)] fn main() { let fn_full_name_lower = | first_name: String, last_name: String | { let fname = first_name.to_lowercase(); let lname = last_name.to_lowercase(); return format!("{} {}", fname, lname); }; let full_name_lower = fn_full_name_lower("Trevor".to_string(), "Sullivan".to_string()); println!("{full_name_lower}"); }
Capture Environment
Rust closures capture the variables defined in the same scope as the closure definition. This means that variables defined outside the closure can be referenced inside the closure.
Consider the following example.
#![allow(unused)] fn main() { let first_name = "Trevor".to_string(); let last_name = "Sullivan".to_string(); let fn_full_name_lower = || { let fname = first_name.to_lowercase(); let lname = last_name.to_lowercase(); return format!("{} {}", fname, lname); }; let full_name_lower = fn_full_name_lower(); println!("{full_name_lower}"); }
Rather than passing the first_name
and last_name
as input arguments, we can define them in the same scope as the closure.
When we call the function, the values of the variables are accessible inside the closure body.
Using the Move Keyword
If you don't need to access a variable after the closure is defined, you can use the move
keyword to move ownership of the variable into the closure itself.
If you try the following example, you'll receive an error message from the Rust compiler saying "borrow of moved value."
That's because ownership of the first_name
variable was moved into the closure, and after the closure's scope is dropped, the first_name
variable is no longer valid.
#![allow(unused)] fn main() { let first_name = "Trevor".to_string(); let last_name = "Sullivan".to_string(); let fn_full_name_lower = move || { let fname = first_name.to_lowercase(); let lname = last_name.to_lowercase(); return format!("{} {}", fname, lname); }; let full_name_lower = fn_full_name_lower(); println!("{full_name_lower}"); println!("{first_name}"); }