For years now I have wanted to write a DI framework for Rust like I wrote for C++:
http://adam.younglogic.com/2008/07/dependency-injection-in-c/
However, I have not been able to wrap my head around how to do that. The problem is that the DI framework, which both creates and shares the instances, will come in to conflict with the borrow checker.
Lets say I have 3 Objects: Query1, Query2, Database. A Query object requires a Database object. Q1 and Q2 are different queries, and both use the same Database object. Thus we have a dependency graph like this:
Query1->Database<-Query2
So a super simplistic structure code base would look like this.
use std::string::String;
struct Database {
url: String
}
struct Query<'a> {
db: &'a Database,
sql: String,
}
impl Database {
fn execute(&self) {
println!("connect to {}",self.url);
}
}
impl Query<'_> {
fn execute(&self) {
self.db.execute();
println!("execute {}",self.sql);
}
}
fn main() {
println!("Hello, world!");
let db = Database{url: "postgresql:server:port".to_string()};
db.execute();
{
let q1 = Query{db: &db, sql: "select * from users;".to_string()};
q1.execute();
}
{
let q2 = Query{db: &db, sql: "select * from projects;".to_string()};
q2.execute();
}
}
This seems to point the way to doing IoC, so long as you only have immutable objects. Mutable objects are going to be tougher: for a given workflow, you need to get the mutable object from the container, mutate it, and return it to the container. Unfortunately, this means that the IoC container is going to inject itself into your workflow, not just your construction flow.
My current suspicion is that we are going to need to hold on to mutable objects for a single function call of a method from an external object. This implies a couple usage patterns. The object could be be explicitly fetched from the IoC container by the caller, passed to the method, and returned to the container after the method call. This means that the caller knows about the object, which breaks encapsulation. To maintain encapsulation, this implies that the called object fetches the mutable object from the IoC container on demand, and returns it before the end of the function call. This seems both do-able and tricky at the same time.
Since an object is supposed to register its dependencies up front, and know that the IoC container is going to fulfill them, the dependent object should register a dependency for the mutable object in the form of a smart pointer or some other type of indirection. The smart pointer will act as a lazy load proxy. Suppose we have a mutable object with a function that call change. The chain of operations would look like this:
The get_mut and release_mut calls should have an immutable reference to the mutable object passed as a parameter.
There is a cascading impact of this change: the IoC container itself must now be a mutable reference that the smart pointer holds. Something inside of it has to be unsafe in order to provide the same object to multiple requestors.
We thus would resolve to more traditional approaches of using a monitor, a semaphor, or some other multiple access control mechanism to protect the shared mutable resource.
It seems like this is a solved problem. Rust has Cells, and a mechanism for shared mutable objects using cells.
One benefit to using cells is that they themselves could act as the IoC container. They would not talk to the IoC container to release the object, but rather the mutable object would be wrapped by a cell that the References to it would check for exclusivity.
There would still be a potential for deadlock with multiple callers across multiple mutable objects, requests should probably be ordered to make sure that one caller does not wait on something that is in turn waiting back on it.