Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement Pseudo Naked Constraints

Tags:

rust

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?

like image 282
Chris Martinez Avatar asked Dec 17 '20 06:12

Chris Martinez


People also ask

Is there a way to avoid race condition with constraints triggers?

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.

How to avoid the race condition in PostgreSQL?

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.

How many constraints are there in Sudoku?

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.

Can the theory of constraints be implemented?

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.


1 Answers

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.

like image 67
kmdreko Avatar answered Oct 02 '22 23:10

kmdreko