Skip to content

← Errors Are Values step 12 of 24

Medium Primitives

`?` on Option, and bridging to Result

Two functions. One uses ? on Option end to end; the other bridges from Option into Result.

pub fn nth_word(s: String, n: usize) -> Option<String>
pub fn nth_word_len(s: String, n: usize) -> Result<usize, String>

nth_word returns the nth whitespace-separated word of the first line of s, owned, counting from zero. None if there is no first line, or no word at that index.

nth_word_len returns the byte length of that word, or Err("no word at index {n}").

So ("the cat sat", 1) gives Some("cat") and Ok(3); ("the cat sat", 9) gives None and Err("no word at index 9"). Only the first line is considered: in "alpha beta\ngamma delta", word index 2 does not exist.

? on Option

? is not a Result feature — it works on anything that implements the (still unstable) Try trait, and Option is one of them:

let line = s.lines().next()?;                  // -> &str, or return None
let word = line.split_whitespace().nth(n)?;    // -> &str, or return None
Some(word.to_string())

Two early exits, three lines, no nesting. And note what ? does on Option: it returns None from the enclosing function. No conversion happens — there is no error payload to convert. On Result the Err goes through From::from; on Option there is nothing to do.

The starter’s E0277, verbatim

error[E0277]: the `?` operator can only be used on `Result`s, not `Option`s,
              in a function that returns `Result`
   |
   |     let word = nth_word(s, n)?;
   |                              ^ use `.ok_or(...)?` instead

This is the single most common early ? failure, and the message is so good that learning to read it is most of the lesson. Rust will not silently pick an error value for you when an Option runs dry — you have to say what the absence means, and ok_or_else is where you say it:

let word = nth_word(s, n).ok_or_else(|| format!("no word at index {n}"))?;

The mirror-image error, for a ? on a Result inside an Option-returning function, reads “the ? operator can only be used on Options, not Results”. Its fix is .ok()?.

The version of this error that actually gets people stuck

Once you have a real error type rather than a String, the same mistake often stops looking like a ? problem at all:

error[E0271]: type mismatch resolving `<i32 as FromStr>::Err == MyErr`
   |
   |     let n: i32 = s.parse()?;
   |                    ^^^^^ expected `MyErr`, found `ParseIntError`

Same underlying cause — ? cannot get from the error you have to the error your function returns — but phrased in terms of an associated type (FromStr::Err) instead of in terms of ?. If you meet E0271 with == in it, mentally rewrite it as: “you asked ? to convert ParseIntError into MyErr, and there is no From impl that does that.” Writing that From impl is item 6.15, and it is where all of this becomes painless.

Why .unwrap() is not the bridge

The gate denies clippy::unwrap_used and clippy::expect_used for this problem, so nth_word(s, n).unwrap() will not compile. That is deliberate. unwrap on the None path does not bridge anything — it aborts the process. Bridging means choosing an error value, and ok_or/ok_or_else is the only API that makes you do it.

(These two lints are allow by default in clippy, and denied only in this track. Turning them on course-wide is what makes clippy feel arbitrary — the point is to force the habit while it is being learned.)

Notes

  • Eager vs lazy applies here too: ok_or(format!(…)) builds the message on every call including the successful ones. ok_or_else(|| format!(…)) builds it only on the None path.
  • ? inside a closure needs the closure’s return type to be inferable. If you get a bewildering inference error from a closure containing ?, annotate it: |x| -> Result<i32, String> { … }.
  • split_whitespace never yields an empty word and collapses runs of spaces, so " spaced out " has word 0 = "spaced".
  • str::len is bytes. "wörld" is 6 bytes and 5 characters.

Remember the grade is compile + tests + clippy -D warnings.

Loading visualization…