Skip to content

← Ownership III: Lifetimes, Explicitly step 15 of 22

Easy Primitives

Make the lint happy: `'_` in return paths

Four one-line functions that return standard-library iterators. The bodies are already written and already correct. Every one of them still fails the gate, and not one of them contains a lifetime you wrote.

pub fn bytes_of(data: &[u8]) -> slice::Iter<u8>       // rejected
pub fn letters(s: &str)      -> str::Chars            // rejected
pub fn fields(s: &str, sep: char) -> str::Split<char> // rejected
pub fn pairs(v: &[i64])      -> slice::Chunks<i64>    // rejected

This is the single most confusing failure a beginner meets, because the error is about lifetimes and you never typed one.

Three syntaxes for one thing

A lifetime can appear in a signature in three different ways:

group how it looks examples
named you wrote a name &'a str, Person<'a>
elided you wrote a placeholder &str, &'_ str, Person<'_>
hidden nothing visible at all Person, Iter<u8>

Iter<u8> is the sting. std::slice::Iter is declared Iter<'a, T> — it has a lifetime parameter, and a path is allowed to omit it entirely. So -> slice::Iter<u8> really means -> slice::Iter<'_, u8> with the lifetime invisible. The rustc lint mismatched_lifetime_syntaxes — warn-by-default since 1.89, and a hard error here because the gate is -D warnings — fires when one lifetime is referred to using syntaxes from different groups in a single signature:

warning: hiding a lifetime that's elided elsewhere is confusing
help: use `'_` for type paths

In bytes_of(data: &[u8]) -> slice::Iter<u8>, the input’s lifetime is elided (&[u8]) and the output’s is hidden (Iter<u8>). Same lifetime, two groups, confusing. Write slice::Iter<'_, u8> and both are elided, and the lint goes quiet.

Note carefully what does not fire: fn f(x: &u8) -> &'_ u8 is fine. &u8 and &'_ u8 are both in the elided group. The lint is about grouping, not spelling.

'_ does not mean “no lifetime”

It means “there is a lifetime here; infer it.” That is why it is the fix, not a way to opt out. And note that elided_lifetimes_in_paths — the lint that would ban hidden lifetimes outright — is still allow-by-default, “because it has some known issues”. So '_ in paths is not mandatory in general. Only consistency within a signature is.

Honesty about the other fix

Naming the lifetime also silences the rustc lint, and it passes this gate:

pub fn bytes_of<'a>(data: &'a [u8]) -> slice::Iter<'a, u8>   // accepted

Both positions are then in the named group, which is consistent. clippy::needless_lifetimes does not fire on this shape, and clippy::elidable_lifetime_names — which would nudge you back to '_ — is pedantic, not part of the gate. So: '_ is the idiomatic answer and the one the compiler suggests, but you are not being graded on idiom here, you are being graded on consistency. Returning impl Iterator<Item = &u8> + '_ instead is also a legitimate design and would pass — it hides the concrete iterator type from your callers, which is a different tradeoff worth thinking about.

The one that trips people up

str::Split is declared Split<'a, P>two parameters, lifetime first. Split<'_, char>, not Split<char, '_> (a syntax error) and not Split<'_> (a missing type argument). Get the slot right.

The entry point

Iterators are not JSON-expressible, so run materialises all four:

pub fn run(v: Vec<i64>, s: String, sep: String)
    -> (Vec<u8>, Vec<String>, Vec<String>, Vec<Vec<i64>>)

In order: the bytes of s; each char of s as its own String; s split on the first character of sep; and v in chunks of two. Two details the tests check — "".split(',') yields one empty field, not zero, and a trailing odd element makes a chunk of length one.

Loading visualization…