15
votes

In Rust 1.3.0, the Deref trait has the following signature in the documentation:

pub trait Deref {
    type Target: ?Sized;
    fn deref(&'a self) -> &'a Self::Target;
}

I would implement it without naming the lifetimes, since they get elided anyway. However, in the docs example it looks like this:

use std::ops::Deref;

struct DerefExample<T> {
    value: T
}

impl<T> Deref for DerefExample<T> {
    type Target = T;

    fn deref<'a>(&'a self) -> &'a T {
        &self.value
    }
}

fn main() {
    let x = DerefExample { value: 'a' };
    assert_eq!('a', *x);
}

This works all well and good, but if I specify the lifetime parameter 'a on the impl instead of the method:

struct DerefExample<T> {
    value: T
}

impl<'a, T> Deref for DerefExample<T> {
    type Target = T;

    fn deref(&'a self) -> &'a T {
        &self.value
    }
}

I get the following error:

error[E0308]: method not compatible with trait
  --> src/main.rs:10:5
   |
10 | /     fn deref(&'a self) -> &'a T {
11 | |         &self.value
12 | |     }
   | |_____^ lifetime mismatch
   |
   = note: expected type `fn(&DerefExample<T>) -> &T`
              found type `fn(&'a DerefExample<T>) -> &'a T`
note: the anonymous lifetime #1 defined on the method body at 10:5...
  --> src/main.rs:10:5
   |
10 | /     fn deref(&'a self) -> &'a T {
11 | |         &self.value
12 | |     }
   | |_____^
note: ...does not necessarily outlive the lifetime 'a as defined on the impl at 7:1
  --> src/main.rs:7:1
   |
7  | / impl<'a, T> Deref for DerefExample<T> {
8  | |     type Target = T;
9  | |
10 | |     fn deref(&'a self) -> &'a T {
11 | |         &self.value
12 | |     }
13 | | }
   | |_^

This confuses me. The method's signature is no different than the one from the docs. In addition, I thought that the difference between specifying the lifetime parameter on the impl or on the method directly is in the scope of the parameter only, so it can be used in the entire impl block instead of just the method. What am I missing here?

1
These answers to different questions might also answer your question: stackoverflow.com/questions/24847331/… and stackoverflow.com/questions/30273850/…oli_obk

1 Answers

14
votes

Yes, there is a difference.

The method's signature is no different than the one from the docs.

The fact that it looks like this in docs is a fault of rustdoc, and has since been resolved.

If you press [src] link in the upper right corner of the documentation, you will be redirected to the actual source of Deref, which looks as follows (I've removed extra attributes and comments):

pub trait Deref {
    type Target: ?Sized;
    fn deref<'a>(&'a self) -> &'a Self::Target;
}

You can see that deref() is declared to have a lifetime parameter.

I thought that the difference between specifying the lifetime parameter on the impl or on the method directly is in the scope of the parameter only.

And this is wrong. The difference is not in scope only. I don't think I will be able to provide convincing side-by-side examples where a semantic difference is visible, but consider the following reasoning.

First, lifetime parameters are no different from generic type parameters. It is no coincidence that they use similar declaration syntax. Like generic parameters, lifetime parameters participate in the method/function signature, so if you want to implement a trait which has a method with lifetime parameters, your implementation must have the same lifetime parameters as well (modulo possible renaming).

Second, lifetime parameters in impl signature are used to express different kinds of lifetime relationship than those on functions. For methods, it is always the caller who determines the actual lifetime parameter they want to use. It is, again, similar to how generic methods work - the caller may instantiate its type parameters with any type they need. It is very important, for Deref in particular - you would want that anything which implements Deref may be dereferenced with the lifetime of the reference the method is called on, not something else.

With impl, however, lifetime parameters are chosen not when the method which uses this parameter is called, but when the appropriate impl is chosen by the compiler. It may do so (and usually does so) based on the type of the value, which precludes the user from specifying arbitrary lifetimes when the method is called. For example:

struct Bytes<'a>(&'a [u8]);

impl<'a> Bytes<'a> {
    fn first_two(&self) -> &'a [u8] {
        &self.0[..2]
    }
}

Here, the first_two() method returns a slice with a lifetime of the value which is stored inside the Bytes structure. The caller of the method can't decide which lifetime they want - it is always fixed to the lifetime of the slice inside the structure this method is called on. It is also impossible to bring the lifetime parameter down to the method while keeping the same semantics, I guess you can see why.

In your case the lifetime parameter you specified does not participate either in the signature of the impl nor in any associated types, so it theoretically could be used as if it was declared on each function separately (because it can be arbitrary when the method is called), but then the reasoning about method signatures (provided above) kicks in.