null for ordinary absence, and it does not rely on exceptions for recoverable errors. Instead, it uses enums: Option<T> and Result<T, E>.These two types show up everywhere. If you understand them early, a lot of Rust APIs stop looking strange.
Option<T> means a value may be missing#
Option<T> has two cases:
enum Option<T> {
Some(T),
None,
}Use it when “no value” is a normal, expected outcome:
fn find_port(name: &str) -> Option<u16> {
match name {
"http" => Some(80),
"https" => Some(443),
_ => None,
}
}This is better than sentinel values like -1 or 0, because the type makes absence explicit.
Working with Option#
You can handle it with match:
let port = find_port("https");
match port {
Some(value) => println!("port: {value}"),
None => println!("unknown service"),
}Or with if let for the one-case form:
if let Some(value) = find_port("http") {
println!("{value}");
}You can also transform it with methods:
let port_text = find_port("https")
.map(|port| port.to_string())
.unwrap_or_else(|| "n/a".to_string());Result<T, E> means an operation can fail#
Result also has two cases:
enum Result<T, E> {
Ok(T),
Err(E),
}Use it when failure should carry information:
fn parse_port(input: &str) -> Result<u16, std::num::ParseIntError> {
input.parse::<u16>()
}Now callers must deal with both success and failure explicitly.
Handling Result#
The direct approach is match:
match parse_port("443") {
Ok(port) => println!("parsed: {port}"),
Err(err) => println!("invalid port: {err}"),
}That style is clear and explicit. It is also sometimes verbose, which is why Rust has the ? operator.
? propagates errors#
Inside a function that returns Result, ? means:
- if the value is
Ok, unwrap it and continue - if the value is
Err, return early with that error
fn parse_and_increment(input: &str) -> Result<u16, std::num::ParseIntError> {
let port = input.parse::<u16>()?;
Ok(port + 1)
}This is one of the most common patterns in idiomatic Rust.
unwrap and expect#
You will see these a lot:
let port = "443".parse::<u16>().unwrap();They are acceptable in quick experiments, tests, and examples, but they panic on failure.
For real application code:
- prefer returning
Result - or handle the error explicitly
- use
expect("useful message")only when failure is truly unrecoverable and the message adds context
When to use which#
Use Option<T> when the absence of a value is ordinary:
- a lookup may not find anything
- a configuration key may be unset
- a list may be empty
Use Result<T, E> when the failure itself matters:
- parsing failed
- file I/O failed
- a network request failed
If you need to explain why something is missing or invalid, Result is usually the better fit.
Summary#
Option<T>models presence or absence.Result<T, E>models success or failure.match,if let, and helper methods are the main ways to work with them.?is the idiomatic way to propagate errors upward.
In Rust, these are not niche types. They are the normal way to write safe APIs.