如何克隆闭包,使它们的类型相同?

3

我有一个结构体,看起来像这样:

pub struct MyStruct<F>
where
    F: Fn(usize) -> f64,
{
    field: usize,
    mapper: F,
    // fields omitted
}

我该如何为这个结构体实现Clone?一种复制函数体的方法是:
let mapper = |x| (mystruct.mapper)(x);

但这会导致mapper的类型与mystruct.mapper不同。

playground


I是什么?这个无法编译。但是,如果F没有返回任何内容,为什么不只使用#[derive(Clone)]呢? - ljedrz
@ljedrz 啊!抱歉,我已经修复了...请查看我现在添加的playpen链接。 - John
4个回答

5

从Rust 1.26.0版本开始,如果所有被捕获的变量都实现了CopyClone,那么闭包也会实现这两个trait:

#[derive(Clone)]
pub struct MyStruct<F>
where
    F: Fn(usize) -> f64,
{
    field: usize,
    mapper: F,
}

fn main() {
    let f = MyStruct {
        field: 34,
        mapper: |x| x as f64,
    };
    let g = f.clone();
    println!("{}", (g.mapper)(3));
}

3
你无法克隆闭包。唯一可以实现闭包克隆的人是编译器...但它不会这么做。所以,你有点被卡住了。
不过,有一种方法可以绕开这个问题:如果你有一个没有捕获变量的闭包,你可以通过不安全的代码强制复制。话虽如此,在那种情况下,更简单的方法是接受一个 "fn(usize) -> f64" 作为代替,因为它们没有捕获环境(任何零大小的闭包都可以重新编写为函数),并且是可复制的。

谢谢!现在我想了想,我不需要在闭包中捕获变量,虽然使用闭包会更好(更易读)。你有没有想过为什么编译器没有实现闭包的Clone方法? - John
1
@John 我认为问题的一部分在于,为了实现Clone,编译器必须知道Clone是什么。历史上,它并没有这样做。此外,其他特性呢?闭包也应该是Copy吗?Eq呢?你在哪里划线?我不认为有人真正确定应该在这里做什么。 - DK.
2
fn(usize) -> f64(一个“fn类型”)不是零大小,它相当于一个函数指针。fn(usize) -> f64 {::some::specific::function}(一个“fn项类型”)是零大小的,但你不能写出那个类型,所以唯一的方法是通过接受一个通用类型 F: Fn(u64) -> f64 + Copy 来利用它(即,在问题的代码中添加一个“Copy”约束)。 - user395760
@delnan 这是否意味着“fn类型”无法内联,但将“fn”作为F:Fn(..) - > .. + Copy可能会使内联成为可能? - John
1
@John 原则上,如果您在某个地方有一个常量函数指针,编译器可以基于该常量的值进行优化(与任何其他值一样)。但实际上,您可能会从不同的调用站点传递不同的函数指针,因此通过fn指定的大多数回调确实不太可能被内联。 - user395760
@delnan 抱歉,我指的是捕获环境的大小(它们没有)。我已经调整了答案。 - DK.

3

您可以使用Rc(或Arc!)来获取同一不可克隆值的多个句柄。与Fn(通过共享引用调用)闭包很好配合使用。

pub struct MyStruct<F> where F: Fn(usize) -> f64 {
    field: usize,
    mapper: Rc<F>,
    // fields omitted
}

impl<F> Clone for MyStruct<F>
    where F: Fn(usize) -> f64,
{
    fn clone(&self) -> Self {
        MyStruct {
            field: self.field,
            mapper: self.mapper.clone(),
           ...
        }
    }
}

请记住,#[derive(Clone)]是一个非常有用的克隆模板,但在某些情况下它并不总是能够做出正确的事情;这就是其中之一。


除了参考计数部分,这是否与“原始”闭包一样好?函数体会被内联吗?(我假设在“原始”闭包中它会被内联,但我不确定必要的条件是什么。) - John
它在指针后面,所以有那个间接性(但仅此而已)。我想它可以很好地内联,但你应该进行验证。 - bluss

0

您可以使用特质对象来实现对您的结构体进行克隆

use std::rc::Rc;

#[derive(Clone)]
pub struct MyStructRef<'f>  {
    field: usize,
    mapper: &'f Fn(usize) -> f64,
}


#[derive(Clone)]
pub struct MyStructRc  {
    field: usize,
    mapper: Rc<Fn(usize) -> f64>,
}

fn main() {
    //ref
    let closure = |x| x as f64;
    let f = MyStructRef { field: 34, mapper: &closure };
    let g = f.clone();
    println!("{}", (f.mapper)(3));
    println!("{}", (g.mapper)(3));

    //Rc
    let rcf = MyStructRc { field: 34, mapper: Rc::new(|x| x as f64 * 2.0) };
    let rcg = rcf.clone();
    println!("{}", (rcf.mapper)(3));
    println!("{}", (rcg.mapper)(3));    
}

是的,尽管这是一个有效的选项,但我想避免动态分派。我正在编写一个模拟器,所以我希望给编译器提供内联闭包的所有可能性... - John
我认为在这种情况下,内联是不可能的。虽然普通函数调用(如@DK.所建议的)比特质对象的调用更便宜。 - aSpex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接