Trait bound autoderef


#1

Currently when you implement Deref<T> on a type A, you can use all the functions defined both by T and by the traits implemented by T.

For example you can do something like this:

use std::collections::Collection;

struct Foo(Vec<int>);

impl Deref<Vec<int>> for Foo {
    fn deref(&self) -> &Vec<int> {
        let &Foo(ref val) = self;
        val
    }
}

fn main() {
    let f = Foo(Vec::new());
    println!("{}", f.is_empty());
}

But let’s say you have a function that awaits an object which implements Collection. You can’t directly pass a Foo or you would get error: failed to find an implementation of trait core::collections::Collection for Foo.

The call can still be achieved by calling needs_collection(&*foo) instead of just needs_collection(&foo).

If I now implement MyCustomTrait for Foo, calling needs_mycustomtrait can be done with needs_mycustomtrait(&foo).

But what if a function requests an object which implements both Collection and MyCustomTrait? Well, currently you can’t call it with a Foo at all, even though you can call methods from both Collection and MyCustomTrait on the foo object itself.

My proposed solution

Allow directly passing a Foo to a function which awaits a Collection. More generally, objects which implement Deref<T> and/or DerefMut<T> should automatically be treated as if they implemented all the traits that T implements.

This should be recursive. If A implements Deref<T> and T implements Deref<U>, then A implements the traits of U.

Real-life motivation

The reason why I’m opening this thread is this reddit post.

The idea is to implemented a web framework. When a request is received by the server, a BaseRequestis created. Then it is turned into a WithSession<BaseRequest>, which is then turned into a WithDatabase<WithSession<BaseRequest>>, which is then turned into a WithAuthenticationAttempt<WithDatabase<WithSession<BaseRequest>>>, which is then turned into a WithAuthenticatedUser<WithAuthenticationAttempt<WithDatabase<WithSession<BaseRequest>>>>, etc.

Even when each element implements Deref for its underlying object, there is currently no way to pass an object of this type to a fn handler<R: HasDatabase + HasAuthenticatedUser + HasBasicRequest>(request: R) because of the issue explained in this thread, even though you can call methods from HasDatabase, HasAuthenticatedUser and HasBasicRequest on the object.

Of course the stack of objects depends on what the framework user needs, that’s why you need to use traits and cannot simply use a type Rq = .... To answer a static content, you only need a HasBasicRequest, but to query a user profile you need a HasDatabase + HasAuthenticatedUser + HasBasicRequest, and to handle a file upload you need a HasFileUpload.


#2

This would likely lead to a better design of the standard library wrt Deref-based “decay”.

Right now the situation is pretty bad - smart pointer have to delegate all the important traits and e.g. HashMap sports find_equiv, which requires that the Equiv implementer also has an identical Hash result (thankfully that doesn’t lead to memory unsafety if not respected).

Some version of this might even allow [T, ..N] to use the Show impl from [T] without calling .as_slice() manually (soon &*).


#3

There are some complications around the Self type that would have to be resolved before something like this could even come close to working. Consider the Clone trait: invoking clone on MyPointer<T> requires knowing how to construct a new MyPointer. It’s necessary but not sufficient to know how to clone T. Therefore, just because we know that T:Clone and MyPointer<T> : Deref<T> does not mean we can say that MyPointer<T> : Clone.


#4

I was thinking mostly of traits that do not mention Self in method signatures at all (modulo receivers). I can personally see this feature being quite useful even with that restriction.


#5

The restrictions are probably similar to the ones that apply when you use &Trait.

I think we can agree that traits that use Self should not be allowed. The &Trait case leads to think that methods that take ownership of self may also cause some troubles, but I can’t find an example.

Note that the issue with clone is already more or less here with Deref itself:

struct Foo(int);

impl Deref<int> for Foo {
    fn deref(&self) -> &int {
        let &Foo(ref val) = self;
        val
    }
}

fn main() {
    let f = Foo(5i);
    println!("{}", f.clone());    // prints 5 because we only clone the int
}