Combine traits with associated types in rust and use factory

I'm trying to create a kind of wrapper trait that combines multiples traits and a function that is in a factory pattern fashion that returns the associated implementation of the trait. It works well as long as you don't have associated types. I don't know how to refer to the Output type

use std::ops::Add;

pub trait Hello {
    fn hello(&self);
}
pub trait Goodbye {
    fn goodbye(&self);
}
struct Suffix {
    suffix: String,
}
pub struct World {
    content: String,
}

impl World {
    fn new(content: String) -> Self {
        World { content: content }
    }
}

impl Hello for World {
    fn hello(&self) {
        println!("Hello {}", self.content)
    }
}
impl Goodbye for World {
    fn goodbye(&self) {
        println!("Goodbye {}", self.content)
    }
}
impl Add<Suffix> for World {
    type Output = World;
    fn add(self, other: Suffix) -> World {
        let suffixed: String = self.content + &other.suffix;
        World::new(suffixed)
    }
}
pub struct Everyone {
    content: String,
}

impl Everyone {
    fn new(content: String) -> Self {
        Everyone { content: content }
    }
}
impl Hello for Everyone {
    fn hello(&self) {
        println!("Hello {}", self.content)
    }
}
impl Goodbye for Everyone {
    fn goodbye(&self) {
        println!("Goodbye {}", self.content)
    }
}

impl Add<Suffix> for Everyone {
    type Output = Everyone;
    fn add(self, other: Suffix) -> Everyone {
        let suffixed: String = self.content + &other.suffix;
        Everyone::new(suffixed)
    }
}
trait HelloGoodbye: Hello + Goodbye {}
impl<T> HelloGoodbye for T where T: Hello + Goodbye {}

fn get_hello_goodbye(number: f64) -> Box<dyn HelloGoodbye> {
    if number > 0.5 {
        Box::new(World::new("World".to_string()))
    } else {
        Box::new(Everyone::new("Everyone".to_string()))
    }
}
trait SuffixableHello: Hello + Add<Suffix, Output = dyn Hello> {}
impl<T> SuffixableHello for T where T: Hello + Add<Suffix, Output = dyn Hello> {}

fn get_suffixable_hello<T: SuffixableHello>(number: f64) -> Box<dyn SuffixableHello> {
    if number > 0.5 {
        Box::new(World::new("World".to_string()))
    } else {
        Box::new(Everyone::new("Everyone".to_string()))
    }
}

fn main() {
    // This works
    let hello_goodbye = get_hello_goodbye(0.64f64);
    hello_goodbye.hello();
    hello_goodbye.goodbye();

    // This does not work
    let suffixable_hello = get_suffixable_hello(0.64f64);
    suffixable_hello.hello()
}

I get this error :

error[E0271]: type mismatch resolving `<World as Add<Suffix>>::Output == (dyn Hello + 'static)`
   |
81 |         Box::new(World::new("World".to_string()))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type mismatch resolving `<World as Add<Suffix>>::Output == (dyn Hello + 'static)`
   |
note: expected this to be `(dyn Hello + 'static)`
   |
33 |     type Output = World;
   |                   ^^^^^
   = note: expected trait object `(dyn Hello + 'static)`
                    found struct `World`
   = note: required for the cast to the object type `dyn SuffixableHello<Output = (dyn Hello + 'static)>`

error[E0271]: type mismatch resolving `<Everyone as Add<Suffix>>::Output == (dyn Hello + 'static)`
   |
83 |         Box::new(Everyone::new("Everyone".to_string()))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type mismatch resolving `<Everyone as Add<Suffix>>::Output == (dyn Hello + 'static)`
   |
note: expected this to be `(dyn Hello + 'static)`
   |
60 |     type Output = Everyone;
   |                   ^^^^^^^^
   = note: expected trait object `(dyn Hello + 'static)`
                    found struct `Everyone`
   = note: required for the cast to the object type `dyn SuffixableHello<Output = (dyn Hello + 'static)>`

Is this possible in rust ? I tried a lot of different combinations and none of them work. I know that I can handle those type inside an enum struct, but if we want to get the corresponding value from a function the return type is still a problem, no ?
Maybe it's a design problem, and this is not the way it should work in rust, if so, is there a better way to do ?
Thank you a lot :slight_smile:

The problem is that you are constraining the Output type for the Add impl to be exactly dyn Hello which is a type, not an interface. And the types you're trying to use as SuffixableHello implement the trait with other specific types, which doesn't match the signature. They actually can't directly return a raw dyn Hello since that type is unsized, so as written it's not possible to implement that trait. It should probably be Box<dyn Hello>

You can make what you have build by changing the impl Add<Suffix> blocks to output a Box<dyn Hello> and change SuffixableHello to

trait SuffixableHello: Hello + Add<Suffix, Output = Box<dyn Hello>> {}
impl<T> SuffixableHello for T where T: Hello + Add<Suffix, Output = Box<dyn Hello>> {}

Here's a playground with that full example


As an alternative to modifying the impls of Add, you can also create a wrapper type that performs the boxing.

pub struct BoxSuffixableHello<T>(T);

impl<T> Hello for BoxSuffixableHello<T>
where
    T: Hello,
{
    fn hello(&self) {
        self.0.hello()
    }
}

impl<T> Add<Suffix> for BoxSuffixableHello<T>
where
    T: Add<Suffix>,
    T::Output: Hello + 'static,
{
    type Output = Box<dyn Hello>;

    fn add(self, rhs: Suffix) -> Self::Output {
        Box::new(self.0 + rhs)
    }
}

trait SuffixableHello: Hello + Add<Suffix> {}
impl<T> SuffixableHello for T where T: Hello + Add<Suffix> {}

fn get_suffixable_hello(number: f64) -> Box<dyn SuffixableHello<Output = Box<dyn Hello>>> {
    if number > 0.5 {
        Box::new(BoxSuffixableHello(World::new("World".to_string())))
    } else {
        Box::new(BoxSuffixableHello(Everyone::new("Everyone".to_string())))
    }
}

Here's a playground with the full code for the wrapper version

Thank you a lot for this, it works exactly as I wanted ! :slight_smile:

EDIT : I play a little bit with your code andI still got an error : Rust Playground

Indeed, we can't use this Add<Suffix> even with this workaround because the signature of the Add trait is

fn add(self, rhs: Rhs) -> Self::Output;

self is not a reference to the object but the object itself. So we get this error :

the size of `dyn SuffixableHello<Output = Box<dyn Hello>>` cannot be statically determined

I've not find a wrokaroud for this yet

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.