rust 如何创建一个实现Fn的trait对象,并且可以克隆到不同的对象?

x8diyxa7  于 2023-04-06  发布在  其他
关注(0)|答案(1)|浏览(140)

此问题在此处已有答案

How to clone a struct storing a boxed trait object?(3个答案)
两年前关闭了。
如果我想要一个不可复制的类型-擦除(动态类型)可调用,那就是

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

如果我想要一个引用计数类型擦除的可调用对象,那就是(取决于我是否需要线程安全)

Rc<dyn Fn(i32) -> ()>
Arc<dyn Fn(i32) -> ()>

但在这里,这些副本都指向同一个潜在的记忆--它们并不独特。
如果我想要 distinct 可调用对象,我该怎么做呢?Box<T>已经实现了Clone,而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创建一个可克隆的类型对象,使副本是不同的?

gj3fmq9x

gj3fmq9x1#

与其让CloneableFn成为Clone的supertrait,不如实现一个clone_box方法,将其克隆到一个盒子中:

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

由于像dyn CloneableFn这样的未调整大小的类型不能被克隆(Clone需要Sized),所以在这里没有理由将Clone作为超trait。然而,将Fn(i32) -> ()作为超trait允许正常调用函数。
然后CloneableFn可以为所有同时实现Fn(i32) -> ()Clone的类型实现:

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

Here's a full playground example
这与How to clone a struct storing a boxed trait object?相关,但在这种情况下,目标trait(Animal)可以被更改为具有超trait,而在这种情况下,这是不可能的(因为目标trait是Fn(i32) -> ())。在某种程度上,这是相反的方法:添加所述目标是其超特征的特征,而不是将超特征添加到所述目标。

相关问题