Skip to content

← Ownership I: Moves, Copy, Clone, Drop step 9 of 20

Medium Primitives

Giving ownership back

Implement two functions that take ownership of a string and return owned results:

pub fn split_owned(text: String, sep: char) -> (Vec<String>, usize)
pub fn first_owned(text: String) -> String

Split the text and count its characters

split_owned returns the pieces separated by sep, plus the number of Unicode scalar values in the original text, including separators. This is what text.chars().count() measures. It is not a byte count or necessarily the number of visible symbols: a letter followed by a combining accent contributes two scalar values.

Text and separator Pieces Count
"a,b,c", ',' ["a", "b", "c"] 5
"", ',' [""] 0
",a,", ',' ["", "a", ""] 3
"héllo,wörld", ',' ["héllo", "wörld"] 11

Keep empty pieces at the start, end and between adjacent separators. A separator is a Rust char, so it may be a non-ASCII scalar value.

Keep the supplied shatter helper and its signature:

fn shatter(text: String, sep: char) -> Vec<String>

Call it to produce the pieces. It takes ownership of text, so any measurement that uses text must happen before that call.

let pieces = shatter(text, sep);
(pieces, text.chars().count()) // Error: text has already moved.

This use after a move produces E0382. Compute the count first, then return the pieces and count together. There is no need to clone the input just to measure it.

Return the first word as owned text

first_owned returns the first nonempty whitespace-separated word, or an empty String if there is none. Use the whitespace rules of split_whitespace(), including Unicode whitespace. Do not include leading or trailing whitespace in the result.

For example, " hello world " gives "hello", "ünïcödé test" gives "ünïcödé", and an empty or whitespace-only input gives "".

The starter's helper tries to return a slice into a string it owns:

fn first_slice(text: String) -> &str {
    text.split_whitespace().next().unwrap_or("")
}

Its output reference has no input lifetime to borrow from, producing E0106. Changing the return type to &'static str does not repair the body: it then produces E0515 because the returned word borrows text, which is dropped when the function returns. A lifetime annotation does not keep that buffer alive.

Change or remove this helper. Convert the selected slice to an owned String while text is still available, then return it. The caller owns that result independently of the input's original buffer.

Moving an owner differs from copying a slice

Returning an existing String transfers ownership without requiring a copy of its contents. Converting a nonempty &str with to_string() creates owned storage and copies the selected bytes. An empty result does not require a buffer allocation. Returning owned data does not universally require a fresh allocation; here the straightforward solution copies a word out of the input.

A different API could borrow caller-owned text instead:

fn first_borrowed(text: &str) -> &str {
    text.split_whitespace().next().unwrap_or("")
}

That slice must remain valid for its uses, so the caller must keep the borrowed storage alive. This exercise requires the owned signatures above.

Use string iterators to handle UTF-8. Byte-range slicing can also be valid when both bounds are character boundaries, but arbitrary byte offsets can panic. Passing requires compilation, correct outputs and Clippy with warnings treated as errors.