【问题标题】:Understanding Fn / FnOnce closures了解 Fn / FnOnce 闭包
【发布时间】:2022-09-24 23:32:02
【问题描述】:

在下面的例子中,我不明白为什么第一个例子允许闭包是Fn,而第二个只有FnOnce

示例 1:

fn with_client(
    client: BasicClient,
) -> impl Filter<Extract = (BasicClient,), Error = Infallible> + Clone {
    warp::any().map(move || client.clone())
}

示例 2:

fn with_client(
    client: BasicClient,
) -> impl Filter<Extract = (BasicClient,), Error = Infallible> + Clone {
    let clone = client.clone();
    warp::any().map(move || clone)
}

如果我尝试运行第二个示例,我会收到关于闭包为 FnOnce 的编译器警告,因为它需要将 clone 移动到闭包中。确实是这样,但我看不出这与第一个示例有何不同,我们需要将client 移动到闭包中?

    标签: rust closures


    【解决方案1】:

    让我们手动对闭包进行脱糖。

    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 })
    }
    

    如你所见,问题不在于我们移动client进入关闭。问题是我们移动它在......之外关闭。当您搬出某物时,您必须拥有数据的所有权:因此,FnOnce。您使用克隆创建闭包的事实并不重要。但是当你克隆封闭内,您只使用共享引用-因此您不需要所有权,甚至不需要独占访问-因此Fn

    【讨论】:

      【解决方案2】:

      基本上因为clone()fn clone(&amp;self) -&gt; Self;,所以只有client 的引用被移动到闭包中。因此,在示例 1 中,可以多次调用闭包。在示例 2 中,对象 clone 本身被移动到闭包中。由于对象只创建一次,闭包是FnOnce

      如果BasicClient 有另一个方法,比如fn not_clone(self); ,并且示例1 调用warp::any().map(move || client.not_clone()),则闭包也将是FnOnce,因为该方法消耗了selfclient 本身,而不是引用,被移动到闭包中。但是,如果BasicClient 除了Clone 之外还实现了Copy trait,编译器将制作一个副本并将副本移动到闭包的每次调用中。 not_clone 闭包以及示例 2 中的闭包将是 Fn

      【讨论】:

        猜你喜欢
        • 2015-07-22
        • 1970-01-01
        • 2023-01-14
        • 1970-01-01
        • 2022-01-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多