We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Errors Are Values step 5 of 24
Borrowing through Option
Summarise two optional fields without consuming either of them.
pub fn summarize(
name: Option<String>,
tags: Option<Vec<String>>,
) -> (usize, Option<String>, Vec<usize>)
Return three things:
-
the length of
namein bytes, or0if there is no name; -
nameitself, handed straight back — sameOption, sameString; - the byte length of each tag, or an empty vector if there are no tags.
Requirement 2 is the whole exercise. You cannot compute the length by consuming the name, because you have to give it back afterwards.
Read E0507 first
The starter does not compile:
error[E0507]: cannot move out of `*s` which is behind a shared reference
|
| let owned: Option<String> = name.as_ref().map(|s| *s);
| ^^
| move occurs because `*s` has type
| `String`, which does not implement
| the `Copy` trait
This is the most demoralising family of beginner error in Rust, because the code reads correctly. Here is what actually happened, in order:
-
nameis anOption<String>you own. -
name.as_ref()turns&Option<String>intoOption<&String>— a new Option, borrowing the same String. Nothing moved. This is the whole point ofas_refand it is the single most useful method onOption. -
.map(|s| *s)then hands your closure an&Stringand you write*s, which says “give me theStringitself”. You cannot take aStringout of a borrow: whoever you borrowed it from still expects it to be there.
The related errors you will meet on the way out: E0382 (“use of moved
value”) if you consume name with map and then try to return it,
E0596 (“cannot borrow as mutable”) if you reach for as_mut without a
mut binding, and E0308 whenever &String and String get confused.
The five methods, and the one distinction that matters
let name: Option<String> = Some("ada".to_string());
name.as_ref() // Option<&String> — borrow the contents
name.as_mut() // Option<&mut String> (needs `let mut name`)
name.as_deref() // Option<&str> — borrow, then deref-coerce
and going the other way, from a borrowed Option back to an owned one:
let borrowed: Option<&String> = name.as_ref();
borrowed.cloned() // Option<String> — needs T: Clone
let n: Option<&i32> = Some(&1);
n.copied() // Option<i32> — needs T: Copy, no allocation
as_deref is as_ref followed by a deref: Option<String> becomes
Option<&str>, Option<Vec<T>> becomes Option<&[T]>. It only works where
T: Deref, so those two plus Box<T> and friends — not for an arbitrary
struct. It is worth reaching for because &str and &[T] are the types the
rest of the standard library actually wants.
Once you have Option<&str>, this whole problem is two lines:
let len = name.as_deref().map_or(0, str::len);
and for the tags, as_deref() gives you Option<&[String]>, whose
unwrap_or_default() is the empty slice — no allocation, no if.
Cloning is not the answer here
The obvious escape is name.clone(), compute on the copy, return the
original. It compiles. It also allocates a whole string to read one integer
off it, and #![deny(clippy::redundant_clone)] at the top of the file will
reject it:
error: redundant clone
.clone() is not forbidden in Rust and never should be — but it should be a
decision, not a reflex you reach for when the borrow checker says no. Almost
every “I had to clone it” in a beginner’s code is an as_ref that was never
learned.
An API rule worth adopting today
Take Option<&T>, not &Option<T>:
fn bad(name: &Option<String>) -> usize { … } // clippy::ref_option
fn good(name: Option<&str>) -> usize { … }
&Option<T> forces the caller to have an Option in hand. Option<&T> is
strictly more general — the caller can produce one from an Option<T> with
.as_deref(), from an &Option<T> with .as_deref(), or out of thin air
with Some(&x). And a caller who only has a &T cannot invent an &Option<T>
at all without allocating one.
Notes
-
str::lencounts bytes, not characters."héllo"is 6 bytes and 5 chars. The tests check this; usechars().count()when you want characters. -
map_or(default, f)ismap(f).unwrap_or(default)in one call. Its argument order surprises everyone once: the default comes first. -
useless_asrefis the lint for the opposite mistake — callingas_ref()where the value is already a reference.
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.