How to do automatic resolving?
In rust-ioc we need to resolve any dependency semi-manually:
fn resolve((x, y): Self::Dependency) -> Self {
Z {
x: x.into_inner(),
y: y.into_inner(),
}
}
In C# ninject we can bind interface to class
public class WarriorModule : NinjectModule
{
public override void Load()
{
this.Bind<IWeapon>().To<Sword>();
}
}
We can use concrete types and automatically inject classes without binding.
Something like this:
class Sword {}
class Samurai
{
readonly Sword weapon;
public Samurai(Sword weapon)
{
this.weapon = weapon;
}
public void Attack(string target)
{
this.weapon.Hit(target);
}
}
Do you think about same style?
- It's possible to do same in rust traits?
- If not, how to use reflection, may be create some language extensions for it?
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:
- Rust is 'explicit' (ie leaks) the way data is stored, because data doesn't live on the heap by default. And to use trait objects you either need a borrowed reference or an owned
Box(maybe you can store a trait object behind anRctoo... 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. - Rust doesn't have inheritance, so it's common for trait bounds to cover many possible types. Attempting to resolve a type by a trait bound could be a bit weird
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:
- Rust's
impl Traitfeature lets you hide the implementation ofGetProductQuery, so it can't be given an explicit name. You have to treat it generically throughout your codebase - Rust's closures automatically generate a structure for you that captures the state you pass in, in this case the
TStoredependency. This saves you from having to write a structure to implementGetProductQuerywith a bunch of generic types on it Resolveris 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 done
So 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!