13
votes

I'm implementing a trait for a reference type using a Rust older than 1.31. Why does Rust want an explicit lifetime when I tell it what reference type I'm implementing the trait for?

Here's a simple example. A struct Inches, an implementation of the Add trait for &Inches, and a function that uses that implementation.

Initial example

(Rust playground link)

use std::ops::Add;

struct Inches(i32);

// this would work: impl<'b> Add for &'b Inches
impl Add for &Inches {
    type Output = Inches;

    fn add(self, other: &Inches) -> Inches {
        let &Inches(x) = self;
        let &Inches(y) = other;

        Inches(x + y)
    }
}

// lifetime specifier needed here because otherwise 
// `total = hilt + blade` doesn't know whether `total` should live
// as long as `hilt`, or as long as `blade`.
fn add_inches<'a>(hilt: &'a Inches, blade: &'a Inches) {
    let total = hilt + blade;
    let Inches(t) = total;
    println!("length {}", t);
}

fn main() {
    let hilt = Inches(10);
    let blade = Inches(20);

    add_inches(&hilt, &blade);
}

Compilation fails with the following error:

error: missing lifetime specifier [E0106]
    impl Add for &Inches {
                 ^~~~~~~

I add the missing lifetime specifier (still doesn't compile)

// was: impl Add for &Inches {
impl Add for &'b Inches {
    ...
}

Compilation error:

error: use of undeclared lifetime name `'b` [E0261]
    impl Add for &'b Inches {

I declare the lifetime on the impl (now it compiles)

(Rust playground link)

// was: impl Add for &'b Inches {
impl<'b> Add for &'b Inches {
    ...
}

This, finally, compiles correctly.

My question

Why is &Inches in impl Add for &Inches considered to lack a lifetime specifier? What problem is solved by telling the compiler that this Add method is for &Inches with some unspecified non-static lifetime 'b, and then never referring to that lifetime anywhere else?

1
The lifetime of 'b is supplied (at compile-time of course) as 'a in the + operation, allowing the compiler to enforce/ensure the lifetime requirements of 'b out to 'a, and then out to the variables in main, right? So, 'b is not unused, per se, it is an expression of a requirement that callers (transitively) must honor.Erik Eidt
Your question already says what version it applies to: I'm implementing a trait for a reference type using Rust 1.30.Shepmaster

1 Answers

12
votes

Rust 1.31 and above

The reason is simple: it wasn't implemented until Rust 1.31.

Now, the initial example compiles, and you can write impl Add for &Inches instead of impl<'b> Add for &'b Inches. This is because 1.31.0 stabilized new lifetime elision rules.

Before Rust 1.31

If you look at the RFC for lifetime elision you can see that your use case should be covered:

impl Reader for BufReader { ... }                       // elided
impl<'a> Reader for BufReader<'a> { .. }                // expanded

However, I tried in the playground and it doesn't work. The reason is that it's not implemented yet.

I grepped Rust's source code for such cases, but there are suprisingly few of them. I could only find this series of implementations for Add on native types:

impl Add<u8> for u8
impl<'a> Add<u8> for &'a u8
impl<'a> Add<&'a u8> for u8
impl<'a, 'b> Add<&'a u8> for &'b u8

As you can see the lifetimes are all explicit here; no elision happens.

For your specific problem, I believe you'll have to stick with explicit lifetimes until the RFC implementation is done!