首页 > 解决方案 > 如何创建实现 Fn 并可以克隆到不同对象的特征对象?

问题描述

如果我想要一个不可复制的类型擦除(动态类型)可调用,那就是

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

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

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

但是在这里,所有副本都引用相同的底层内存——它们并没有区别。

如果我想要不同的可调用对象,我该怎么做?在 implements时Box<T>已经实现,但没有实现,所以这里不适用。做类似的事情:CloneTCloneFnClone

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可克隆的类型对象,以便副本是不同的?

标签: rust

解决方案


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

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

由于dyn CloneableFn无法克隆(Clonerequires Sized)之类的未调整大小的类型,因此这里没有理由将其Clone作为超特征。但是,拥有Fn(i32) -> ()作为超特征可以正常调用函数。

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

这是一个完整的游乐场示例

这与如何克隆存储盒装特征对象的结构有关?,但在这种情况下,目标特征 ( Animal) 可以更改为具有超特征,而在这种情况下,这是不可能的(因为目标特征是Fn(i32) -> ())。在某种程度上,这是相反的方法:添加目标是超特征的特征,而不是向目标添加超特征。


推荐阅读