We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership I: Moves, Copy, Clone, Drop step 9 of 20
Giving ownership back
Two small functions that both have to hand ownership back to their caller.
pub fn split_owned(text: String, sep: char) -> (Vec<String>, usize)
pub fn first_owned(text: String) -> String
-
split_ownedsplitstextonsepand returns the owned pieces together with the number of characters in the original text.("a,b,c", ',')gives(["a", "b", "c"], 5). Splitting""gives([""], 0)— one empty piece, becausestr::splitalways yields at least one. -
first_ownedreturns the first whitespace-separated word as an ownedString, or""when there isn’t one.
Part 1: the tuple-return ceremony
The fixed helper eats its input:
fn shatter(text: String, sep: char) -> Vec<String>
so the naive body
let pieces = shatter(text, sep);
(pieces, text.chars().count()) // E0382
fails. The repair is one line moved: measure first, hand over second. That is the whole of part 1, and it is worth doing precisely because it is annoying. The Rust Book introduces the same shape early —
fn calculate_length(s: String) -> (String, usize) {
let length = s.len();
(s, length) // give it back, plus what you learned
}
— and then says, in effect, this is unbearable, here is &. It is unbearable.
Feel it now. When Track 3 lets you write fn calculate_length(s: &String) -> usize
you will know exactly what problem the ampersand solves, rather than treating it
as the syntax everyone happens to use.
Part 2: the error with no lifetimes in it
first_owned looks like it should be one line:
fn first_slice(text: String) -> &str {
text.split_whitespace().next().unwrap_or("")
}
That does not compile, and the two errors you get on the way are the first time the compiler reasons about lifetimes with no lifetime syntax anywhere in sight.
The first is E0106:
error[E0106]: missing lifetime specifier
|
| fn first_slice(text: String) -> &str {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value,
but there is no value for it to be borrowed from
Read the help line literally. A &str is a pointer into somebody else’s bytes.
The signature promises to return one, and the compiler is asking a reasonable
question: whose bytes? Not the caller’s — the only String in scope is text,
which this function owns.
Follow rustc’s first suggestion and write -> &'static str, and you get the
error the suggestion was warning you about:
error[E0515]: cannot return value referencing function parameter `text`
|
| text.split_whitespace().next().unwrap_or("")
| ----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| returns a value referencing data owned by the current function
| `text` is borrowed here
text is a local. It dies at the closing brace. A reference into it would
outlive the bytes it points at, and that is precisely the dangling pointer that
ownership exists to prevent. E0515 is the compiler catching a use-after-free
before it exists.
::: question So what makes -> String work, when -> &str cannot?
Because a String owns its bytes, and ownership can be handed out of a
function. Moving a String out is a return: the three stack words travel to the
caller, and the heap buffer they point at is untouched and now belongs to
whoever received them. Nothing dangles, because nothing was left behind.
.to_string() on the &str is the operation that turns “a view into bytes that
are about to die” into “a fresh buffer that outlives them”. It costs one
allocation and a copy of the word. That is the price of returning owned data,
and for a function like this it is the right price to pay.
The alternative — returning a borrow that stays valid — requires the caller to
own the text and hand you a reference to it. That is fn first(text: &str) -> &str,
which does compile, and which is Track 8’s subject. Notice that the signature is
what changes, not the body: whether a borrow can escape a function is decided
entirely by where the data lives.
:::
Watch out
The hidden cases include multi-byte text on both functions. Anything that
indexes into a str by byte offset will either produce wrong output or panic
on a character boundary — chars().count() and split_whitespace() are both
UTF-8-aware and do the right thing. And split_owned‘s count is over the
original text, separators included, not over the pieces.
Two clippy lints hover around this shape and are worth knowing by name.
let_and_return fires when you bind a value and immediately return it in the
next statement; needless_late_init fires when you declare a binding and only
initialise it later. Both push you toward writing the value where it is produced.
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.