Reputation: 41982
In the following example, I don't understand why the first example allows the closure to be Fn
, but the second only FnOnce
.
Example 1:
fn with_client(
client: BasicClient,
) -> impl Filter<Extract = (BasicClient,), Error = Infallible> + Clone {
warp::any().map(move || client.clone())
}
Example 2:
fn with_client(
client: BasicClient,
) -> impl Filter<Extract = (BasicClient,), Error = Infallible> + Clone {
let clone = client.clone();
warp::any().map(move || clone)
}
If I try and run the second example, I get a compiler warning about the closure being FnOnce
as it needs to move clone
into the closure. This is certainly the case, but I can't see how this differs from the first example, where we need to move client
into the closure?
Upvotes: 2
Views: 1042
Reputation: 149
Basically because clone()
is fn clone(&self) -> Self;
, only a reference of client
is moved into the closure. Therefore in example 1 the closure can be called multiple times. In example 2 the object clone
itself is moved into the closure. Since the object is only created once, the closure is FnOnce
.
If BasicClient
has another method, say fn not_clone(self);
, and example 1 calls warp::any().map(move || client.not_clone())
, the closure will also be a FnOnce
, because the method consumes self
. client
itself, not a reference, is moved into the closure. However if BasicClient
implements Copy
trait in addition to Clone
the compiler will makes a copy and moves the copy into the closure for each invocation of the closure. The not_clone
closure, as well as the closure in example 2, will be Fn
.
Upvotes: 1
Reputation: 70910
Let's desugar the closures manually.
struct Closure1 {
client: BasicClient,
}
impl FnOnce<()> for Closure1 {
type Output = BasicClient;
extern "rust-call" fn call_once(self, (): ()) -> BasicClient {
<Self as Fn<()>>::call(&self, ())
}
}
impl FnMut<()> for Closure1 {
extern "rust-call" fn call_mut(&mut self, (): ()) -> BasicClient {
<Self as Fn<()>>::call(&*self, ())
}
}
impl Fn<()> for Closure1 {
extern "rust-call" fn call(&self, (): ()) -> BasicClient {
<BasicClient as Clone>::clone(&self.client)
}
}
struct Closure2 {
client: BasicClient,
}
impl FnOnce<()> for Closure2 {
type Output = BasicClient;
extern "rust-call" fn call_once(self, (): ()) -> BasicClient {
self.client
}
}
fn with_client(
client: BasicClient,
) -> impl Filter<Extract = (BasicClient,), Error = Infallible> + Clone {
warp::any().map(Closure1 { client })
}
fn with_client(
client: BasicClient,
) -> impl Filter<Extract = (BasicClient,), Error = Infallible> + Clone {
let clone = <BaseClient as Clone>::clone(&clone);
warp::any().map(Closure1 { client: clone })
}
Like you see, the problem is not that we move client
into the closure. The problem is that we move it out of the closure. When you move out of something, you must have ownership of the data: thus, FnOnce
. The fact that you're creating the closure with a clone does not matter. But when you clone inside the closure, you only use shared references - and thus you don't need ownership, or even exclusive access - thus Fn
.
Upvotes: 2