Rust Closure Variable Capture
(Jin Qing’s Column, Jan., 2025)
In tonic\examples\src\mock\mock.rs:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
...
let mut client = Some(client);
let channel = Endpoint::try_from("http://[::]:50051")?
.connect_with_connector(service_fn(move |_: Uri| {
let client = client.take();
async move {
if let Some(client) = client {
Ok(TokioIo::new(client))
} else {
...
}
}))
.await?;
...
Changing serivce_fn from:
service_fn(move |_: Uri| {
let client = client.take();
async move {
if let Some(client) = client {
Ok(TokioIo::new(client))
} else {
...
}
})
moving client.take() into async block:
service_fn(move |_: Uri| async move {
let client = client.take();
if let Some(client) = client {
Ok(TokioIo::new(client))
} else {
...
})
produces build error:
error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
--> examples\src\mock\mock.rs:35:44
|
35 | .connect_with_connector(service_fn(move |_: Uri| async move {
| ---------------------- ^^^^^^^^^^^^^ this closure implements `FnOnce`, not `FnMut`
| |
| the requirement to implement `FnMut` derives from here
36 | let client = client.take();
| ------ closure is `FnOnce` because it moves the variable `client` out of its environment
|
= note: required for `tower::util::ServiceFn<{closure@examples\src\mock\mock.rs:35:44: 35:57}>` to implement `tower::Service<tonic::transport::Uri>`
Rewrite to make the error more clear:
service_fn(move |_: Uri| {
async move {
let client = client.take();
if let Some(client) = client {
Ok(TokioIo::new(client))
} else {
...
}
})
There are 2 captures in the closure: variable client is captured by the closure and then is captured again by the inner async block.
In the FnMut, we should not move the captured variable. But in this case, client is moved into the inner async block.
We must clone the variable client, or take the inner value of the client, to be moved into the inner async block.