What is the elided_lifetimes_in_paths lint for?

@amarao asked in another thread:

#![deny(elided_lifetimes_in_paths)] is a “allowed by default” lint (not even on warn by default!), that I like to enable to help me catch the following type of errors:

fn main ()
{
    let result = {
        let numbers = [42, 27];
        try_sum(&numbers)
    };
    match result {
        | Ok(sum) => println!("Sum is {}", sum),
        | Err(err_msg) => eprintln!("Error: {}", err_msg),
    }
}

fn try_sum (numbers: &[i32]) -> Result<i32, ErrMsg>
{
    numbers
        .iter().cloned()
        .try_fold(0, i32::checked_add)
        .ok_or("sum overflowed")
}

/// `ErrMsg` is an alias for `&str`:
type ErrMsg<'a> = &'a str;

This code fails to compile with:

error[E0597]: `numbers` does not live long enough
 --> src/main.rs:5:17
  |
3 |     let result = {
  |         ------ borrow later stored here
4 |         let numbers = [42, 27];
5 |         try_sum(&numbers)
  |                 ^^^^^^^^ borrowed value does not live long enough
6 |     };
  |     - `numbers` dropped here while still borrowed

Where does the problem come from?

The error message gives us some hint:

numbers dropped here while still borrowed

In other words, the return value of try_sum is still borrowing the input numbers:

fn try_sum (numbers: &[i32]) -> Result<i32, ErrMsg>
{
    numbers
        .iter().cloned()
        .try_fold(0, i32::checked_add)
        .ok_or("sum overflowed")
}

For some people, specially beginners, the reason may not be obvious, specially when more complex code is involved. But with #![deny(elided_lifetimes_in_paths)], we get the following error:

error: hidden lifetime parameters in types are deprecated
  --> src/main.rs:15:45
   |
15 | fn try_sum (numbers: &[i32]) -> Result<i32, ErrMsg>
   |                                             ^^^^^^- help: indicate the anonymous lifetime: `<'_>`

which gives:

fn try_sum (numbers: &[i32]) -> Result<i32, ErrMsg<'_>>

In other words, the function signature has an elided lifetime parameter in return position, which points towards the root of the problem:
there already was a single input elided lifetime parameter with &[i32] (I wish there was a lint for that too), and now that there is an output elided parameter, they “connect”, as dictated by lifetime elision rules:

If there is exactly one lifetime used in the parameters (elided or not), that lifetime is assigned to all elided output lifetimes.

leading to:

fn try_sum<'numbers> (numbers: &'numbers [i32]) -> Result<i32, ErrMsg<'numbers>>

for Rust, this means that the error message is borrowing from numbers, which is definitely not the case here.

Solution

Since our function is actually returning a &'static str on error, either we explicit it with ErrMsg<'static> or, in this case, declare ErrMsg to be always 'static:

type ErrMsg = &'static str;

ErrMsg, self::ErrMsg, or more generally, path::to::ErrMsg, is a path element

4 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.