Why lifetimes required here?

Is the reason I have to specify lifetimes in the longest function below that Rust doesn't want to assume that every value in the strings parameter array has the same lifetime? I was thinking lifetimes are only required in function signatures where two or more arguments are references, the return type is a reference, and one of the reference arguments is returned.

fn longest<'a>(strings: &'a [&str]) -> &'a str {
    strings
        .iter()
        .fold("", |acc, s| if s.len() > acc.len() { s } else { acc })
}

fn main() {
    let fruits = ["apple", "banana", "cherry", "date"];
    let result = longest(&fruits);
    println!("longest is {}", result);
}

(Playground)

Output:

longest is banana

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.12s
     Running `target/debug/playground`

fn longest<'a>(strings: &'a [&str]) -> &'a str

is actually the same as

fn longest<'a, 'b>(strings: &'a [&'b str]) -> &'a str

so there are two lifetimes to choose from for the return type.

this is not 100% accurate. The rules are

Elision rules are as follows:

  • Each elided lifetime in input position becomes a distinct lifetime parameter.
  • If there is exactly one input lifetime position (elided or not), that lifetime is assigned to all elided output lifetimes.
  • If there are multiple input lifetime positions, but one of them is &self or &mut self , the lifetime of self is assigned to all elided output lifetimes.
  • Otherwise, it is an error to elide an output lifetime.

(source)

So the relevant condition is whether there is more than one "input lifetime position".

These lifetime positions can be from reference types as well as from other types with lifetime parameters, e.g.

fn foo(x: SomeType<'_>)
3 Likes

They’re both part of the same compund type, but there are two references here, and you probably want to move your annotation to the other one:

fn longest<'a>(strings: & [&'a str]) -> &'a str {
    strings
        .iter()
        .fold("", |acc, s| if s.len() > acc.len() { s } else { acc })
}

In your version, the slice must be kept alive while the return value exists, which is more restrictive than necessary. This updated version only requires the owner of the actual string data to be kept alive, but the list of candidates can be dropped.

5 Likes