I'm working on flushing out dependency injection (DI) in Rust. I have something working, but I'm trying to make it better. As I understand, Rust does not support naked constraints with type parameters (as some other languages do). I'm trying to determine whether it is possible to fake naked constraints in an unsafe way (assuming that's necessary). I'm completely ok with epic fail for an incorrect mapping. My understanding in Rust is simply that I need to convince it that T does implement U and can be cast to it - implicitly or explicitly. By cast, I mean a concrete, but generic, type can be cast to a trait object because it implements it (I realize most people ask for the other direction and I understand the complexities there).
Including everything I have would be a bit much; however, here's some context. Consider that we can boil down a factory function to (this works):
Rc<dyn Fn(&ServiceProvider) -> Rc<dyn Any>>
Currently, I have a builder that looks something like (abridged):
pub struct ServiceDescriptorBuilder<TSvc: Any + ?Sized, TImpl> {
_phantom_svc: PhantomData<TSvc>,
_phantom_impl: PhantomData<TImpl>,
}
impl<TSvc: Any + ?Sized, TImpl> ServiceDescriptorBuilder<TSvc, TImpl> {
pub fn from<F>(self, factory: F) -> ServiceDescriptor
where
F: Fn(&ServiceProvider) -> Rc<TSvc> + 'static, {
// convert to factory
let factory = Rc::new(move |sp| Rc::new(factory(sp)));
}
}
This completely works for something like:
let descriptor = ServiceDescriptorBuilder::<dyn Foo, FooImpl>::new(/* unimportant */)
.from(|sp| Rc::new(FooImpl::default()))
My goal is to remove the additional Rc declaration in the factory closure. All instances are wrapped in Rc. A transient lifetime always gets a fresh Rc with count 1, while a singleton has a cloned Rc. What I want to be able to do is (e.g. minus the Rc):
let descriptor = ServiceDescriptorBuilder::<dyn Foo, FooImpl>::new(/* unimportant */)
.from(|sp| FooImpl::default())
I can get the second, simpler variant to compile, but it fails at runtime - for somewhat obvious reasons. I need to convince Rust to use Rc<dyn Foo> with an owned value of FooImpl. This just doesn't seem to be possible with generics. Perhaps it's possible with associative types, but that's only an option if it still results in a succinct syntax. What really escapes me is the magic the compiler itself can do. It seems to have no problem understanding that Rc<FooImpl> is covariant and can satisfy Rc<dyn Foo>.
I have something working so this isn't really blocking, but it really irks me that this is not achievable in a simpler form. Perhaps it's just a current limitation in the language. Some other languages would allow this to be expressed as where TImpl: TSvc. I won't pretend to understand why this is hard or not yet supported, but other languages have figured it out. I'm aware that there is already a limitation that you cannot constrain a type to a trait. Perhaps that's the only feature needed to make this more succinct.
I'm content with waiting, but it seems there is some albeit unsafe way to make Rc own an instance of FooImpl, but expressed as Rc<dyn Foo>. Unless I have a gross misunderstanding, this is what the compiler is ultimately producing. It's unclear to me how to force or replicate that.
I'm not excited to use a macro to solve this, but I'd consider that as an acceptable solution; especially, if it can be hidden away as an implementation detail (from callers/devs). While my current solution does work and is more verbose, it does provide some level of compiler checks because you can't just return anything. A more concise syntax that removes this check (with transmute or something) might not be worth the trade-off.
Suggestions?