Chaining Results with and_then
You are writing a function that reads a configuration file, parses a port number, and validates that the port is in the allowed range. If the file doesn't exist, the whole operation fails. If the port is text instead of a number, it fails. If the port is 99999, it fails. You don't want to write a pyramid of if statements where each step checks the result of the previous one. You want the logic to flow: try this, then try that, then try this. If anything breaks, stop immediately and bubble the error up.
That is what and_then does. It chains operations that return Result values. Each step only runs if the previous step succeeded. If any step returns an error, the chain stops and the error propagates to the end.
How and_then works
and_then takes a Result and a closure. If the Result is Ok, it passes the value inside to the closure. The closure performs work and returns a new Result. If the original Result was Err, and_then skips the closure entirely and returns the error immediately.
The closure is the next step in the chain. It only runs when the previous step succeeded. Think of and_then as a relay race where the baton is the success value. Each runner takes the baton, does their leg, and hands off a new baton. If a runner drops the baton, the race ends. The next runner never gets a turn. The error propagates to the finish line without the remaining runners ever seeing it.
The closure can change the type of the success value. You can start with a Result<String, E>, parse it into an integer, and end with a Result<i32, E>. and_then handles the type change seamlessly.
Minimal example
fn parse_and_double(s: &str) -> Result<i32, String> {
// Parse the string. Returns Result<i32, ParseIntError>.
// We map the error to a String for simplicity.
s.parse::<i32>()
.map_err(|_| "Invalid number".to_string())
.and_then(|n| {
// Closure receives n only if parse succeeded.
// Returns a new Result.
if n > 100 {
Err("Number too large".to_string())
} else {
Ok(n * 2)
}
})
}
fn main() {
println!("{:?}", parse_and_double("42")); // Ok(84)
println!("{:?}", parse_and_double("abc")); // Err("Invalid number")
println!("{:?}", parse_and_double("200")); // Err("Number too large")
}
Walkthrough
When you call parse_and_double("42"), the parse succeeds. and_then sees Ok(42). It calls the closure with 42. The closure checks 42 > 100, finds false, and returns Ok(84). The function returns Ok(84).
When you call parse_and_double("abc"), the parse fails. and_then sees Err. It never calls the closure. It returns the error immediately. The closure code doesn't run. This is short-circuiting. The chain stops at the first failure.
When you call parse_and_double("200"), the parse succeeds. and_then calls the closure with 200. The closure checks 200 > 100, finds true, and returns Err("Number too large"). The function returns that error.
Chain and_then when the next step depends on the previous result and can itself fail. If the step can't fail, use map instead.
Realistic example
Configuration loading often involves multiple fallible steps. Reading a file, parsing the content, extracting a field, and validating the value. and_then chains these steps cleanly.
use std::collections::HashMap;
use std::fs;
fn load_port(path: &str) -> Result<u16, String> {
// Read the file content. Fails if file missing or unreadable.
fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path, e))
.and_then(|content| {
// Parse content. Simulates JSON/TOML parsing.
// Returns Result<HashMap, String>.
parse_config(&content)
})
.and_then(|config| {
// Extract the port. Fails if key missing.
config.get("port")
.copied()
.ok_or_else(|| "Missing 'port' key".to_string())
})
.and_then(|port| {
// Validate port range.
if port == 0 || port > 65535 {
Err("Port out of range".to_string())
} else {
Ok(port)
}
})
}
fn parse_config(_content: &str) -> Result<HashMap<String, u16>, String> {
// Placeholder for actual parsing logic.
// Returns a mock config for demonstration.
let mut config = HashMap::new();
config.insert("port".to_string(), 8080);
Ok(config)
}
This example chains four operations. Each and_then takes the success value from the previous step and produces a new Result. If fs::read_to_string fails, the parsing and validation never run. If parsing fails, validation never runs. The error bubbles up through the chain.
Watch the types. and_then expects a closure that returns a Result. If you return a plain value, the compiler will stop you. Fix the return type, or switch to map.
Map versus and_then
Beginners often confuse map and and_then. Both transform the success value. The difference is whether the transformation can fail.
map takes a closure that returns a plain value. It cannot fail. If you use map, the result type stays Result<T, E>. The error type E never changes.
and_then takes a closure that returns a Result. It can fail. If you use and_then, the result type becomes Result<U, E>. The success type can change from T to U. The error type must match.
Use this terminology table to distinguish the signatures:
map:Fn(T) -> U. Transforms the value. Cannot fail. Result type:Result<U, E>.and_then:Fn(T) -> Result<U, E>. Transforms the value. Can fail. Result type:Result<U, E>.map_err:Fn(E) -> F. Transforms the error. Cannot fail. Result type:Result<T, F>.
If you use map and the closure returns a Result, you create a nested Result<Result<U, E>, E>. The compiler rejects this with E0308 (mismatched types) because the function expects a flat Result. Use and_then to flatten the chain.
Pitfalls and compiler errors
Nested results
The most common mistake is using map when the closure returns a Result.
fn bad_example(s: &str) -> Result<i32, String> {
s.parse::<i32>()
.map_err(|_| "Invalid".to_string())
.map(|n| {
// Closure returns Result, but map expects plain value.
// This creates Result<Result<i32, String>, String>.
Ok(n * 2)
})
}
The compiler rejects this with E0308 (mismatched types). The function signature promises Result<i32, String>, but map produces Result<Result<i32, String>, String>. The fix is to switch to and_then.
Error type mismatch
and_then requires the closure to return the same error type as the Result it operates on. If the types differ, the chain breaks.
fn type_mismatch(s: &str) -> Result<i32, String> {
s.parse::<i32>()
// parse returns Result<i32, ParseIntError>.
// Closure returns Result<i32, String>.
// Error types don't match.
.and_then(|n| Ok(n * 2))
}
The compiler rejects this with E0308 (mismatched types). The Result from parse has error type ParseIntError. The closure returns a Result with error type String. Rust doesn't know how to convert between them. Use map_err to align the error types before chaining.
fn fixed_example(s: &str) -> Result<i32, String> {
s.parse::<i32>()
.map_err(|e| format!("Parse error: {}", e))
.and_then(|n| Ok(n * 2))
}
Treat the error type as part of the contract. If and_then returns a different error, the chain breaks. Use map_err to align the types before chaining.
Convention: The question mark operator
Modern Rust code rarely uses long chains of and_then in function bodies. The community prefers the ? operator for linear code. The ? operator is syntactic sugar that performs the same logic as and_then: it returns the error immediately if the value is an error, or unwraps the success value to continue.
fn parse_and_double_with_question(s: &str) -> Result<i32, String> {
let n = s.parse::<i32>()
.map_err(|_| "Invalid number".to_string())?;
if n > 100 {
return Err("Number too large".to_string());
}
Ok(n * 2)
}
This version is easier to read. Each step is on its own line. The error propagation is implicit. The compiler handles the short-circuiting.
The convention is to use ? for linear code. Reserve and_then for closures, method chains on Result values that aren't in a function returning Result, or when composing functions dynamically. If you see a long chain of and_then calls in a function body, refactor it to use ?.
Trust the ? operator for linear code. Reserve and_then for closures and composition. Keep your chains readable.
Decision matrix
Use and_then when the next step returns a Result and you are chaining inside a closure. Use and_then when you need to capture variables from the outer scope that aren't available in a linear function body. Use and_then when composing functions dynamically, such as building a pipeline of transformations at runtime. Use map when the next step transforms the value but cannot fail. Use ? when you are writing a linear function body and want the compiler to handle the error propagation for you. Use map_err when you need to transform the error type without touching the success value.