特征对象如何将具有泛型方法的特征作为参数?

Vla*_*lad 1 generics abstraction traits rust trait-objects

所以特征对象不能有泛型方法——看起来不错。但在这种语言中,使用抽象机制的唯一方法是通过泛型和特征对象。这意味着对于每个特征,我必须事先决定它是否可以用作对象,并在各处使用 dyn 而不是 impl。并且它内部的所有特征都必须以相同的方式来支持这一点。这种感觉非常难看。你能提出什么建议或者告诉我为什么这样设计吗?

fn main() {}

// some abstracted thing
trait Required {
    fn f(&mut self, simple: i32);
}

// this trait doesn't know that it's going to be used by DynTrait
// it just takes Required as an argument
// nothing special
trait UsedByDyn {
    // this generic method doesn't allow this trait to be dyn itself
    // no dyn here: we don't know about DynTrait in this scope
    fn f(&mut self, another: impl Required);
}

// this trait needs to use UsedByDyn as a function argument
trait DynTrait {
    // since UsedByDyn uses generic methods it can't be dyn itself
    // the trait `UsedByDyn` cannot be made into an object
    //fn f(&mut self, used: Box<dyn UsedByDyn>);

    // we can't use UsedByDyn without dyn either otherwise Holder can't use us as dyn
    // the trait `DynTrait` cannot be made into an object
    // fn f(&mut self, used: impl UsedByDyn);

    // how to use UsedByDyn here?
}

struct Holder {
    CanBeDyn: Box<dyn DynTrait>,
}
Run Code Online (Sandbox Code Playgroud)

use*_*342 7

这意味着对于每个特征,我必须事先决定它是否可以用作对象,并在各处使用 dyn 而不是 impl。

您可以这样做,但幸运的是这不是唯一的选择。

您还可以像平常一样编写您的特征,在适当的情况下使用泛型。如果/当您需要特征对象时,请定义一个您在本地使用的新的对象安全特征,并公开您在该位置实际需要的 API 子集。

例如,假设您拥有或使用非对象安全特征:

trait Serialize {
    /// Serialize self to the given IO sink
    fn serialize(&self, sink: &mut impl io::Write);
}
Run Code Online (Sandbox Code Playgroud)

该特征不能用作特征对象,因为它(大概是为了确保最大效率)具有通用方法。但这并不需要阻止您的代码使用特征对象来访问特征的功能。假设您需要Serialize对值进行装箱,以便将它们保存在向量中,并将其保存到一个文件

// won't compile
struct Pool {
    objs: Vec<Box<dyn Serialize>>,
}

impl Pool {
    fn add(&mut self, obj: impl Serialize + 'static) {
        self.objs.push(Box::new(obj) as Box<dyn Serialize>);
    }

    fn save(&self, file: &Path) -> io::Result<()> {
        let mut file = io::BufWriter::new(std::fs::File::create(file)?);
        for obj in self.objs.iter() {
            obj.serialize(&mut file);
        }
        Ok(())
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码无法编译,因为Serialize不是对象安全的。但是 - 您可以轻松定义一个新的对象安全特征来满足以下需求Pool

// object-safe trait, Pool's implementation detail
trait SerializeFile {
    fn serialize(&self, sink: &mut io::BufWriter<std::fs::File>);
}

// Implement `SerializeFile` for any T that implements Serialize
impl<T> SerializeFile for T
where
    T: Serialize,
{
    fn serialize(&self, sink: &mut io::BufWriter<std::fs::File>) {
        // here we can access `Serialize` because `T` is a concrete type
        Serialize::serialize(self, sink);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在Pool几乎可以使用dyn SerializeFileplayground):

struct Pool {
    objs: Vec<Box<dyn SerializeFile>>,
}

impl Pool {
    fn add(&mut self, obj: impl Serialize + 'static) {
        self.objs.push(Box::new(obj) as Box<dyn SerializeFile>);
    }

    // save() defined the same as before
    ...
}
Run Code Online (Sandbox Code Playgroud)

定义一个单独的对象安全特征似乎是不必要的工作 - 如果原始特征足够简单,那么您当然可以一开始就使其成为对象安全。但有些特征要么太通用,要么太面向性能,无法从一开始就成为对象安全的,在这种情况下,最好记住保持它们通用是可以的。当您确实需要对象安全版本时,通常是为了一项具体任务,其中根据原始特征实现的自定义对象安全特征将完成这项工作。