Search code examples
genericstypesrusttraitsassociated-types

How to store trait implementations together when the trait has associated types


Trait objects can be used when we want to store multiple types together in a collection.

But I don't know how to do this when the trait has associated types.

trait Error {}

trait Trait {
    type Error: Error;
    fn set(&mut self, key: String, value: String) -> Result<(), Self::Error>;
}

struct StructA;
impl Trait for StructA {
    type Error = ErrorA;
}

enum ErrorA {}
impl Error for ErrorA {}

struct StructB;
impl Trait for StructB {
    type Error = ErrorB;
}

enum ErrorB {}
impl Error for ErrorB {}

fn main() -> Result<(), Box<dyn Error>> {
    let value: Box<dyn Trait<Error = dyn Error>> = match 0 {
        0 => Box::new(StructA),
        _ => Box::new(StructB),
    };

    value.set(String::from("key"), String::from("value"))?;

    Ok(())
}

I have to specify the associated type Box<dyn Trait<Error = _>> here, but I don't know which type would fit. I tried dyn Error but it will not work.


Solution

  • If you can change Trait, you can make it object-safe for all error types by returning a dynamic error type to begin with, e.g. change the signature of set() to something like:

    fn set(&mut self, key: String, value: String) -> Result<(), Box<dyn Error + '_>>;
    

    However, if you can't change Trait to make it object-safe, you can still create your own object-safe trait, and provide a blanket implementation for every type that implements Trait. For example:

    trait DynamicTrait {
        fn set(&mut self, key: String, value: String) -> Result<(), Box<dyn Error + '_>>;
    }
    
    impl<T: Trait> DynamicTrait for T {
        fn set(&mut self, key: String, value: String) -> Result<(), Box<dyn Error + '_>> {
            Trait::set(self, key, value).map_err(|e| Box::new(e) as _)
        }
    }
    

    DynamicTrait works exactly like Trait, but returns Box<dyn Error> in case of error, so it's object-safe. For example, this just works without modifying the implementation of either Trait or its implementations StructA and StructB:

    fn main() {
        let value: Box<dyn DynamicTrait> = match 0 {
            0 => Box::new(StructA),
            _ => Box::new(StructB),
        };
    }
    

    Playground