richzilla
richzilla

Reputation: 41982

Understanding Fn / FnOnce closures

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

Answers (2)

fred xia
fred xia

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

Chayim Friedman
Chayim Friedman

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

Related Questions