ZetCode

Rust unwrap function

last modified October 25, 2026

In this article we show how to use the unwrap method and its alternatives in Rust.

Rust has no null values and no exceptions. A value that may be missing is represented by Option<T>, which is either Some(T) or None. An operation that may fail returns Result<T, E>, which is either Ok(T) or Err(E). To use the value inside, the program must deal with the other case.

The unwrap method is the shortest way to do it. It returns the value inside Some or Ok; for None or Err, it panics: the program prints an error message and the current thread stops. The unwrap method is useful in examples, prototypes, and tests, and when the value cannot be missing. In other code, the absent value should be handled with a default, a match, or the ? operator.

The examples were tested with Rust 1.97 and the 2024 edition. They are run as Cargo projects with cargo run -q. Panic messages contain the ID of the thread, for example thread 'main' (66887), which is different on each run.

Unwrapping a value

Many functions of the standard library return an Option or a Result. When the value is present, unwrap returns it.

main.rs
fn main() {
    let words = ["sky", "cloud", "rain"];

    let first = words.first().unwrap();
    let pos = words.iter().position(|w| *w == "cloud").unwrap();
    let n: u32 = "42".parse().unwrap();

    println!("first: {first}");
    println!("position of cloud: {pos}");
    println!("parsed: {n}");
}

We get the first element of an array, the position of a word, and a parsed number.

let first = words.first().unwrap();
let pos = words.iter().position(|w| *w == "cloud").unwrap();

The first and position methods return an Option, because the array may be empty and the word may not be found. Here the values are present.

let n: u32 = "42".parse().unwrap();

The parse method returns a Result, because the string may not contain a valid number.

$ cargo run -q
first: sky
position of cloud: 1
parsed: 42

Unwrapping None

Calling unwrap on None causes a panic.

main.rs
fn main() {
    let words = ["sky", "cloud", "rain"];

    let pos = words.iter().position(|w| *w == "snow").unwrap();
    println!("position of snow: {pos}");
}

The word "snow" is not in the array, so position returns None.

let pos = words.iter().position(|w| *w == "snow").unwrap();

The panic message tells us that Option::unwrap was called on a None value and gives the file, line, and column of the call. The message does not say which value was missing, which makes such panics hard to debug in larger programs.

$ cargo run -q

thread 'main' (68515) panicked at src/main.rs:4:55:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Unwrapping an error

When unwrap is called on an Err, the panic message contains the debug representation of the error.

main.rs
fn main() {
    let vals = ["12", "7", "x3"];

    for v in vals {
        let n: i32 = v.parse().unwrap();
        println!("{n}");
    }
}

The third string is not a valid number.

let n: i32 = v.parse().unwrap();

The first two values are printed; then the program panics. The message shows the ParseIntError with the InvalidDigit kind. The error type must implement the Debug trait, otherwise unwrap is not available.

$ cargo run -q
12
7

thread 'main' (68526) panicked at src/main.rs:5:32:
called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The exit code

A panic in the main thread ends the program with the exit code 101.

main.rs
use std::env;

fn main() {
    println!("start");

    let name = env::args().nth(1).unwrap();

    println!("hello {name}");
}

The program expects a name on the command line. Without it, nth(1) returns None.

let name = env::args().nth(1).unwrap();

The code after the panic does not run. Before the program ends, the stack is unwound and the destructors of the live values run. With the RUST_BACKTRACE=1 environment variable, the panic message also contains a backtrace.

$ cargo run -q
start

thread 'main' (68537) panicked at src/main.rs:6:35:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
$ echo $?
101
$ cargo run -q -- Jane
start
hello Jane

The expect method

The expect method works like unwrap, but it takes a message that is printed when the program panics.

main.rs
use std::env;

fn main() {
    let home = env::var("HOME").expect("HOME should be set");
    println!("home: {home}");

    let port: u16 = "80800".parse().expect("port should be a number from 0 to 65535");
    println!("port: {port}");
}

We read an environment variable and parse a port number.

let home = env::var("HOME").expect("HOME should be set");

The message should describe what was expected; the convention is to write it as a statement of the expectation, such as "HOME should be set".

let port: u16 = "80800".parse().expect("port should be a number from 0 to 65535");

The number does not fit into u16. The panic message contains our message followed by the error.

$ cargo run -q
home: /home/jano

thread 'main' (68559) panicked at src/main.rs:7:37:
port should be a number from 0 to 65535: ParseIntError { kind: PosOverflow }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Default values

The unwrap_or method returns a given default value instead of panicking; unwrap_or_default returns the default value of the type.

main.rs
use std::collections::HashMap;
use std::env;

fn main() {
    let prices = HashMap::from([("apple", 3), ("plum", 2)]);

    let apple = prices.get("apple").copied().unwrap_or(0);
    let pear = prices.get("pear").copied().unwrap_or(0);
    println!("apple: {apple}, pear: {pear}");

    let width: u32 = "abc".parse().unwrap_or(80);
    println!("width: {width}");

    let n: i32 = "".parse().unwrap_or_default();
    let editor = env::var("NO_SUCH_VARIABLE").unwrap_or_default();
    println!("{n} {editor:?}");
}

We look up prices in a map and parse strings with a fallback value.

let pear = prices.get("pear").copied().unwrap_or(0);

The get method returns Option<&i32>. The copied method turns it into Option<i32>, and the missing key gives zero.

let width: u32 = "abc".parse().unwrap_or(80);

For Result, unwrap_or ignores the error and returns the default.

let n: i32 = "".parse().unwrap_or_default();
let editor = env::var("NO_SUCH_VARIABLE").unwrap_or_default();

The unwrap_or_default method uses the Default trait: zero for numbers and an empty string for String.

$ cargo run -q
apple: 3, pear: 0
width: 80
0 ""

Lazy defaults

The argument of unwrap_or is always evaluated, even if the value is present. The unwrap_or_else method takes a closure, which is called only when the default is needed.

main.rs
fn lookup(id: u32) -> Option<u32> {
    if id == 1 { Some(5) } else { None }
}

fn expensive_default() -> u32 {
    println!("  computing the default");
    100
}

fn main() {
    println!("unwrap_or with Some:");
    let a = lookup(1).unwrap_or(expensive_default());
    println!("unwrap_or_else with Some:");
    let b = lookup(1).unwrap_or_else(expensive_default);
    println!("unwrap_or_else with None:");
    let c = lookup(2).unwrap_or_else(expensive_default);
    println!("{a} {b} {c}");

    let n = "12x".parse::<u32>().unwrap_or_else(|e| {
        eprintln!("warning: {e}, using 0");
        0
    });
    println!("{n}");
}

We compare the two methods with a function that prints a message when it is called.

let a = lookup(1).unwrap_or(expensive_default());
let b = lookup(1).unwrap_or_else(expensive_default);

In the first line, expensive_default is called before unwrap_or runs, and its result is thrown away. In the second line, the function is passed without calling it, and it is not called, because the value is present.

let n = "12x".parse::<u32>().unwrap_or_else(|e| {
    eprintln!("warning: {e}, using 0");
    0
});

For Result, the closure receives the error, so we can report it.

$ cargo run -q
unwrap_or with Some:
  computing the default
unwrap_or_else with Some:
unwrap_or_else with None:
  computing the default
5 5 100
0
warning: invalid digit found in string, using 0

Unwrapping the error

The unwrap_err and expect_err methods do the opposite: they return the error and panic on an Ok value. They are mostly used in tests.

main.rs
fn main() {
    let err = "abc".parse::<i32>().unwrap_err();
    println!("error: {err}");
    println!("kind: {:?}", err.kind());

    let err = "".parse::<i32>().expect_err("an empty string should not parse");
    println!("error: {err}");

    let err = "3".parse::<i32>().unwrap_err();
    println!("error: {err}");
}

We get the errors of failed parsing operations.

let err = "abc".parse::<i32>().unwrap_err();
println!("error: {err}");
println!("kind: {:?}", err.kind());

The display format of the error gives a readable message; the kind method tells the reason of the failure.

let err = "3".parse::<i32>().unwrap_err();

The string is a valid number, so the call panics. The message contains the value inside Ok.

$ cargo run -q
error: invalid digit found in string
kind: InvalidDigit
error: cannot parse integer from empty string

thread 'main' (68592) panicked at src/main.rs:9:34:
called `Result::unwrap_err()` on an `Ok` value: 3
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The ? operator

In a function that returns a Result or an Option, the ? operator is the usual replacement for unwrap. Instead of panicking, it returns the error to the caller.

main.rs
use std::num::ParseIntError;

fn sum_unwrap(a: &str, b: &str) -> i32 {
    a.parse::<i32>().unwrap() + b.parse::<i32>().unwrap()
}

fn sum(a: &str, b: &str) -> Result<i32, ParseIntError> {
    Ok(a.parse::<i32>()? + b.parse::<i32>()?)
}

fn main() {
    println!("{}", sum_unwrap("4", "5"));

    println!("{:?}", sum("4", "5"));
    println!("{:?}", sum("4", "five"));

    match sum("10", "x") {
        Ok(n) => println!("sum: {n}"),
        Err(e) => println!("cannot add: {e}"),
    }
}

We have two functions that add numbers given as strings.

fn sum_unwrap(a: &str, b: &str) -> i32 {
    a.parse::<i32>().unwrap() + b.parse::<i32>().unwrap()
}

The first function panics on invalid input. Its signature does not show that it can fail.

fn sum(a: &str, b: &str) -> Result<i32, ParseIntError> {
    Ok(a.parse::<i32>()? + b.parse::<i32>()?)
}

The second function returns the error. The caller decides what to do with it: print it, use a default, or pass it further. See the Rust error handling article for more about the operator.

$ cargo run -q
9
Ok(9)
Err(ParseIntError { kind: InvalidDigit })
cannot add: invalid digit found in string

Pattern matching

The match expression, if let, and let else handle both cases explicitly.

main.rs
fn describe(input: &str) -> String {
    let Ok(n) = input.parse::<i32>() else {
        return format!("{input:?} is not a number");
    };

    match n.checked_mul(1_000_000) {
        Some(m) => format!("{n} million is {m}"),
        None => format!("{n} million does not fit into i32"),
    }
}

fn main() {
    for input in ["2", "abc", "3000"] {
        println!("{}", describe(input));
    }

    let names = ["Jane", "Paul"];
    if let Some(last) = names.last() {
        println!("last name: {last}");
    }
}

The describe function handles invalid input and overflow without panicking.

let Ok(n) = input.parse::<i32>() else {
    return format!("{input:?} is not a number");
};

The let else statement binds the value if the pattern matches; otherwise the else block must leave the function. After the statement, n is a plain i32.

match n.checked_mul(1_000_000) {
    Some(m) => format!("{n} million is {m}"),
    None => format!("{n} million does not fit into i32"),
}

The checked_mul method returns None on overflow. The match handles both variants.

if let Some(last) = names.last() {
    println!("last name: {last}");
}

The if let expression runs the block only when there is a value.

$ cargo run -q
2 million is 2000000
"abc" is not a number
3000 million does not fit into i32
last name: Paul

Combinators

The methods of Option and Result transform the values without unwrapping them. The Rust Option type article covers them in detail.

main.rs
fn main() {
    let text = "name=John Doe";

    let name = text.split_once('=').map(|(_, v)| v).unwrap_or("unknown");
    println!("{name}");

    let len = text.find(' ').map_or(0, |i| i + 1);
    println!("{len}");

    let total: i32 = ["1", "2", "x", "4"]
        .iter()
        .filter_map(|s| s.parse::<i32>().ok())
        .sum();
    println!("{total}");

    let all: Result<Vec<i32>, _> = ["1", "2", "x"].iter().map(|s| s.parse::<i32>()).collect();
    println!("{all:?}");
}

We transform optional values and process a list of strings that contains an invalid number.

let name = text.split_once('=').map(|(_, v)| v).unwrap_or("unknown");

The map method applies the closure to the value inside Some; None stays None.

let len = text.find(' ').map_or(0, |i| i + 1);

The map_or method combines map with a default value.

.filter_map(|s| s.parse::<i32>().ok())

The ok method converts a Result into an Option; filter_map keeps only the successfully parsed numbers.

let all: Result<Vec<i32>, _> = ["1", "2", "x"].iter().map(|s| s.parse::<i32>()).collect();

Collecting into a Result<Vec<_>, _> gives all the values, or the first error.

$ cargo run -q
John Doe
10
7
Err(ParseIntError { kind: InvalidDigit })

When unwrap is acceptable

Sometimes the value cannot be missing, but the compiler does not know it. In such cases, unwrap or expect is fine. The expect message should explain why the value is there.

main.rs
use std::net::Ipv4Addr;

fn print_max(vals: &[i32]) {
    if vals.is_empty() {
        println!("no values");
        return;
    }

    let max = vals.iter().max().unwrap();
    println!("max: {max}");
}

fn main() {
    let localhost: Ipv4Addr = "127.0.0.1".parse().unwrap();
    println!("{localhost}");

    print_max(&[3, 9, 1, 7]);
    print_max(&[]);

    let digit = '7'.to_digit(10).expect("'7' is a decimal digit");
    println!("{digit}");
}

The example shows three calls that cannot fail.

let localhost: Ipv4Addr = "127.0.0.1".parse().unwrap();

Parsing a constant string that is known to be valid cannot fail. If it did, it would be a bug in the program, and a panic is the right reaction.

if vals.is_empty() {
    println!("no values");
    return;
}

let max = vals.iter().max().unwrap();

The max method returns None only for an empty slice, which we have ruled out.

let digit = '7'.to_digit(10).expect("'7' is a decimal digit");

Also in tests, unwrap is common: a panic marks the test as failed and shows the error.

$ cargo run -q
127.0.0.1
max: 9
no values
7

Source

Option::unwrap

Result::unwrap

The Rust Programming Language: To panic! or Not to panic!

In this article we have worked with the unwrap method and its alternatives in Rust.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Rust tutorials.