Open zerkalica opened 7 years ago
That's a good question! So currently resolution is provided by a trait that you implement, which needs a little manual work and doesn't support abstract dependencies, but does resolution at compile-time. That catches any cycles or 'missing' dependencies as early as possible.
From a resolution point of view, you could associate a factory method with a trait bound, and resolve that as a trait object. As for removing the manual implementation, the most idiomatic approach would probably be a custom derive macro, which is about as close as you get to reflection in Rust.
So it seems like all the key pieces are there, but I think there are a few things that could complicate it:
Box
(maybe you can store a trait object behind an Rc
too... I haven't actually tried). The alternative is a generic. Either way means your types that you want to inject into need to know how dependencies are stored.This is just a random dump of my thoughts at this stage :) This repo is just an experiment, so I don't expect anyone to actually use this code.
What do you think?
I think this is a very interesting experiment--thank you for posting not only the repo, but so much of your thinking. It's helpful to gain the benefits of your insight as I go down this same DI/IoC path.
No worries @U007D! If you're looking at experimenting in this space too I'd be interested to see where you end up :)
Lately I've been structuring components in my Rust apps a bit like this:
// A component
#[auto_impl(Fn)] // a little utility to automatically impl `GetProductQuery` for all `Fn(GetProduct) -> Result<GetProductResult, QueryError>`
pub trait GetProductQuery {
fn get_product(&self, query: GetProduct) -> Result<GetProductResult, QueryError>;
}
// Dependency injection
pub fn get_product_query<TStore>(store: TStore) -> impl GetProductQuery
where TStore: ProductStore
{
// The actual implementation
move |query: GetProduct| {
let ProductData { id, title, .. } = store.get(query.id)?.ok_or("not found")?.into_data();
Ok(GetProductResult {
id: id,
title: title
})
}
}
// Dependency resolution
impl Resolver {
pub fn get_product_query(&self) -> impl GetProductQuery {
let store = self.product_store();
get_product_query(store)
}
}
I've got a little sample app I'm working on at the moment that uses this approach. I plan to throw it up on GitHub this month. The essential bits are:
impl Trait
feature lets you hide the implementation of GetProductQuery
, so it can't be given an explicit name. You have to treat it generically throughout your codebaseTStore
dependency. This saves you from having to write a structure to implement GetProductQuery
with a bunch of generic types on itResolver
is a concrete type that knows how to construct components without you having to know what their dependencies are. It's a bit service-locator-y, but gets the job doneSo you end up with something that separates the concerns of dependency injection, resolution and storage without a lot of magic.
I think there's definitely room for some kind of full-fledged IoC container for Rust that gives you even more flexibility to manage these things with a mix of compile-time and run-time work. Containers make figuring out how to compose your app together much more obvious.
Wow, that is elegant! Thank you for sharing your thinking! My colleague @bgbahoue and I have been thinking about DI off and on for a few months now. He's created a remarkably capable first pass at emulating C#'s AutoFac. You can see it at https://github.com/humanenginuity/shaku.
We also plan to open source this; meanwhile we're wrestling with how many attributes to get rid of; how much pain are we signing up for by heavily depending on procedural macros (unstable), the various limitations of Trait objects (eg. methods returning Self), generics and the inability to generate types at runtime, and so on.
I like how your concept minimizes magic, and subsequently the drama. Looking forward to seeing the sample app!
In rust-ioc we need to resolve any dependency semi-manually:
In C# ninject we can bind interface to class
We can use concrete types and automatically inject classes without binding.
Something like this:
Do you think about same style?