Closures
Closure Syntax
Closures are anonymous functions you can define inline, often right where you need to use them. Here is a example of a short, inline closure with a simple expression as its body:
/// Finds all strings in a list that contain the '@' character. fn find_emails(list: Vec<String>) -> Vec<String> { list.into_iter() // `filter` takes a predicate, a closure that returns a `bool`. .filter(|s| s.contains('@')) // Closure with a simple expression as its body. Note the `| |`. .collect() } fn main() { for s in find_emails(vec![ String::from("example"), String::from("example@example.com"), ]) { println!("{}", s); } }
Closures can capture variables from their surrounding scope
- by reference:
&T
, - by mutable reference:
&mut T
, or - by value:
T
.
They preferentially capture variables by reference and only go lower when required.
fn main() { let outer_variable = 10; // Define a simple closure that takes a parameter and captures `outer_variable`: let my_closure = |x| { println!("Parameter x = {}", x); println!("Outer variable = {}", outer_variable); // Captured variable. x + outer_variable // Optional return value. }; // The body can be an expression or block between `{` and `}`. // Call the closure: let result = my_closure(5); println!("Result from closure: {}", result); // Output: 15. }
Force Closure Capture by Value
A closure can be forced to capture its environment by copying or moving values by prefixing it with the move
keyword.
This is often used to ensure that the closure's lifetime is 'static
and common when creating a new thread.
use std::thread; /// Demonstrates the use of the `move` keyword with closures. fn main() { let mut data = vec![1, 2, 3]; println!("Before defining closure: {:?}", data); // `list` is still available here. // `move` forces the closure to take ownership of the values it uses. // Without `move`, this would be an error because the closure might outlive // `main`, making the reference `&data` invalid. let handle = thread::spawn(move || { // `move` forces the closure to take ownership of `data`. println!("Data inside thread: {:?}", data); data.push(4); }); // println!("{:?}", data); // Error: `data` was moved into the closure. handle.join().unwrap(); }
Annotate Closure Types
Closures can often infer parameter and return-value types, but you can add them manually:
use std::thread; use std::time::Duration; /// Demonstrates closures with type annotations. fn main() { // Closures can use type annotations, as shown below. // Multiple statements can be enclosed in a block. let expensive_closure = |num: u64| -> u64 { println!("Calculating..."); thread::sleep(Duration::from_secs(num)); num }; println!("Return value: {}", expensive_closure(1)); }
Use Closures as Function Arguments
Closures are frequently used as arguments to functions, especially higher-order functions like those found on iterators (map
, filter
, fold
, etc.).
A closure automatically implements one of three special traits:
std::ops::Fn
⮳ if the closure uses the captured value by reference (&T
),std::ops::FnMut
⮳ if the closure uses the captured value by mutable reference (&mut T
),std::ops::FnOnce
⮳ if the closure uses the captured value by value (T
) e.g. moves it.
When defining a function that accepts a closure, you use the Fn
, FnMut
, or FnOnce
traits as bounds:
// This function demonstrates how to accept a closure as an input parameter. // The `F` in `fn apply<F>(f: F)` is a generic type parameter, allowing // the function to accept any type that satisfies the specified trait bounds. fn apply<F>(f: F) where F: FnOnce(), // The closure `f` takes no input and returns nothing. { f(); } // This function demonstrates how to accept a closure that takes an input // and returns a value. fn apply_to_3<F>(f: F) -> i32 where // The closure takes an `i32` and returns an `i32`. // `Fn` closures can only take immutable references to captured variables // or don't capture anything at all. F: Fn(i32) -> i32, { f(3) } fn main() { apply(|| println!("Applied")); let var = 39; println!("`apply_to_3` result: {}", apply_to_3(|x| x + var)); }
Related Topics
- Functions.
- Rust Patterns.
- Functional Programming.