How do you implement Dependency Injection in Rust?


#1

I’m trying to write a program in Rust using the principle of Dependency Injection (DI or sometimes Inversion of Control) so that the various concrete implementations do not communicate directly, and are only built against traits.

So something like this:

trait A {
    fn b(&self) -> B;
}

trait B {}

struct AImpl {
    b: B + 'static //'
}

impl AImpl {
    fn new(b: B) -> AImpl {
        AImpl{ b: b }
    }
}

impl A for AImpl {
    fn b(&self) -> B {
        self.b
    }
}

struct BImpl;

impl BImpl {
    fn new() -> BImpl {
        BImpl
    }
}

fn main() {
    let di = AImpl::new(BImpl::new());
}

However, the Rust memory management seems to make this very complicated to do. Are there any examples of people using DI in Rust? What is the best way to do the above?


#2

When I need to do approximately this, I use one of three approaches:

  1. Generics. This is probably the “preferred” approach, at least you get a lot of advice to use this. Roughly, you can write the definition of A and AImpl to be parametric in a type X : B, read as a type X implementing trait B. You can have a field of type X, and use any of the interfaces B provides without needing to commit to a specific type X until you instantiate AImpl.

  2. Trait Objects. By using Box<B> you can wrap just about any implementor of the trait B and provide a vtable for methods from B. This allows you to have one AImpl without needing generic parameters (maintaining all the parameters can be hard, because they tend to creep throughout your code). There are some restrictions about what types you can box, mostly sanely based on what you should be able to expect happens (B can’t have methods returning Self, for example, because then Box<B> wouldn’t have a well defined signature for such a method).

  3. Enums. This is sort of a cheap version of trait objects that I find works pretty well, and avoids much of the mystery. If you plan on having just a few implementors of B, then putting together an enum with the cases and having it implement B as well works quite well. AImpl can have one of these as a member, without generic parameters, and if you want to update the possible cases you can do so where the enum is defined, without having the details bleed into AImpl.

All this being said, I’m not a dependency injection guru, and any/all of these may be DI anti-patterns. Take them with a grain of salt and see if they accomplish what you needed (and report back in either case; I’m curious to learn more too!)


#3

Thanks for the info, could you point me to any code examples that use these techniques (particular 1 and 2)? That would be really helpful for me.


#4

For an example where I use it, consider something like the communicator trait in this example.

pub trait Communicator {
    fn index(&self) -> u64;     // number out of peers
    fn peers(&self) -> u64;     // number of peers
    fn new_channel<T:Send>(&mut self) -> (Vec<Box<Pushable<T>>>, Box<Pullable<T>>);
}

It is a trait, and there are a few implementors in the link above, which among other things has a method promising to return some Pullable and Pushable boxed trait objects. Depending on the implementor, the boxes contain different implementations of Pushable and Pullable, but anyone using a Communicator doesn’t need to know about the details.

I’m not really sure I would recommend digging too deep in the example above; it works for me, but I’m not coming at it from the direction of an especially well thought out piece of software engineering.

One place to get perhaps more interactive advice is on the irc channel, where folks are pretty happy to have gists pasted at them, edit them and fire them back, etc. You may get a lower latency experience there. Also, Steve Klabnik will probably be summoned to this thread at some point, and observe the existence of a “Generics vs Trait Objects” page has has written just for this purpose (I looked and couldn’t find one either in the Book, or on rustbyexample.com).

Cheers,


#5

Cool, thanks, I’ll try IRC.