Ok and Err: reading a Result¶
Level: 101 · for newcomers
One line: A Result<T, E> is either Ok holding the value you asked for or Err holding the reason you did not get it, and the compiler will not let you use it as the value until you have said what happens in the Err case.
use std::num::ParseFloatError;
fn read_field(text: &str) -> Result<f32, ParseFloatError> {
text.trim().parse()
}
fn main() {
match read_field("yes") {
Ok(value) => println!("the value is {value}"),
Err(error) => println!("not a number: {error}"), // not a number: invalid float literal
}
}
The whole type¶
Result is an ordinary enum in the standard library, the failure-shaped twin of Option:
Two type parameters: T is what success carries, E is what failure carries. Result<f32, ParseFloatError> is an f32, or a ParseFloatError saying why there is no f32. Unlike None, an Err always carries something. That difference is what Option vs Result is about.
Where you meet one first¶
Usually from parse, because text is input and input can be wrong. Block 1 of the verified output sends five strings through read_field: "1", "-1.5" and " 1 " come back Ok, "x" comes back Err with kind Invalid, and "" with kind Empty. Files, network calls, and anything else that can fail for a reason outside your program return a Result too.
A Result is not the value¶
Three things the compiler refuses or warns about, and each one points at a way out.
Using it where the value is expected is E0308, and rustc suggests expect:
error[E0308]: mismatched types
--> not_a_number.rs:8:22
|
8 | let value: f32 = read_field("1");
| --- ^^^^^^^^^^^^^^^ expected `f32`, found `Result<f32, ParseFloatError>`
| |
| expected due to this
|
help: consider using `Result::expect` to unwrap the `Result<f32, ParseFloatError>` value, panicking if the value is a `Result::Err`
|
8 | let value: f32 = read_field("1").expect("REASON");
| +++++++++++++++++
A match with only the Ok arm is E0004, as with Option:
error[E0004]: non-exhaustive patterns: `Err(_)` not covered
--> one_arm.rs:8:11
|
8 | match read_field("1") {
| ^^^^^^^^^^^^^^^ pattern `Err(_)` not covered
Ignoring it completely compiles, with a warning. Result is marked #[must_use], so a call whose Result nobody looks at gets flagged:
warning: unused `Result` that must be used
--> ignored.rs:8:5
|
8 | read_field("1");
| ^^^^^^^^^^^^^^^
|
= note: this `Result` may be an `Err` variant, which should be handled
The ways out¶
| You want | Write | On Err |
|---|---|---|
| to handle both cases here | match, or if let Ok(v) |
the arm you wrote runs |
| the error to go to your caller | ? |
the function returns the Err |
| a fallback value | unwrap_or, unwrap_or_else, unwrap_or_default |
you get the fallback |
| the value, and a crash if you were wrong | unwrap, expect |
the program panics |
an Option instead |
.ok() |
None, and the reason is dropped (usually a downgrade) |
| to ask without taking the value out | .is_ok(), .is_err() |
false, true |
The error is a value¶
Err holds a real value you can print, store or inspect. Most error types implement both Debug and Display: {:?} gives ParseFloatError { kind: Invalid }, for a developer, and {} gives invalid float literal, for a user (block 3). The match at the top of this page prints the Display form. The Error trait is what the two forms are part of.
The verified output¶
Verified output of ok_and_err.rs — regenerated by tools/run_examples.py, never hand-typed.
1. What parse hands back
read_field("1" ) = Ok(1.0)
read_field("-1.5") = Ok(-1.5)
read_field(" 1 " ) = Ok(1.0)
read_field("x" ) = Err(ParseFloatError { kind: Invalid })
read_field("" ) = Err(ParseFloatError { kind: Empty })
2. match reads both shapes, and must
"-1": Ok, the value is -1
"yes": Err, because invalid float literal
3. The error is a value too: Debug for a developer, Display for a user
{:?} -> ParseFloatError { kind: Invalid }
{} -> invalid float literal
4. Asking without taking the value out
good.is_ok() = true, bad.is_err() = true
good.ok() = Some(1.0), bad.ok() = None <- Result to Option, error dropped
If you are coming from another language¶
Python. float("x") raises ValueError, and nothing in the signature says it might. A Result is that exception turned into a return value: Err(ParseFloatError) is the ValueError, and the match is the try/except. The difference is that the compiler knows. You cannot forget the except, because E0004 and the #[must_use] warning above are the reminder, and there is no way for the error to pass silently up through three callers.
ABAP. The nearest thing is sy-subrc after a statement, or a method that raises a class-based exception such as cx_sy_conversion_no_number. Result joins the two: the return code and the value arrive together, and you cannot read the value without checking which one you got.
See also¶
SomeandNone— the same reading, for a value that may be absentOptionvsResult— which of the two a function should return- What
unwrapdoes — the shortest way out, and what it costs - The
?operator — pass theErrto your caller in one character - Parsing a string —
parse, theResultmost people meet first maincan return aResult— using?insidemainstd::result↗- Rust: The Practical Guide, chapter 5 exercises, run —
Ok(res)andErr(e)written into the book's empty match arms, on acalculate(self)that consumes its shape
Po polsku¶
Result<T, E> to zwykły enum z dwoma wariantami: Ok(T) niesie wartość, o którą prosiliśmy, a Err(E) — powód, dla którego jej nie ma. To odpowiednik Option dla operacji, które mogą się nie udać: None nie niesie niczego, a Err zawsze niesie błąd. Najczęściej pierwszym spotkaniem jest parse, bo tekst jest wejściem, a wejście bywa błędne — "1" daje Ok(1.0), "x" daje Err(ParseFloatError { kind: Invalid }), a pusty łańcuch Err(ParseFloatError { kind: Empty }).
Result nie jest wartością: przypisanie go do f32 to E0308 (kompilator sam podpowiada expect), match bez gałęzi Err to E0004, a zignorowany Result daje ostrzeżenie unused Result that must be used, bo typ ma atrybut #[must_use]. Wyjścia są cztery: obsłużyć oba przypadki (match, if let), przekazać błąd wyżej operatorem ?, podać wartość zastępczą (unwrap_or i rodzina) albo zaryzykować panikę (unwrap, expect). Błąd jest zwykłą wartością: {:?} pokazuje go programiście, {} — użytkownikowi.
Dla znających Pythona: Result to wyjątek zamieniony w wartość zwracaną — Err to ValueError, a match to try/except, tyle że kompilator pilnuje, żeby except nie zabrakło. W ABAP-ie najbliżej jest sy-subrc albo wyjątek klasowy, z tą różnicą, że tutaj kod powrotu i wartość przychodzą razem.
Szukaj po polsku: typ wynikowy · obsługa błędów w Ruscie · rust Result Ok Err · rust unused Result that must be used · rust E0004 Err not covered · rust parse Result