audunhalland / entrait

Loosely coupled Rust application design made easy
86 stars 3 forks source link

Investigate always taking self by value #11

Closed audunhalland closed 2 years ago

audunhalland commented 2 years ago

I don't know whether this will work, but consider:

impl<T> Trait for Impl<T> {
    fn foo(&self) {}
}

vs.

impl<T> Trait for &Impl<T> {
    fn foo(self) {}
}

If the traits always take self by value, it might not be necessary to duplicate the traits when doing dependency inversion, because then we can make a newtype without reborrowing:

struct NewType<'a, T>(&'a Impl<T>);

impl<'a, T> Trait for NewType<'a, T> {
    fn foo(self) { self.0.foo() }
}

The main reason it was necessary to duplicate the trait with the current design, was that it was impossible to make futures implement Send when "reborrowing" for a struct like NewType. NewType had to be passed as a reference into the delegation-target, referencing the value just created on the stack, and things referencing a value owned by the stack (vs. the heap) can't be sent to another thread. If NewType is passed by-value, this restriction is likely lifted.

audunhalland commented 2 years ago

I tested this and it basically works, but it requires adding a Copy bound for dependencies:

#[entrait(Foo)]
fn foo(deps: &(impl Bar + Copy)) {
    deps.bar();
}

So I don't think this will be worth it.

playground

audunhalland commented 2 years ago

But of course, the generated traits can be modified to have Copy as a supertrait 😎

If that is done, I think it will work.

audunhalland commented 2 years ago

Another consequence of this will be that it will be impossible to consume dependencies. But I can't really see any use cases for that, so should be fine.

audunhalland commented 2 years ago

Yet another consequence (which appears to be a good one) is that one no longer has to write & for deps, thus the parentheses are not longer needed for multiple deps:

trait Foo: Copy {
    fn foo(self);
}
trait Bar: Copy {
    fn bar(self);
}

fn takes_foo(deps: impl Foo + Bar) {
    deps.foo();
    deps.bar();
}

But then, deps will be hardcoded to always be & forever, and there will no longer be any chance of mutating them or taking them by value.

audunhalland commented 2 years ago

Have to investigate whether it will be possible to use e.g. tokio::spawn, cloning the underlying deps. Maybe it could be done with a separate trait. trait CloneUnderlyingValue { fn clone(self); }

audunhalland commented 2 years ago

It appears that it will be very hard to incorporate things like Clone into this reference-based solution. Working with Ts and &self is much easier than working with &Ts and self.

audunhalland commented 2 years ago

Putting this one ice for now.