Closures basics
You probably already know that a closure in rust is a function written with the following syntax:
let double_closure = |x| x * 2;
assert_eq!(4, double_closure(2));
Written as a regular function it looks like:
fn double_function(x: u32) -> u32 {
x * 2
}
assert_eq!(4, double_function(2));
Very similar. There is actually a small difference between the two, the double_function parameter and return type are u32.
On the other hand, because we did not specify any type in double_closure, the
default integer type has been picked, namely i32.
We can fix that like this:
let double_typed_closure = |x: u32| -> u32 { x * 2 };
assert_eq!(4, double_typed_closure(2));
assert_eq!(4, double_typed_closure(2u32));
// assert_eq!(4, double_typed_closure(2u16)); // This would be an error.
And for a classic example usage of closures, we can use the Option::map method:
assert_eq!(Some(4), Some(2).map(|x| x * 2));
assert_eq!(Some(4), Some(2).map(double_closure)); // double_closure from above
assert_eq!(Some(4), Some(2).map(double_function)); // Passing double_function works too!
So, it seems closures are just a shorter syntax for functions with type inference.
Capture
The main difference between closures and functions is that closures can capture variables from their environment while functions can't:
let hello = "Hello ";
let greeter_closure = |x| String::new() + hello + x;
assert_eq!("Hello world", greeter_closure("world"));
assert_eq!(
Some("Hello world".to_owned()),
Some("world").map(greeter_closure)
);
Notice how the hello variable is used within the body of the greeter_closure.
Let's try that with a function:
let hello = "Hello ";
fn greeter_function(x: &str) -> String {
String::new() + hello + x
}
error[E0434]: can't capture dynamic environment in a fn item
--> src/main.rs:7:25
|
7 | String::new() + hello + x
| ^^^^^
|
= help: use the `|| { ... }` closure form instead
This does not work and the compiler helpfully suggest to use a closure instead.
Capture by shared reference
In the greeter_closure example above, the hello variable was captured by
shared reference because the variable is only read.
As shown below, we can still use that variable after the closure declaration and usage:
let hello = "Hello ";
let greeter_closure = |x| String::new() + hello + x;
// We can still use the `hello` variable here
assert_eq!("Hello ", hello);
assert_eq!("Hello world", greeter_closure("world"));
// And here
assert_eq!("Hello ", hello);
Capture by mutable reference
It is also possible to capture by mutable reference so that the closure can alter the value of the captured variable. See this naive way to compute the sum of integers from 1 to 10:
let mut total = 0;
let add_mut_closure = |x| total += x;
// We can't access total here:
// assert_eq!(0, total);
// error[E0502]: cannot borrow `total` as immutable because it is also borrowed as mutable
(1..=10).for_each(add_mut_closure);
// But we can access total here, now that `add_mut_closure` is out of scope.
assert_eq!(55, total);
Capture by value
Finally, one can capture by value:
let last_word = "last word: ".to_owned();
let drop_closure = |sigh| {
let res = String::new() + &last_word + sigh;
drop(last_word); // Forcing the capture by value
res
};
// We can't access `last_word` here:
// assert_eq!("last word: ".to_owned(), last_word);
// error[E0382]: borrow of moved value: `last_word`
assert_eq!("last word: sigh!", drop_closure("sigh!"));
// We can't access `last_word` here either
// assert_eq!("last word: ".to_owned(), last_word);
// error[E0382]: borrow of moved value: `last_word`
// And we can't call drop_closure again
// assert_eq!("last word: sigh!", drop_closure("sigh!"));
// error[E0382]: use of moved value: `drop_closure`
FnOnce trait
In the previous example, notice the last error when trying to call drop_closure twice.
Here is the full error:
error[E0382]: use of moved value: `drop_closure`
--> src/main.rs:18:32
|
12 | assert_eq!("last word: sigh!", drop_closure("sigh!"));
| --------------------- `drop_closure` moved due to this call
...
18 | assert_eq!("last word: sigh!", drop_closure("sigh!"));
| ^^^^^^^^^^^^ value used here after move
|
note: closure cannot be invoked more than once because it moves the variable `last_word` out of its environment
--> src/main.rs:5:10
|
5 | drop(last_word);
| ^^^^^^^^^
note: this value implements `FnOnce`, which causes it to be moved when called
--> src/main.rs:12:32
|
12 | assert_eq!("last word: sigh!", drop_closure("sigh!"));
| ^^^^^^^^^^^^
The interesting note is:
note: this value implements `FnOnce`, which causes it to be moved when called
What is that FnOnce implementation the compiler is talking about?
It is a trait automatically implemented by the compiler on closures which state that the closure can be called at least once.
That trait is a bit special because it cannot be implemented manually in stable rust.
However, if we switch to unstable and enable some features, we can play with it and try to desugar how closures are actually implemented by the compiler.
Let's try to desugar the drop_closure above.
First, make sure to switch to the nightly channel and to enable
the following features (for example by putting them at the top of your main.rs):
#![feature(fn_traits)]
#![feature(unboxed_closures)]
Next, we need to define a struct having the captured variables as fields:
struct DropStruct {
last_word: String,
}
Simple enough, we are capturing only one variable so our struct has one field.
Now the FnOnce implementation:
impl FnOnce<(&str,)> for DropStruct {
type Output = String;
extern "rust-call" fn call_once(self, (sigh,): (&str,)) -> Self::Output {
let res = String::new() + &self.last_word + sigh;
drop(self.last_word);
res
}
}
That is some weird trait!
Let's go step by step.
impl FnOnce<(&str,)> means that we are implementing a closure which takes one parameter which is a &str.
If the closure took two arguments of type i32 and i64 we would have impl FnOnce<(i32, i64)>. (&str,) is the definition of a tuple of one element.
See the reference on tuple types for details.
for DropStruct should not be too surprising.
type Output = String specifies that our closure returns a String.
extern "rust-call" is some magic which I won't explain mostly because I don't know exactly why it is required.
The rest of the implementation should be self explanatory. We just took the content
of the closure and replaced last_word by self.last_word.
Let's try it:
let last_word = "last word: ".to_owned();
let drop_struct = DropStruct { last_word };
// We could call `call_once`:
// assert_eq!("last word: sigh!", drop_struct.call_once(("sigh!",)));
// But more simply, we can use the function call syntax:
assert_eq!("last word: sigh!", drop_struct("sigh!"));
// And we still can't call it twice
// assert_eq!("last word: sigh!", drop_struct("sigh!"));
// error[E0382]: use of moved value: `drop_struct`
FnMut trait
What about our add_mut_closure from before? We were able to call it
multiple times and even mutate the capture variables.
That kind of closure implements the FnMut trait.
Let's try desugar the following closure which push elements in a vector:
let mut v = vec![];
let push_closure = |x| v.push(x);
(1..=5).for_each(push_closure);
assert_eq!(vec![1, 2, 3, 4, 5], v);
First we need to define a struct:
struct PusherStruct<'a> {
v: &'a mut Vec<i32>,
}
Because we are capturing by reference, we need to introduce a lifetime.
Now the FnMut implementation:
impl<'a> FnMut<(i32,)> for PusherStruct<'a> {
extern "rust-call" fn call_mut(&mut self, (x,): (i32,)) -> Self::Output {
self.v.push(x)
}
}
It is very similar to the FnOnce trait except that the function is called call_mut instead of call_once and that it takes &mut self instead of self.
Let's try to compile that:
error[E0277]: expected a `FnOnce(i32)` closure, found `PusherStruct<'a>`
--> src/main.rs:8:5
|
8 | extern "rust-call" fn call_mut(&mut self, args: (i32,)) -> Self::Output {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an `FnOnce(i32)` closure, found `PusherStruct<'a>`
|
help: the trait `FnOnce(i32)` is not implemented for `PusherStruct<'a>`
Turns out we need to implement FnOnce too. Remember that FnOnce defines functions which can be called at least once.
In the example above, we called our closure 5 times, so it can definitely be called at least once.
Let's implement it:
impl<'a> FnOnce<(i32,)> for PusherStruct<'a> {
type Output = ();
extern "rust-call" fn call_once(mut self, args: (i32,)) -> Self::Output {
self.call_mut(args)
}
}
Our closure does not return anything so the Output is the unit.
As for the call_once implementation, we can just call call_mut to avoid repetition.
This should compile and we can now use it like so:
let mut v = vec![];
let pusher_struct = PusherStruct { v: &mut v };
(1..=5).for_each(pusher_struct);
assert_eq!(vec![1, 2, 3, 4, 5], v);
Fn trait
Finally, there is a third trait implemented by closures which can be called multiple times and don't need a mutable reference; the Fn trait.
To see that let's try to desugar the greeter_closure from before:
let hello = "Hello ";
let greeter_closure = |x| String::new() + hello + x;
assert_eq!("Hello world", greeter_closure("world"));
assert_eq!("Hello rust", greeter_closure("rust")); // Can be called multiple times
As usual, we need to define our struct:
struct GreeterStruct<'a> {
hello: &'a str,
}
Let's not make the same mistake as before, and remember to implement FnOnce and FnMut first. The same way an FnMut closures are also FnOnce because they can be called at least once. Fn closures are also FnMut because if given a mutable reference, they can still perform their work which does not mutate the reference.
impl<'a, 'b> FnOnce<(&'b str,)> for GreeterStruct<'a> {
type Output = String;
extern "rust-call" fn call_once(self, args: (&'b str,)) -> Self::Output {
self.call(args)
}
}
impl<'a, 'b> FnMut<(&'b str,)> for GreeterStruct<'a> {
extern "rust-call" fn call_mut(&mut self, args: (&'b str,)) -> Self::Output {
self.call(args)
}
}
This should be pretty straightforward. call_once and call_mut are just calling call which is defined in Fn:
impl<'a, 'b> Fn<(&'b str,)> for GreeterStruct<'b> {
extern "rust-call" fn call(&self, (x,): (&'b str,)) -> Self::Output {
String::new() + &self.hello + &x
}
}
And we can use it like this:
let hello = "Hello";
let greeter_struct = GreeterStruct {
hello,
};
assert_eq!("Hello world", greeter_struct("world"));
assert_eq!("Hello rust", greeter_struct("rust")); // Can be called multiple times
The move keyword
You may already know that one can add the move keyword in front of a closure to force the closure to take ownership of the capture variables even if the closure only need a reference to it.
For example:
let hello = "Hello ".to_owned();
let greeter_closure = move |x| String::new() + &hello + x;
// We can't access `hello` here
// assert_eq!("Hello ", hello);
// error[E0382]: borrow of moved value: `hello`
assert_eq!("Hello world", greeter_closure("world"));
// Nor here
// assert_eq!("Hello ", hello);
// error[E0382]: borrow of moved value: `hello`
In order to clearly understand what we can do depending on whether the closure needs a shared reference, a mutable reference or a value and if there is move keyword or not, let's introduce those small dummy functions:
fn by_ref(_data: &String) {}
fn by_mut(_data: &mut String) {}
fn by_value(_data: String) {}
Now, let's see what we can do with different combination of move / not move and by_ref / by_mut / by_value:
let data = "by_ref".to_owned();
let by_ref_closure = || by_ref(&data);
// Access data while the closure is still in scope
assert_eq!("by_ref", data);
// Call the closure once
by_ref_closure();
// Call the closure multiple times
by_ref_closure();
// Access data once the closure is out of scope
assert_eq!("by_ref", data);
Now with move:
let data = "move_by_ref".to_owned();
let move_by_ref_closure = move || by_ref(&data);
// Access data while the closure is still in scope
// assert_eq!("move_by_ref", data);
// error[E0382]: borrow of moved value: `data`
// Call the closure once
move_by_ref_closure();
// Call the closure multiple times
move_by_ref_closure();
// Access data once the closure is out of scope
// assert_eq!("move_by_ref", data);
// error[E0382]: borrow of moved value: `data`
This makes sense, since the closure took ownership of data we can't access it anymore from outside.
Similarly we can define the following closures:
let data = "by_mut".to_owned();
let by_mut_closure = || by_mut(&data);
let data = "move_by_mut".to_owned();
let move_by_mut_closure = move || by_mut(&data);
let data = "by_value".to_owned();
let by_value_closure = || by_value(&data);
let data = "move_by_value".to_owned();
let move_by_value_closure = move || by_value(&data);
I will let you play with them, here what you should see:
| by_ref | by_mut | by_value | move by_ref | move by_mut | move by_value | |
|---|---|---|---|---|---|---|
| Access when in scope | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Call once | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Call multiple times | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ |
| Access when out of scope | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
And the trait implemented by each closures:
| by_ref | by_mut | by_value | move by_ref | move by_mut | move by_value | |
|---|---|---|---|---|---|---|
| FnOnce | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| FnMut | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ |
| Fn | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
We can see that the move key word has no impact on the implemented trait. It only changes the capture to be from reference to value.
For example, the desugaring of by_ref_closure is:
struct ByRefStruct<'a> {
data: &'a String,
}
impl<'a> FnOnce<()> for ByRefStruct<'a> {
type Output = ();
extern "rust-call" fn call_once(self, args: ()) -> Self::Output {
self.call(args)
}
}
impl<'a> FnMut<()> for ByRefStruct<'a> {
extern "rust-call" fn call_mut(&mut self, args: ()) -> Self::Output {
self.call(args)
}
}
impl<'a> Fn<()> for ByRefStruct<'a> {
extern "rust-call" fn call(&self, (): ()) -> Self::Output {
by_ref(self.data)
}
}
whereas for move_by_ref_closure:
struct MoveByRefStruct {
data: String,
}
impl FnOnce<()> for MoveByRefStruct {
type Output = ();
extern "rust-call" fn call_once(self, args: ()) -> Self::Output {
self.call(args)
}
}
impl FnMut<()> for MoveByRefStruct {
extern "rust-call" fn call_mut(&mut self, args: ()) -> Self::Output {
self.call(args)
}
}
impl Fn<()> for MoveByRefStruct {
extern "rust-call" fn call(&self, (): ()) -> Self::Output {
by_ref(&self.data)
}
}
Notice how the data field changed from &'a String to String and the call to by_ref from self.data to &self.data eventhough in the closure forms we had by_ref(&data) in both cases.
So we now hopefully understand what the move keyword does but you might wonder why that can be useful? After all, the first table above shows that we only removed flexbility.
Spawning a thread:
let data = "by_ref".to_owned();
std::thread::spawn(|| by_ref(&data)).join().unwrap();
Without move, we get the following compiler error which helpfully suggest adding move:
error[E0373]: closure may outlive the current function, but it borrows `data`, which is owned by the current function
--> src/main.rs:9:20
|
9 | std::thread::spawn(|| by_ref(&data)).join().unwrap();
| ^^ ---- `data` is borrowed here
| |
| may outlive borrowed value `data`
|
note: function requires argument type to outlive `'static`
--> src/main.rs:9:1
|
9 | std::thread::spawn(|| by_ref(&data)).join().unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: to force the closure to take ownership of `data` (and any other referenced variables), use the `move` keyword
|
9 | std::thread::spawn(move || by_ref(&data)).join().unwrap();
| ++++
Creating a function returning a closure:
fn make_greeter(greeter: &'static str) -> impl Fn(&'static str) -> String {
move |name| format!("{greeter} {name}")
}
let hello_greeter = make_greeter("Hello");
let hi_greeter = make_greeter("Hi");
assert_eq!(hello_greeter("rust"), "Hello rust");
assert_eq!(hi_greeter("rust"), "Hi rust");
Here too we need move otherwise we get the same borrow checker error.
Async closures
AsyncFnOnce
By adding the async keyword before the closure, we can make it return a Future. For example, the following closure will sleep 1 second before returning it last word.
let last_word = "last word: ".to_owned();
let async_drop_closure = async |sigh| {
tokio::time::sleep(Duration::from_secs(1)).await;
let res = String::new() + &last_word + sigh;
drop(last_word);
res
};
// We can't access `last_word` here:
// assert_eq!("last word: ".to_owned(), last_word);
// error[E0382]: borrow of moved value: `last_word`
assert_eq!("last word: sigh!", async_drop_closure("sigh!").await);
// We can't access `last_word` here either
// assert_eq!("last word: ".to_owned(), last_word);
// error[E0382]: borrow of moved value: `last_word`
// And we can't call async_drop_closure again
// assert_eq!("last word: sigh!", async_drop_closure("sigh!").await);
// error[E0382]: use of moved value: `async_drop_closure`
So, very similar to our previous FnOnce example. However, async closures implement AsyncFnOnce instead.
Below is the desugaring however, I won't explain in details the mechanism because it also require explaining async functions desugaring with state machines. If the topic interest you, I recommand two articles:
- Futures in Rust: An In-Depth Technical Analysis by Murat Genc.
- Understanding Rust futures by going way too deep by Amos (fasterthanlime).
Note that you need to enable #![feature(async_fn_traits)] in nightly.
struct AsyncDropStruct {
last_word: String,
}
struct AsyncDropStructFuture<'a> {
last_word: Option<String>,
sigh: &'a str,
state: AsyncDropStructFutureState,
}
enum AsyncDropStructFutureState {
Initial,
SleepAwaitPoint {
future: Pin<Box<dyn Future<Output = ()>>>,
},
}
impl<'a> Future for AsyncDropStructFuture<'a> {
type Output = String;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.state {
AsyncDropStructFutureState::Initial => {
let future = Box::pin(tokio::time::sleep(Duration::from_secs(1)));
self.state = AsyncDropStructFutureState::SleepAwaitPoint { future };
self.poll(cx)
}
AsyncDropStructFutureState::SleepAwaitPoint { ref mut future } => {
match future.as_mut().poll(cx) {
Poll::Ready(()) => {
let last_word = self
.last_word
.take()
.expect("`last_word` should have a value at this point");
let res = String::new() + &last_word + self.sigh;
drop(last_word);
Poll::Ready(res)
}
Poll::Pending => Poll::Pending,
}
}
}
}
}
impl<'a> AsyncFnOnce<(&'a str,)> for AsyncDropStruct {
type CallOnceFuture = AsyncDropStructFuture<'a>;
type Output = String;
extern "rust-call" fn async_call_once(
self,
(sigh,): (&'a str,),
) -> Self::CallOnceFuture {
AsyncDropStructFuture {
last_word: Some(self.last_word),
sigh,
state: AsyncDropStructFutureState::Initial,
}
}
}
let async_drop_struct = AsyncDropStruct {
last_word: "last word: ".to_owned(),
};
assert_eq!("last word: sigh!", async_drop_struct("sigh!").await);
AsyncFnMut
In the same way we had FnMut for non-async closures, we have AsyncFnMut for async ones.
For example:
let mut v = vec![];
let mut async_pusher_closure = async |x| {
tokio::time::sleep(Duration::from_secs(1)).await;
v.push(x);
};
async_pusher_closure(5).await;
async_pusher_closure(2).await;
assert_eq!(vec![5, 2], v);
With it desugaring:
struct AsyncPusherStruct<'a> {
v: &'a mut Vec<i32>,
}
struct AsyncPusherStructFuture<'a> {
v: &'a mut Vec<i32>,
arg: i32,
state: AsyncPusherStructFutureState,
}
enum AsyncPusherStructFutureState {
Initial,
SleepAwaitPoint {
future: Pin<Box<dyn Future<Output = ()>>>,
},
}
impl<'a> Future for AsyncPusherStructFuture<'a> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match &mut self.state {
AsyncPusherStructFutureState::Initial => {
let future = Box::pin(tokio::time::sleep(Duration::from_secs(1)));
self.state = AsyncPusherStructFutureState::SleepAwaitPoint { future };
self.poll(cx)
}
AsyncPusherStructFutureState::SleepAwaitPoint { future } => {
match future.as_mut().poll(cx) {
Poll::Ready(()) => {
let arg = self.arg;
self.v.push(arg);
Poll::Ready(())
}
Poll::Pending => Poll::Pending,
}
}
}
}
}
impl<'a> AsyncFnOnce<(i32,)> for AsyncPusherStruct<'a> {
type CallOnceFuture = AsyncPusherStructFuture<'a>;
type Output = ();
extern "rust-call" fn async_call_once(self, args: (i32,)) -> Self::CallOnceFuture {
AsyncPusherStructFuture {
v: self.v,
arg: args.0,
state: AsyncPusherStructFutureState::Initial,
}
}
}
impl<'a> AsyncFnMut<(i32,)> for AsyncPusherStruct<'a> {
type CallRefFuture<'b>
= AsyncPusherStructFuture<'b>
where
Self: 'b;
extern "rust-call" fn async_call_mut(&mut self, args: (i32,)) -> Self::CallRefFuture<'_> {
AsyncPusherStructFuture {
v: self.v,
arg: args.0,
state: AsyncPusherStructFutureState::Initial,
}
}
}
let mut v = vec![];
let mut async_adder_struct = AsyncPusherStruct { v: &mut v };
async_adder_struct(5).await;
async_adder_struct(4).await;
assert_eq!(vec![5, 4], v);
Notice how in the same way FnMut requires an FnOnce implementation, AsyncFnMut requires an AsyncFnOnce implementation.
AsyncFn
Finally, we also have AsyncFn closures:
let last_word = "last word: ".to_owned();
let async_greeter_closure = async |sigh| {
tokio::time::sleep(Duration::from_secs(1)).await;
String::new() + &last_word + sigh
};
// We can access `last_word` here:
assert_eq!("last word: ".to_owned(), last_word);
assert_eq!("last word: sigh!", async_greeter_closure("sigh!").await);
// We can call async_greeter_closure again
assert_eq!("last word: sigh!", async_greeter_closure("sigh!").await);
// We can access `last_word` here too
assert_eq!("last word: ".to_owned(), last_word);
And the desugaring:
struct AsyncGreeterStruct<'a> {
last_word: &'a String,
}
struct AsyncGreeterStructFuture<'a, 'b> {
last_word: &'a String,
sigh: &'b str,
state: AsyncGreeterStructFutureState,
}
enum AsyncGreeterStructFutureState {
Initial,
SleepAwaitPoint {
future: Pin<Box<dyn Future<Output = ()>>>,
},
}
impl<'a, 'b> Future for AsyncGreeterStructFuture<'a, 'b> {
type Output = String;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.state {
AsyncGreeterStructFutureState::Initial => {
let future = Box::pin(tokio::time::sleep(Duration::from_secs(1)));
self.state = AsyncGreeterStructFutureState::SleepAwaitPoint { future };
self.poll(cx)
}
AsyncGreeterStructFutureState::SleepAwaitPoint { ref mut future } => {
match future.as_mut().poll(cx) {
Poll::Ready(()) => {
let res = String::new() + &self.last_word + self.sigh;
Poll::Ready(res)
}
Poll::Pending => Poll::Pending,
}
}
}
}
}
impl<'a, 'b> AsyncFnOnce<(&'b str,)> for AsyncGreeterStruct<'a> {
type CallOnceFuture = AsyncGreeterStructFuture<'a, 'b>;
type Output = String;
extern "rust-call" fn async_call_once(
self,
(sigh,): (&'b str,),
) -> Self::CallOnceFuture {
AsyncGreeterStructFuture {
last_word: self.last_word,
sigh,
state: AsyncGreeterStructFutureState::Initial,
}
}
}
impl<'a, 'b> AsyncFnMut<(&'b str,)> for AsyncGreeterStruct<'a> {
type CallRefFuture<'c>
= AsyncGreeterStructFuture<'a, 'b>
where
Self: 'c;
extern "rust-call" fn async_call_mut(
&mut self,
(sigh,): (&'b str,),
) -> Self::CallRefFuture<'_> {
AsyncGreeterStructFuture {
last_word: self.last_word,
sigh,
state: AsyncGreeterStructFutureState::Initial,
}
}
}
impl<'a, 'b> AsyncFn<(&'b str,)> for AsyncGreeterStruct<'a> {
extern "rust-call" fn async_call(&self, (sigh,): (&'b str,)) -> Self::CallRefFuture<'_> {
AsyncGreeterStructFuture {
last_word: self.last_word,
sigh,
state: AsyncGreeterStructFutureState::Initial,
}
}
}
let last_word = "last word: ".to_owned();
let async_greeter_struct = AsyncGreeterStruct {
last_word: &last_word,
};
assert_eq!("last word: sigh!", async_greeter_struct("sigh!").await);
assert_eq!("last word: sigh!", async_greeter_struct("sigh!").await);
Last word
Sigh
That ended up being a long article and there are still things I didn't mention. If you want to read more on the subject I recommend:
- The closure chapter in the rust book
- The closure chapter in the rust reference
- The article from the baby steps blog about adding an explicit capture clause
That last article is actually the one which made me realized that there was a lot I didn't know about closures and made me write this article. I hope you learned a few things as well.