Sean Kelleher
Sean Kelleher

Reputation: 2092

Storing a nested trait instance in a generic variable

If I have an "outer" trait with a univeral generic type (Collection, below), then I can create an instance of that with an "inner" trait type (Collection<&dyn Stringable>). I can then use any values with that instance as long as those values implement the nested trait (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)
    }
}

However, if I implement both the outer and inner trait for a type (Collection<i8>), a value of that type can't be put in a Collection<&dyn Stringable> variable, even though the inner type of the first (i8) implements the second (Stringable). The code below gives the following error:

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

Code:

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]
    }
}

Is it possible, for example, to write something like impl Collection<&dyn Stringable> for StaticCollection, so that the StaticCollection can be stored in the generic Collection<&dyn Stringable> variable?

Upvotes: 0

Views: 206

Answers (1)

Ohad
Ohad

Reputation: 2618

It depends on how StaticCollection is implemented, but it is technically possible:

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!()
    }
}

However StaticCollection must be created with &dyn Stringable items. For example (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
    }
}

Note that we are generic over the 'a lifetime which is for the items of the Vec, but because the trait (as it is defined) requires get_all to return a &Vec borrowed from self, it is not possible to "transform" the Vec. So if you define StaticCollection { v: Vec<&i8> } (or Vec<i8>, get_all can not be implemented (without "leaking" a new Vec, which is probably not what you want).

The full lifetimes of get_all are actually:

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

A word of warning: dyn Trait can get you into really tight corners with the compiler and borrow checker. You might also want to checkout this blog post about "type families" which explores similar concepts.

Upvotes: 1

Related Questions