将嵌套特征实例存储在通用变量中

Storing a nested trait instance in a generic variable

如果我有一个具有通用泛型类型的“外部”特征(Collection,如下),那么我可以创建一个具有“内部”特征类型的实例(Collection<&dyn Stringable>) .然后我可以对该实例使用任何值,只要这些值实现嵌套特征 (Stringable):

fn main() {
    let mut vs: &mut Collection<&dyn Stringable> = &mut vec![];
    vs.add(&1);
    vs.add(&true);
    vs.add(&3);
    for v in vs.get_all() {
        println!("{}", v.string());
    }
}

trait Collection<T> {
    fn add(&mut self, v: T);
    fn get_all(&self) -> &Vec<T>;
}

impl<T> Collection<T> for Vec<T> {
    fn add(&mut self, v: T) {
        self.push(v)
    }
    fn get_all(&self) -> &Vec<T> {
        &self
    }
}

trait Stringable {
    fn string(&self) -> String;
}

impl Stringable for i8 {
    fn string(&self) -> String {
        format!("int({})", self)
    }
}

impl Stringable for bool {
    fn string(&self) -> String {
        format!("bool({})", self)
    }
}

但是,如果我同时为一个类型 (Collection<i8>) 实现了外部特征和内部特征,则该类型的值不能放入 Collection<&dyn Stringable> 变量中,即使内部特征第一个 (i8) 的类型实现了第二个 (Stringable)。下面的代码给出了以下错误:

the trait `Collection<&dyn Stringable>` is not implemented for `StaticCollection`

代码:

fn main() {
    let mut vs: &mut Collection<&dyn Stringable> = &mut StaticCollection{};
    for v in vs.get_all() {
        println!("{}", v.string());
    }
}

struct StaticCollection {}

impl Collection<i8> for StaticCollection {
    fn add(&mut self, v: i8) {}
    fn get_all(&self) -> &Vec<i8> {
        &vec![1, 2, 3]
    }
}

是否可以,例如,写类似 impl Collection<&dyn Stringable> for StaticCollection 的东西,以便 StaticCollection 可以存储在通用 Collection<&dyn Stringable> 变量中?

这取决于StaticCollection如何实现,但技术上是可行的:

impl<'a> Collection<&'a dyn Stringable> for StaticCollection {
    fn add(&mut self, v: &'a dyn Stringable) {
        
    }
    fn get_all(&self) -> &Vec<&'a dyn Stringable> { 
        panic!()
    }
}

但是 StaticCollection 必须使用 &dyn Stringable 项创建。 例如 (Link to playground):

fn main() {
    let mut vs: &mut Collection<&dyn Stringable> = &mut StaticCollection { 
        v: vec![&0,&1,&2]
    };
    for v in vs.get_all() {
        println!("{}", v.string());
    }
}

struct StaticCollection {
    v: Vec<&'static dyn Stringable>,
}

impl<'a> Collection<&'a dyn Stringable> for StaticCollection {
    fn add(&mut self, v: &'a dyn Stringable) {
        
    }
    fn get_all(&self) -> &Vec<&'a dyn Stringable> { 
        &self.v
    }
}

请注意,我们在 'a 生命周期内是通用的,这是针对 Vec,但是因为特征(如定义的那样) 需要 get_all 到 return 从 self 借来的 &Vec,不可能“改造” Vec。所以如果你定义了StaticCollection { v: Vec<&i8> }(或者Vec<i8>get_all就无法实现(不“泄露”一个新的Vec,这可能不是你想要的)。

get_all 的完整生命周期实际上是:

fn get_all<'s>(&'s self) -> &'s Vec<&'a ...>

一句警告:dyn Trait 可能会让您在使用编译器和借用检查器时陷入困境。 您可能还想查看 this blog post 探索类似概念的“类型族”。