Closure Functions in Rust
Closures are one of Rust's functional programming-esque features that can create anonymous functions. Closures, like functions, execute blocks of logic within. They are fundamentally different in how they operate and handle ownership.
They may even be used as a return type, as shown in examples such as unwrap_or_else()
:
let some_result: i32 = dangerous_value().unwrap_or_else(|| 42);
The two vertical pipes within unwrap_or_else
signify that it is a closure, and 42 is the returned
value. Closures can be used in generics using the Fn
trait or even as a parameter for an actual
function.
Defining & Using Closures
Defining closures is similar to defining a variable. It utilizes two vertical pipes to signify any parameters. A return type can also be specified as part of the closure. Closures are called the same way a function is called:
fn main() {
let name = "Bob";
// Closure that captures `name` and returns a new string with "Hello, " prepended
let greet_closure = |name: &str| -> String {
format!("Hello, {}!", name)
};
// It is called the same way a function is
let greeting = greet_closure(name);
println!("{}", greeting); // Prints "Hello, Bob!"
}
Type Inference
Where functions require explicit type declarations for their parameter and return types, closures don't have this requirement. They are able to infer both types, depending on how one uses it:
This also can shorten the closure into one line, as the curly brackets are omitted, and the return type is directly specified. If this was a multi-line closure, then curly brackets would be required.
let name = "Bob";
let greet_closure = |name| format!("Hello, {}!", name);
// It is called the same way a function is. The types are inferred here!
// `name` is of type &str, meaning it now expects it from thereon.
let greeting = greet_closure(name);
println!("{}", greeting); // Prints "Hello, Bob!"
// This will fail!
let greeting_two = greet_closure(123);
18 | let greeting = greet_closure(123);
| ------------- ^^^ expected `&str`, found integer
| |
| arguments to this function are incorrect
Capturing Environments
Unlike functions, closures can capture their environment. A closure can utilize local variables within a scope that the closure is defined:
fn main() {
let name = "Bob";
// This closure has no arguments now but can use the `name`
// variable defined in the main scope.
let greet_closure_that_captures = || format!("Hello Captured, {}!", name);
// It is called the same way a function is
let greeting_two = greet_closure_that_captures();
println!("{}", greeting); /
}
Try it yourself!
What's going on here?
The code above showcases two examples of closures in use. The first example accepts a parameter, of which its type is inferred. The second example removes the parameter and instead captures a variable outside of the closure but within the same scope.