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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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
List all Rust tutorials.