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?
It sounds like such triggers could be used to avoid the race condition. Constraint triggers respect the MVCC rules, so they cannot “peek” at uncommitted rows of concurrent transactions. But the trigger execution can be deferred to the end of the transaction. They also have an entry in the pg_constraint system catalog.
Finally, PostgreSQL has the option to create “constraint triggers” with CREATE CONSTRAINT TRIGGER. It sounds like such triggers could be used to avoid the race condition.
In case of Sudoku, there are three such constraints. Finally, a model is a world which meets all the constraints. Below I will tie all of it.
Having learned about the Theory of Constraints in a previous article, today we want to focus primarily on implementation. We will use a company example to see how every step of the theory can be implemented. We will go straight into defining our environment. For this article, we will assume that you run a car manufacturing plant.
If I'm summarizing correctly, you basically want to go from a concrete type FooImpl
to a trait object dyn Foo
but in a generic setting (in this case through Rc
).
use std::rc::Rc;
fn to_dyn<DynTrait, Impl>(i: Rc<Impl>) -> Rc<DynTrait> {
// ... some implementation
}
There is no existing constraint that can do this in stable Rust. You would have to manually implement something like a ToDyn
trait for each type you want to use; probably not ideal. I do not know a way to do this otherwise even unsafely.
However this can be done on nightly via the Unsize<T>
trait.
#![feature(unsize)]
use std::rc::Rc;
use std::marker::Unsize;
fn to_dyn<DynTrait: ?Sized, Impl>(i: Rc<Impl>) -> Rc<DynTrait>
where
Impl: Unsize<DynTrait> // `Impl` can be "unsized" into `DynTrait`
{
i
}
This is part of RFC 982: DST Coercion which itself is part of the larger RFC 401: Type Conversions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With