Intro to Dependency Injection in Rust

Photo by Wojciech Then on Unsplash

Let’s start with a struct Client that contains a verifier struct which helps us perform verification on the responses fetched by the client

struct Verifier {}impl Verifier {
fn verify() {
println!("Verification Successful!");
}
}

pub struct Client {
verifier: Verifier
}

What’s wrong with the above implementation?

let’s say tomorrow, we want to change the implementation of how we verify responses in the Client; it will require us to change the Verifier struct

But we also need to think about testing? How do you unit test something like this? We cannot provide our mock Verifier implementation when testing

Let’s change the implementation a little bit

struct Verifier {}impl Verifier {
fn verify() {
println!("Verification Successfull!");
}
}
pub struct Client {
verifier: Verifier
}
impl Client {
fn constructor(verifier: Verifier) -> Self {
return Client {
verifier: verifier
}
}
}

This does not change anything - we are injecting the Verifier into Client but it is still the same struct with the same verify() implementation (so the behaviour is still the same)

We need something that lets us customize the verify() function

Traits to the rescue

A trait lets us define shared behaviour by defining methods that any type can implement (even the ones you did not define!)

We can define a trait that includes the Verification behaviour

pub trait Verification {
fn verify();
}

Now we can implement this trait for any type and we can pass it to our Client constructor which allows us to create different Client instances with varying Verification behaviour

pub struct Client {
verifier: Box<dyn Verification>
}
impl Client {
fn constructor(verifier: Box<dyn Verification>) -> Self {
return Client {
verifier: verifier
}
}
}

What is this Box<dyn ..> thing?

Box helps us allocate memory on the heap and then puts the object in the box in the allocated memory

We need it because any type can implement the Verification trait and the compiler cannot know the size of the passed type at compile time

Now, the above implementation lets us pass any type that implements the Verification trait which helps with testing and also allows separation of concerns

// Usage of Client Structpub struct FailingVerifier {}impl Verification for FailingVerifier {
fn verify() {
println!("Failing Verification")
}
}
fn main() {
let client = Client::constructor(Box::new(FailingVerifier{}));
}

That marks the end of the article, please feel free to point out in the comments if I am wrong about anything

--

--

--

Software and Finance Enthusiast

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Let’s sort quickly using Quicksort :)

Kubernetes learning experience

How to create side by side buttons with the Divi Theme?

Add new Google Calendar events to a Google Sheets spreadsheet as new rows and email it to a…

Add new Google Calendar events to a Google Sheets spreadsheet as new rows and email it to a specified email addressalt text

Using IBM Cloud Code Engine to Analyze Big Data without Writing a Single Line of Code

v 2.7.8 | Quickswitch ⚡️

Making all those hits count.

ONLYOFFICE Connectors

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Vishwa Patel

Vishwa Patel

Software and Finance Enthusiast

More from Medium

The Technologies I’m Learning in 2022

Building an API in C++ With Pistache

Rust Foo: NTP Client (Part 2)

PipyJS — A functional style JavaScript engine for programming Pipy