Trait objects of combined traits and type reconstruction

⚓ rust    📅 2025-05-22    👤 surdeus    👁️ 4      

surdeus

Warning

This post was published 39 days ago. The information described in this article may have changed.

Hello,

I have some question about trait objects of combined traits.

They need some build up, you'll find them at the very end. A
complete minimal example is on GitHub, runs with cargo run.

I've been playing with trait objects, which seem to solve a similar
problem as Haskell's existential types: They allow you to put things
of different type (A and B) into a generic datastructure like
Vec<T>, which would normally force you to choose T=A, or
T=B, exclusively, or use a sum type like Either.

However, if A and B both implement a common trait, say:
Printable, then you may create a Vec<&dyn Printable> which can
hold items of both types.

let a = A{};
let b = B{};
let items: Vec<&dyn Printable> = vec!(&a, &b);

This is type safe, since the compiler only allows you to use those
properties of the elements of the vector that are provided by the
common trait.

In other words: When taking an item from the vector, all you know for
sure is it's printable. That's what the type says, and that's what
the compiler allows you to do. You may print them.

So far, so good.

I have then tried (obviously) to reconstruct the original type of the
vector's members, and this is also possible: Define an enum for every
possible case, here this would be

enum Discovery<'a> { IsA(&'a A), IsB(&'a B) }

extend A and B with a function to report their case

trait Discoverable { fn discover(&self) -> Discovery; }

impl Discoverable for A { fn discover(&self) -> Discovery { IsA(&self) } }
impl Discoverable for B { fn discover(&self) -> Discovery { IsB(&self) } }

and almost! Next I wanted to create a vector of type

Vec<&dyn Printable + Discoverable>

which failed with “only auto traits can be used as additional traits
in a trait object”, but included a helpful suggestion (Thanks!)
making me try

trait CombinedTrait: Printable + Discoverable {}
impl<T: Printable + Discoverable> CombinedTrait for T {}

let items: Vec<&dyn CombinedTrait> = vec!(&a, &b);

Now this works partially. The function

fn print_and_discover<T: CombinedTrait>(x: &T) {
    x.print();
    match x.discover() {
        IsA(a) => a.only_a(),
        IsB(b) => b.only_b(),
    }
}

happily prints the items using their common “printability”, then
discovers their type and dispatches accordingly, calling functions
only_ that are ony implemented for the individual types.

But this seems to go only so far. While I can create my vector

let items: Vec<&dyn CombinedTrait> = vec!(&a, &b);

print and rediscover the items

items[i].print();
match items[i].discover() { … }

I fail to pass the vector to a function:

fn print_list(items: &Vec<&dyn Printable>) { … }
print_list(&items);

gives “expected trait Printable, found trait CombinedTrait”.

Question 1: Why does a found CombinedTrait not satisfy an
expected Printable?

Also, I fail to create and use an iterator (other than the counting
loop above):

for i in items.iter() { print_and_discover(i); }

gives “the trait bound &dyn CombinedTrait: CombinedTrait is not
satisfied”

Question 2: is it not? should it not be?

Cheers!


struct A {}

impl A {
    fn only_a(&self) {
        println!("only A");
    }
}


struct B {}

impl B {
    fn only_b(&self) {
        println!("only B");
    }
}



trait Printable {
    fn print(&self);
}

impl Printable for A {
    fn print(&self) {
        println!("print A");
    }
}

impl Printable for B {
    fn print(&self) {
        println!("print B");
    }
}

fn print_list(items: &Vec<&dyn Printable>) {
    for i in items {
        i.print();
    }
}



enum Discovery<'a> {
    IsA(&'a A),
    IsB(&'a B)
}
use Discovery::*;

trait Discoverable {
    fn discover(&self) -> Discovery;
}

impl Discoverable for A {
    fn discover(&self) -> Discovery { IsA(&self) }
}

impl Discoverable for B {
    fn discover(&self) -> Discovery { IsB(&self) }
}

trait CombinedTrait: Printable + Discoverable {}

impl<T: Printable + Discoverable> CombinedTrait for T {}

fn print_and_discover<T: CombinedTrait>(x: &T) {
    x.print();
    match x.discover() {
        IsA(a) => a.only_a(),
        IsB(b) => b.only_b(),
    }
}



fn main() {

    let a = A{};
    let b = B{};


    // Nice!

    println!("-- different types in one list");
    let items: Vec<&dyn Printable> = vec!(&a, &b);
    print_list(&items);


    // Quality!!!

    println!("-- reconstruction of original type");
    print_and_discover(&a);
    print_and_discover(&b);


    // Looking good...

    println!("-- combined trait in vector (commented out)");

    #[allow(unused_variables)]
    let items: Vec<&dyn CombinedTrait> = vec!(&a, &b);

    for i in 0..items.len() {
        items[i].print();
        match items[i].discover() {
            IsA(a) => a.only_a(),
            IsB(b) => b.only_b(),
        }
    }


    // These two don't compile, and I don't understand why.

    /*
    print_list(&items);
    // */

    /*
    for i in items.iter() {
        print_and_discover(i);
    }
    // */
}

6 posts - 4 participants

Read full topic

🏷️ rust_feed