Man, step away from Rust for six months and you feel like an idiot again.
Consider this sample:
// Move client to an option so we can _move_ the inner value
// on the first attempt to connect. All other attempts will fail.
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 {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Client already taken",
))
}
}
}))
.await?;
I borrowed this code for my own in-memory gRPC server (for unit-testing purposes) and it works great. I wanted to simplify this block - I've no need to guard against multiple clients (which is apparently what the comment indicates this is guarding against).
The first step - getting rid of the Option let check - works (though the compiler then requires you to type the Result instance):
let mut client = Some(client_duplex);
Ok(Endpoint::try_from("http://[::]:50051")
.map_err(|e| e.to_string())?
.connect_with_connector(service_fn(move |_: Uri| {
let client: Option<tokio::io::DuplexStream> = client.take();
async move {
Ok::<TokioIo<DuplexStream>, std::io::Error>(TokioIo::new(client.unwrap()))
}
}))
.await
The next step would be to get rid of the Option. Why do we need an Option here? There would seem to be no reason: We create the wrapper then take the value out.
So I tried this:
let mut _client = client_duplex;
Ok(Endpoint::try_from("http://[::]:50051")
.map_err(|e| e.to_string())?
.connect_with_connector(service_fn(move |_: Uri| {
let client = _client;
async move {
Ok::<TokioIo<DuplexStream>, std::io::Error>(TokioIo::new(client))
}
}))
.await
Now the compiler will not have it:
error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
|
89 | .connect_with_connector(service_fn(move |_: Uri| {
| ---------------------- ^^^^^^^^^^^^^ this closure implements `FnOnce`, not `FnMut`
| |
| the requirement to implement `FnMut` derives from here
90 | let client = _client;
| ------- closure is `FnOnce` because it moves the variable `_client` out of its environment
|
= note: required for `ServiceFn<{closure@services/src/./tests/eze_client_test.rs:89:52: 89:65}>` to implement `Service<Uri>`
Why does this resultant closure type only to FnOnce and not FnMut? The compiler tells us, helpfully, that "closure is FnOnce
because it moves the variable _client
out of its environment - but doesn't the previous version (that compiles) do the same thing?
(Secondarily, regarding "the requirement to implement FnMut
derives from here," how is that evident from the function signature of connect_with_connector
?)