Skip to content

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:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

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:

Abridged — real rustc 1.98.0 output for not_a_number.rs
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:

Abridged — real rustc 1.98.0 output for one_arm.rs, the note pointing into std dropped
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:

Abridged — real rustc 1.98.0 output for ignored.rs
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

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