Revision 2026-10-02
Published 2 October 2026
Why Option::map takes FnOnce¶
FnOnce is a closure that can be called at least once. It may consume what it captured,
so Rust can’t promise a second call. map calls f at most once (zero times for None),
so a one-shot closure is all it needs:
let label = String::from("car");
let result = Some(5).map(|n| {
drop(label); // closure consumes `label`, so it can only run once
n * 2
});
FnOnce is the least restrictive closure trait. Every closure implements it, and closures
that don’t consume their captures also implement FnMut and Fn. So map accepts both.
as_ref(): map without losing the Option¶
map takes self by value, so name.map(...) consumes name:
let name: Option<String> = Some("Karthik".to_string());
let len = name.map(|s| s.len());
// println!("{name:?}"); // error: use of moved value
as_ref() turns &Option<String> into Option<&String>, a borrow of the same string.
Nothing is copied or moved:
let name: Option<String> = Some("Karthik".to_string());
let len = name.as_ref().map(|s| s.len());
println!("{name:?}"); // still usable
println!("{len:?}"); // Some(7)
But doesn’t map still consume something?¶
Yes. map always consumes its receiver. What changes is which Option that is:
name Option<String> still owns the String
name.as_ref() Option<&String> new temporary holding a borrow
.map(...) consumes that temporary, not `name`
Consuming the temporary only drops a reference, so the String stays where it was.
Why the closure body doesn’t change¶
The closure’s parameter type follows the Option:
let name = Some(String::from("Karthik"));
name.clone().map(|s: String| s.len()); // Option<String> -> s: String
name.as_ref().map(|s: &String| s.len()); // Option<&String> -> s: &String
s.len() compiles either way. Why? Auto-deref.
Auto-deref¶
Method-call autoderef
When you write x.method(), Rust looks for method on the type of x, then
follows references (&T → T, and anything implementing Deref) until it finds
one, and auto-borrows x to match self. You never write (*s).len() or (&s).len().
String::len takes &self:
let owned: String = String::from("car");
let borrowed: &String = &owned;
owned.len(); // auto-borrow: String::len(&owned)
borrowed.len(); // already a ref: String::len(borrowed)
Both calls resolve to the same method, which is why the closure body is identical for
Option<String> and Option<&String>.
saturating_sub on u64¶
Unsigned subtraction that would go below zero panics in debug builds (and wraps in
release). saturating_sub clamps at 0 instead:
let a: u64 = 3;
let b: u64 = 5;
// a - b; // panics in debug: attempt to subtract with overflow
assert_eq!(a.saturating_sub(b), 0);
assert_eq!(b.saturating_sub(a), 2);
Result: why wrap the error in Err?¶
Result is a plain enum with two variants:
A function returning Result<(), RentalError> must hand back one of those variants:
Ok(()) (success, nothing to return) or Err(some_rental_error).
RentalError::TripAlreadyStarted alone is just the error value, so Err(...) is what
puts it in the failure case. Nothing converts it automatically:
pub fn start_trip(&mut self, order_id: &str, odometer_reading: u64) -> Result<(), RentalError> {
let booking = self
.bookings
.get_mut(&OrderId(order_id.to_string()))
.ok_or(RentalError::BookingNotFound)?; // Option -> Result, then `?`
if booking.start_odometer.is_some() {
return Err(RentalError::TripAlreadyStarted);
}
booking.start_odometer = Some(odometer_reading);
Ok(())
}
? goes the other way: on Err(e) it returns Err(e) from start_trip, and on
Ok(booking) it unwraps the value and carries on.