如何创建一个实现Fn trait并可克隆为不同对象的Trait对象?

5
如果我需要一个不可拷贝的类型擦除(动态类型)可调用对象,那么
Box<dyn Fn(i32) -> ()>

如果我需要一个引用计数的类型擦除可调用对象,那么(取决于是否需要线程安全)
Rc<dyn Fn(i32) -> ()>
Arc<dyn Fn(i32) -> ()>

然而,在这里,所有的副本都指向同一个底层内存 - 它们不是独立的。

如果我想要独立的可调用对象,该怎么办?Box<T> 已经在 T 实现了 Clone ,但是 Fn 没有实现 Clone,所以这个方法并不适用。做一些像这样的事情:

Box<dyn Fn(i32) -> () + Clone>

出现错误:

error[E0225]: only auto traits can be used as additional traits in a trait object
 --> src/main.rs:7:35
  |
7 | fn foo(f: Box<dyn Fn(i32) -> () + Clone>) {
  |                   -------------   ^^^^^ additional non-auto trait
  |                   |
  |                   first non-auto trait
  |
  = help: consider creating a new trait with all of these as super-traits and using that trait here instead: `trait NewTrait: Fn<(i32,)> + Clone {}`
  = note: auto-traits like `Send` and `Sync` are traits that have special properties; for more information on them, visit <https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits>

错误信息中的建议由于 Fn 拼写错误而无法工作,但这个方法可以:
trait CopyableFn: Fn(i32) -> () + Clone {}
Box<dyn CopyableFn>

仅仅只有这个还不行,因为:

error[E0038]: the trait `CopyableFn` cannot be made into an object
 --> src/main.rs:7:11
  |
5 | trait CopyableFn: Fn(i32) -> () + Clone {}
  |       ----------                  ----- ...because it requires `Self: Sized`
  |       |
  |       this trait cannot be made into an object...
6 | 
7 | fn foo(f: Box<dyn CopyableFn>) {
  |           ^^^^^^^^^^^^^^^^^^^ the trait `CopyableFn` cannot be made into an object

有没有一种创建可克隆的类型对象Fn并使得副本是不同的方法?

5
这实际上是 如何克隆存储了 trait 对象的结构体? 的重复问题。问题在于将 Clone 添加为 CopyableFn 的超特质会使其不再是对象安全的(dyn CopyableFn 就无效了)。那个答案中解释的解决方案 (clone_box) 将可以解决你的问题。 - Ibraheem Ahmed
2
但是Fn没有实现Clone吗? [你确定吗?] - Shepmaster
4
这是我的示例,更新了使用 Fn。如果可以,请问这对你有用吗?如果是的话,我认为应将其标记为重复。 - Ibraheem Ahmed
显示剩余3条评论
1个回答

6

不要将CloneableFn作为Clone的超级trait,而是实现一个clone_box方法将其克隆到一个盒子里:

trait CloneableFn: Fn(i32) -> () {
    fn clone_box<'a>(&self) -> Box<dyn 'a + CloneableFn>
    where
        Self: 'a;
}

由于像 dyn CloneableFn 这样的未定大小类型无法被克隆(Clone 需要 Sized),因此在这里将 Clone 作为超级特征几乎没有意义。然而,将 Fn(i32) -> () 作为超级特征可以使函数像正常函数一样被调用。

然后,对于所有实现了 Fn(i32) -> ()Clone 的类型,都可以实现 CloneableFn

impl<F> CloneableFn for F
where
    F: Fn(i32) -> () + Clone,
{
    fn clone_box<'a>(&self) -> Box<dyn 'a + CloneableFn>
    where
        Self: 'a,
    {
        Box::new(self.clone())
    }
}

最后,Box<dyn CloneableFn> 不会自动实现 Clone,因为 dyn CloneableFn 没有实现,所以我们需要自己实现:

impl<'a> Clone for Box<dyn 'a + CloneableFn> {
    fn clone(&self) -> Self {
        (**self).clone_box()
    }
}

有了这个,你现在可以克隆 Box<dyn CloneableFn> 并将其作为常规函数调用:

// let closure borrow some shared state
use std::sync::atomic::{AtomicI32, Ordering};
let x = AtomicI32::new(0);

let f = |n| {
    println!("x = {}", x.fetch_add(n, Ordering::Relaxed));
};

let f: Box<dyn CloneableFn> = Box::new(f);
let g = f.clone();

f(3);
g(5);
f(7);

这里有一个完整的playground示例

这与如何克隆存储盒式trait对象的结构体?相关,但在那种情况下,目标trait (Animal)可以被改变成具有超trait,而在这种情况下不可能(因为目标trait是Fn(i32) -> ())。从某种意义上说,这是相反的方法:添加一个trait,其目标是一个超trait,而不是将超trait添加到目标中。


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