We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership III: Lifetimes, Explicitly step 15 of 22
Make the lint happy: '_ in return paths
This exercise has four one-line functions that return iterators from the standard library. Their bodies are already correct. Each one still fails the Rust gate, even though none of the signatures names a lifetime.
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
Your job is to fix the four return types, then write run, which calls all
four functions and collects their results.
What a lifetime parameter is doing here
A lifetime is the compiler's name for the region of code in which a borrow
is valid. Each of these iterators borrows from its input. For example,
data.iter() yields &u8 references that point into data, so the
iterator must not outlive data. That is why std::slice::Iter is declared
as Iter<'a, T>: the 'a parameter records which borrow the iterator
depends on. Chars<'a>, Split<'a, P> and Chunks<'a, T> have a lifetime
parameter for the same reason.
When you leave a lifetime out of a function signature, the lifetime elision rules fill it in. Here each function has exactly one borrowed input, so the rules connect the returned iterator's lifetime to that input. The starter signatures already mean the right thing. The problem is only how they are written.
Three ways to write the same lifetime
The compiler groups the ways a lifetime can appear in a signature:
| group | what you write | examples |
|---|---|---|
| named | a lifetime name | &'a str, Person<'a> |
| elided | a visible placeholder or a bare & |
&str, &'_ str, Person<'_> |
| hidden | nothing at all, in a type path that has a lifetime parameter | Person, Iter<u8> |
A type path such as slice::Iter<u8> is allowed to omit its lifetime
argument entirely, so -> slice::Iter<u8> means the same as
-> slice::Iter<'_, u8>, with the lifetime invisible.
The rustc lint mismatched_lifetime_syntaxes fires when one lifetime is
written using syntax from different groups within a single signature. It is
a warning by default (it was added in Rust 1.89). The Rust gate on this site
runs Clippy with -D warnings, which turns every warning into an error, so
this lint rejects the submission. For bytes_of, Rust 1.98 reports:
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 in the
elided group (&[u8]) and the output's is in the hidden group
(Iter<u8>). Writing slice::Iter<'_, u8> puts both in the elided group, and
the lint no longer fires.
The lint compares groups, not exact spelling. fn f(x: &u8) -> &'_ u8 does
not trigger it, because &u8 and &'_ u8 are both in the elided group.
'_ does not mean "no lifetime"
'_ means "a lifetime belongs here; apply the usual rules to choose it." In
these signatures the elision rules pick the input's lifetime, exactly as they
did when the lifetime was hidden. Adding '_ changes how the signature reads,
not what it means.
Rust does not require '_ in every path. A separate lint,
elided_lifetimes_in_paths, reports any hidden lifetime, but it is
allow-by-default (its documentation notes known issues with it), so it is off
unless a crate turns it on. Only mixing groups within one signature is
reported by default.
Other accepted fixes
Naming the lifetime also satisfies the 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. With Rust 1.98, Clippy's
default lints accept this signature. The Clippy lint that suggests replacing
such a name with '_, clippy::elidable_lifetime_names, is in the
pedantic group, which this gate does not enable. '_ is the shorter fix
and the one rustc's help message suggests, but either form passes.
You could also return impl Iterator<Item = &u8> (with or without + '_),
which passes as well. That is a different design choice: it hides the
concrete iterator type from callers, so they can use only the Iterator
methods. For this exercise, keep the concrete types and fix the lifetimes.
Where the lifetime argument goes
Lifetime arguments come before type arguments. str::Split is declared
Split<'a, P>, so the fix is Split<'_, char>. Writing Split<char, '_>
is rejected with error E0747 ("type provided when a lifetime was expected"),
and Split<'_> is rejected with E0107 because the type argument is missing.
The entry point
The tests exchange JSON, which cannot represent an iterator, so run
collects each one into a vector:
pub fn run(v: Vec<i64>, s: String, sep: String)
-> (Vec<u8>, Vec<String>, Vec<String>, Vec<Vec<i64>>)
Return, in this order:
- bytes: the UTF-8 bytes of
s, in order (bytes_of). - letters: each
charofsas its own one-characterString(letters). A Rustcharis a single Unicode scalar value, so a multi-byte character such aséis one entry here but two or more bytes in the first list. A letter written aseplus a combining accent is two entries. - fields:
ssplit on the first character ofsep, exactly asstr::splitdoes (fields). Any further characters insepare ignored. Adjacent separators, or a separator at the start or end, produce empty fields, and an emptysproduces one empty field (""), not zero fields. The tests always pass a nonemptysep. - pairs:
vin consecutive chunks of two, in order (pairs). Ifvhas an odd length, the last chunk has one element; an emptyvgives no chunks.
For example, run(vec![1, 2, 3, 4, 5], "a,b".into(), ",".into()) returns
bytes [97, 44, 98], letters ["a", ",", "b"], fields ["a", "b"] and
pairs [[1, 2], [3, 4], [5]].
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.