Shuumatsu
Shuumatsu

Reputation: 662

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.

Upvotes: 0

Views: 151

Answers (1)

user4815162342
user4815162342

Reputation: 154836

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

Upvotes: 2

Related Questions