Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the differences between specifying lifetime parameters on an impl or on a method?

Tags:

rust

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?

like image 674
jtepe Avatar asked Jul 17 '15 07:07

jtepe


People also ask

What is the lifetime of a parameter?

... "the lifetime of a parameter ends when the function in which it is defined returns ", the suspend indeed returns to the caller as per control flow returns to the current coroutine caller or resumer.

What is a lifetime specifier in Rust?

Lifetimes are what the Rust compiler uses to keep track of how long references are valid for. Checking references is one of the borrow checker's main responsibilities. Lifetimes help the borrow checker ensure that you never have invalid references.

What is impl in Rust?

The impl keyword is primarily used to define implementations on types. Inherent implementations are standalone, while trait implementations are used to implement traits for types, or other traits. Functions and consts can both be defined in an implementation.

What is Anonymous lifetime Rust?

'_ , the anonymous lifetime Rust 2018 allows you to explicitly mark where a lifetime is elided, for types where this elision might otherwise be unclear. To do this, you can use the special lifetime '_ much like you can explicitly mark that a type is inferred with the syntax let x: _ = ..; .


1 Answers

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.

like image 70
Vladimir Matveev Avatar answered Sep 28 '22 15:09

Vladimir Matveev