Failure is not an Option<T>, but a Result<T, E>.
Result is the sibling of Option. Where Option says "maybe a value,
maybe nothing", Result says "maybe a value, maybe an error":
enum Result<T, E> {
Ok(T),
Err(E),
}
This is how Rust handles fallible operations. There are no exceptions. A function that can fail says so in its signature, and the caller has to deal with both branches.
fn parse_port(input: &str) -> Result<u16, &'static str> {
match input.parse::<u16>() {
Ok(n) if n > 0 => Ok(n),
Ok(_) => Err("port must be greater than 0"),
Err(_) => Err("not a valid number"),
}
}
The snippet above sneaks in three pieces of syntax that don't have their own chapter, so it's worth pausing on each:
&'static strThis is a &str whose lifetime is 'static: a fancy way of saying
"this string lives for the entire duration of the program." String
literals like "port must be greater than 0" are baked into the
binary, so they qualify. For now, treat &'static str as the right
type to use for hard-coded error messages. Lifetimes in general get a
more careful treatment in later chapters.
parse::<u16>().parse() doesn't know which type you want to parse into, so you tell
it with the funny-looking ::<T> syntax. It's just a way to spell out a
generic type argument at the call site:
let n = "42".parse::<u16>().unwrap();
// equivalent if the type can be inferred from context:
let n: u16 = "42".parse().unwrap();
You'll see this anywhere a function returns T and the type isn't clear
from the surrounding code.
Ok(n) if n > 0 => ...The if n > 0 clause on a match arm is called a guard. The arm only
fires when both the pattern matches and the guard is true. Without it
you'd need a nested if inside the arm body, which reads worse.
match n {
x if x < 0 => "negative",
0 => "zero",
_ => "positive",
}
ResultThe &'static str you see for the error type is the simplest possible
error: a borrowed string literal. Real applications usually define their
own error enums, but &'static str is fine while you're learning.
Patterns to handle a Result:
match safe_divide(10.0, 0.0) {
Ok(n) => println!("got {n}"),
Err(e) => println!("oops: {e}"),
}
let n = safe_divide(10.0, 2.0).unwrap_or(0.0);
if let Ok(n) = safe_divide(10.0, 2.0) {
println!("got {n}");
}
Result has many of the same combinators as Option: .map, .map_or,
.and_then, .unwrap_or. Once you're comfortable with this chapter, the
? operator (chapter 19) will let you chain fallible operations without
the boilerplate.
The simplest way to produce a Result: an if checks the failure
case, and the else branch returns Ok(...).
The signature is the interesting part: &'static str for the error
is the simplest possible error type and is fine while you're learning.
Useful from the standard library
- The
ResultconstructorsOk(value)andErr(message)are in the prelude, so you can use them without importing anything.f64 == 0.0is the bounds check. Floating-point comparison has plenty of nasty edge cases in general, but exact zero is fine.Result::is_erris what the test uses; you don't need it inside the function.
Same idea as safe_divide, but returning an owned String. Notice
you can mix Ok(String::from("...")) and Err("...") in the same
function: the success and error types are independent.
Useful from the standard library
str::is_emptyis the cleanest way to detect an empty filename.String::fromor.to_string()turns the literal"config content"into the ownedStringtheOkarm needs.
Now the Ok value is a borrow of the input. The &str in the return
type implicitly borrows from email, so the compiler infers a
lifetime linking input and output via lifetime elision. Chapter 12
makes this explicit; for now, just notice the function compiles even
though no lifetimes appear in the signature.
Useful from the standard library
str::containstakes achar(or another&str) and answers yes/no. Soemail.contains('@')is exactly the check you need.- The
Okbranch can return the input slice directly: it's already a&strwith the right lifetime. Noto_string()allocation needed.
This is the hardest function in the chapter; the previous three were
warmups. More than one thing can go wrong, and they need different
error messages. Strip the optional % first, then parse::<u8>()
the rest, then bounds-check. Each step is its own potential Err.
Note: the error type here is &'static str, which means the message
has to be a string literal. If you find yourself wanting
format!("{input} is out of range") in an Err, you'd need to
change the return type to Result<u8, String>. Stick with literals
for this exercise.
Useful from the standard library
str::strip_suffixremoves a trailing pattern if present and returnsOption<&str>.input.strip_suffix('%').unwrap_or(input)peels the%when there is one.str::parseis the canonical "may fail to parse" call. The turbofish (parse::<u8>()) tells it which numeric type to produce.u8already rejects negative numbers and anything above255, which catches a couple of cases for free.Result::map_errswaps the error type without touchingOk. Handy to turn the parser's error into your own static message.- A bounds check
if n > 100 { return Err("...") }finishes the job; theu8type already takes care ofn < 0.
You produced Results with simple if checks, returned an owned
String in the Ok arm, borrowed from the input via lifetime
elision, and combined strip_suffix, parse, and a bounds check
into a real validating parser.
What we learned
Result<T, E>is how Rust expresses fallibility. There are no exceptions; a function that can fail says so in its signature.Ok(value)andErr(error)are the constructors. Both are in the prelude.Resulthas the same combinator family asOption:unwrap_or,map,map_or, plusmap_errfor transforming the error side andokto drop the error and convert toOption<T>.&'static stris the cheapest error type: a borrowed string literal that lives forever. Real applications usually graduate to enums orString-based errors, but this is a fine starting point.- The turbofish (
parse::<u8>()) spells out a generic type argument at the call site when the type isn't clear from context.- Match guards (
Ok(n) if n > 0 => ...) attach a boolean condition to a pattern. The arm only fires when both hold.- The
?operator (chapter 19) will let you chain fallible calls without writingmatchevery time. For now,matchis fine.