Skip to content

← Ownership III: Lifetimes, Explicitly step 3 of 22

Easy Primitives

Borrow it or own it: your first lifetime decision

Two tiny functions. The whole exercise is the difference between their return types.

pub fn first_word(s: &str) -> &str   // borrows: a slice OF the input
pub fn shout(s: &str) -> String      // owns: brand-new data

first_word returns everything before the first ASCII space, or the whole string if there is no space. shout returns s uppercased with one ! appended.

Why these two, together

Every function that takes a &str and produces text has to answer one question before anything else: is my output a view into my input, or is it new data?

  • first_word("hello world") is "hello" — those five bytes are already in the caller’s string. Copying them would be waste. Return a &str that points at the caller’s bytes.
  • shout("hello world") is "HELLO WORLD!" — those bytes do not exist anywhere yet. Uppercasing can change byte length (ßSS), and the ! is invented. There is nothing to point at, so the function must allocate and hand back a String.

Get this fork wrong in the borrowing direction and you meet E0515: cannot return value referencing local variable. That is exactly what the starter does, on purpose. Read the error before you touch anything:

let owned = s.to_string();   // a fresh String that lives inside this call
&owned[..i]                  // a slice of it...

…and the function returns. owned is dropped at the closing brace, so the slice you handed back would point at freed memory. The compiler is not being fussy; it caught a use-after-free at compile time. No annotation can fix this — the caller decides how long the returned reference must be valid, and a local cannot satisfy a promise made to the caller. The fix is to slice the input, which the caller already owns and which outlives the call.

The pins

The last two lines of the file are signature pins:

const _: for<'a> fn(&'a str) -> &'a str = first_word;
const _: fn(&str) -> String = shout;

Each one coerces your function into a function-pointer type. If your signature does not match — say you “fixed” first_word by returning String — the coercion fails to compile. Do not edit or delete them; a submission without them is a failed submission. They are how this problem grades a decision that no runtime assertion could ever see.

Edges the tests check

  • ""first_word is "", shout is "!".
  • " lead" (leading space) → first_word is "", not "lead". The first space is at index 0.
  • "trailing ""trailing".
  • Multibyte input must survive. find(' ') returns a byte index, and slicing a str at a non-character boundary panics — here you are slicing at the position of an ASCII space, which is always a valid boundary, so you are safe. It is worth knowing why you are safe rather than assuming it.

A note on tactics: s.split(' ').next().unwrap() looks tempting. unwrap is not linted by default here, but reaching for it whenever you are “pretty sure” is how panics get shipped. find + match, or unwrap_or, says what you mean without the gamble.

Loading visualization…