Result
This is an enum
type in Rust that indicates whether a function call succeeded or failed. It can return a helpful error message, along with a value.
Results must be somehow used—they cannot just be ignored. The question mark operator will return a result from another function call.
This program implements simple result-handling and uses the question mark to propagate an error from another function. In main we call the test()
function twice.
Test()
returns a result. The value returned is a usize
, and the second argument is an error message string
.test()
function. The first statement is another function call to test_inner
.test_inner()
with a question mark. If test_inner
returns Err, test()
propagates this result and test does not finish.fn test_inner(argument: usize) -> Result<usize, &'static str> { if argument == 6 { Err("Not valid") } else { Ok(argument) } } fn test(argument: usize) -> Result<usize, &'static str> { // Use question mark to propagate the error from another call. test_inner(argument)?; // Valid. Ok(100) } fn main() { // Call with argument 5, this is valid. if let Ok(result) = test(5) { println!("Result: {result}"); } // Call with argument 6, this causes an error. let result = test(6).unwrap(); println!("Result: {result}"); }Result: 100 thread main panicked at called Result::unwrap() on an Err value: "Not valid", ... note: run with RUST_BACKTRACE=1 environment variable to display a backtrace
iter
It is possible to use flatten()
on an iterator of Results. Such iterators are returned by BufReader
and its lines()
functions.
open()
and BufReader
to get an iterator of Results, and it uses flatten on them.map()
an internal unwrap
call.flatten()
with a Result
iterator can be elegant and clear to read. The test1()
function is shorter and simpler than test2.use std::io::*; use std::fs::*; pub fn test1(path: &str) -> Vec<String> { if let Ok(file) = File::open(path) { let reader = BufReader::new(file); // Version A: We can use flatten on an iterator of options. reader.lines().flatten().collect() } else { vec![] } } pub fn test2(path: &str) -> Vec<String> { if let Ok(file) = File::open(path) { let reader = BufReader::new(file); // Version B: We can use map to unwrap each option. reader.lines().map(|l| l.unwrap()).collect() } else { vec![] } } fn main() { println!("{:?}", test1("/Users/sam/test.txt")); println!("{:?}", test2("/Users/sam/test.txt")); }Hello Website Visitor["Hello", "Website", "Visitor"] ["Hello", "Website", "Visitor"]
Is_ok
, is_err
Suppose we call a function that returns a result. We need to handle this result to avoid a compile-time warning.
is_ok
and is_err
functions on the Result
that is returned.if
-statements—here we print messages if we have an "ok" or "err" result.use std::fs; fn main() { // Try to delete this file, and use is_ok and is_err. let path = "/does/not/exist"; if fs::remove_file(&path).is_ok() { println!("Not reached"); } if fs::remove_file(&path).is_err() { println!("IS ERR"); } }IS ERR
Result
Usually Results are used with code that handles input and output (file handling). But the concept is the same with any code that returns Result
.
Rust has powerful data types that represent Options and Results. With a Result
, we can use a question mark to reuse the result from a function call and return.