An easy way of implementing the Dependency Injection Pattern in Rust

Photo by cottonbro studio: https://www.pexels.com/photo/white-and-black-boat-on-sea-4928899/

Introduction

Dependency Injection is simply said, the idea that your classes should depend on abstraction, i.e. the abstraction of a datasource for example, rather than on concrete implementations. This means that classes will depend on interfaces rather than on real classes.

This has several advantages:

  1. It is easy to exchange on implementation for another, without changing the changing the client code.
  2. Because an interface usually covers a small part of the total API of a class, you can precisely determine which class gets access to which methods and functionality. If for example you have a datasource class, you can have a read- and a write-interface, and possible a combination of the two. Each client can then have either interface, depending on the need. This is an example of the principle of least privilege.

It looks like this:

The summary of this pattern is: you should depend on interfaces, not on concrete implementations. This make it easier to swap different implementations of the same functionality. This is also the ‘D’ in SOLID.

Implementation in Rust

My implementation of this pattern in Rust was surprisingly easy.

In this example I will build an imaginary carfactory which just needs wheels: big wheels and small wheels. Since the machines producing this wheels have the same interface or trait, we can use that:

trait WheelProducer {
    fn produce_wheel(&self)->String;
}

For simplicity’s sake, our wheelproducer produces only a string representation of a wheel.

The CarFactory has an instance of a WheelProducer:

struct CarFactory {
    wheel_producer:Box<dyn WheelProducer>
}

Because the WheelProducer is a trait object, we have to use the dyn keyword. Also, because the size of the producer is not known beforehand, we have to put it in a Box

Notice that we define the WheelProducer field in terms of the interface, not of a concrete class.

Now on to our first WheelProducer:

struct SmallWheelProducer;

impl WheelProducer for SmallWheelProducer {
    fn produce_wheel(&self) -> String {
        return "Small wheel".to_string()
    }
}

This code is self-explanatory. Note that SmallWheelProducer implements the WheelProducer interface.

Next we do the same thing for the BigWheelProducer:

struct BigWheelProducer;

impl WheelProducer for BigWheelProducer {
    fn produce_wheel(&self) -> String {
        return "Big wheel".to_string()
    }
}

Testing time

Now we can test our setup:

fn main() {
    let wheel_producer=SmallWheelProducer;
    let factory=CarFactory {
        wheel_producer: Box::new(wheel_producer)
    };

    let wheel=factory.wheel_producer.produce_wheel();
    println!("Wheel produced: {}",wheel);
}

Line by line:

  1. We initiate an object which has a WheelProducer trait.
  2. We pass that on when we construct our factory
  3. Next we produce a wheel, and print out the result.

Try changing the SmallWheelProducer to BigWheelProducer. The rest of the code stays the same, but the results are different.

Dependency injection packages

Well, doing this manually is easy enough in our simple case, however, in more advanced cases it can become error-prone and cumbersome.

I found two crates that may take over some of the logic and automate for example constructor injection:

  • Inject: according to its own description: Inject implements macros for dependency resolution, attempting to promote the Inversion of control (IoC) principle.
  • Syrette: The description of this crate says: The convenient dependency injection & inversion of control framework for Rust.

In later articles I will try out both of these crates and possibly more.

Conclusion

Implementing this pattern is not very difficult. The main thing to keep in mind is to use interfaces or traits instead of concrete implementations.

As I mentioned, in a next post I will try out the different crates available to us for achieving this pattern

Leave a Reply

Your email address will not be published. Required fields are marked *