about
05/04/2022
RustBites - Error Handling
Code Repository Code Examples Rust Bites Repo

Rust Bite - Error Handling

Error handling, bubbling up

1.0 - Introduction

Rust error handling is based on two things: enumerations and matching. Rust's enumerations are more powerful than those of C++ and C#. Each element of the enumeration may wrap some type, as shown in the example below. Error handling in Rust does not use exceptions. Instead, it uses error returns, but those are much more useful than simply returning error codes. Because Result<T, E> is a Rust style enumeration, functions can return either a result of computation or an error with a single return value. The functions for testing those conditions are all part of the enum. Result Type enum Result<T, E> { Ok(T), Err(E), } References: std::result
std::error::Error
The example below shows how Result<T, E> is used. Functions returning Result or Option use display::{*}; fn demo_result<'a>(p: bool) -> Result<&'a str, &'a str> { print!("\n value of input predicate is {}", p); if p { return Ok("it's ok"); } else { return Err("not ok"); } } fn demo_option<'a>(p:bool) -> Option<&'a str> { print!("\n value of input predicate is {}", p); if p { return Some("something just for you!"); } else { return None; } } Output: -- demo Result -- ----------------------- -- using match value of input predicate is true result is it's ok value of input predicate is false result is not ok -- using expect value of input predicate is true result is it's ok -- demo Option -- ----------------------- --using match value of input predicate is true something just for you! value of input predicate is false sorry, nothing here --using unwrap value of input predicate is true something just for you! That's all folks! Using code: use display::{*}; sub_title(" -- demo Result -- "); shows("\n-- using match"); let r = demo_result(true); match r { Ok(rslt) => print!("\n result is {}", rslt), Err(rslt) => print!("\n result is {}", rslt) } let r = demo_result(false); match r { Ok(rslt) => print!("\n result is {}", rslt), Err(rslt) => print!("\n result is {}", rslt) } shows("\n\n-- using expect"); let r = demo_result(true) .expect("predicate was false"); print!("\n result is {}", r); ///////////////////////////////////////////// // uncomment to see panic // let _r = demo_result(false) // .expect("predicate was false"); putline(); sub_title(" -- demo Option -- "); shows("\n--using match"); let r = demo_option(true); match r { Some(rslt) => print!("\n {}", rslt), None => print!("\n sorry, nothing here") } let r = demo_option(false); match r { Some(rslt) => print!("\n {}", rslt), None => print!("\n sorry, nothing here") } shows("\n\n--using unwrap"); let r = demo_option(true).unwrap(); print!("\n {}", r); ///////////////////////////////////////////// // uncomment to see panic // let _r = demo_option(false).unwrap(); print!("\n\n That's all folks!\n\n");

2.0 - Bubbling Up Errors

Rust requires Result<T, E>, returned from functions, to be handled, or explicitly ignored by binding the result to a variable with leading underscore, e.g.
let _ign = function_returning_result(...);
Otherwise, it must match against the returned result, as shown above. If a function calls many other functions that return results, the code gets cluttered with all the error handling. One very nice way to avoid that is to use error bubbling.
If we suffix the function call with a ?, called try operator, then if the result is not an error, ? binds the unwrapped T value from Result<T, E>. So the expression:
let ret_val = function_returns_result()?
Binds the result value to ret_val. If, on the other hand, the function returns an error, then operator ? immediately returns Err(Error) to the caller.
This is illustrated in the example below:
Error Bubbling #[derive(Debug)] struct Error; #[derive(Debug)] struct Demo; impl Demo { fn do_int(&self, i:i32) -> &Self { print!("\n my argument is {}", i); &self } fn do_float(&self, f:f64) -> &Self { print!("\n my argument is {}", f); &self } fn do_vec(&self, v:Vec<i32>) -> &Self { print!("\n my argument is {:?}", v); &self } fn do_err(&self, p:bool) -> Result<String, Error> { let e = Error {}; if p { Ok("no error".to_string()) } else { Err(e) } } } fn main() -> Result<(),Error> { let d = Demo {}; let rslt = d.do_int(42) .do_float(3.14159) .do_vec(vec![1,2,3]) .do_err(true); print!("\n rslt = {:?}", rslt); /*------------------------------------------- Bubbling up Errors If do_err returns Ok(value) then bind value to rslt, else return Err(Error). */ let rslt = d.do_int(42) .do_float(3.1415927) .do_err(true)?; // binds to rslt print!("\n rslt = {:?}", rslt); let rslt = d.do_int(42) .do_float(3.1415927) .do_err(false)?; // returns Err print!("\n rslt = {:?}", rslt); Ok(()) } Output Compiling playground v0.0.1 (/playground) Finished dev [unoptimized + debuginfo] target(s) in 3.83s Running `target/debug/playground` Error: Error Standard Output my argument is 42 my argument is 3.14159 my argument is [1, 2, 3] rslt = Ok("no error") my argument is 42 my argument is 3.1415927 rslt = "no error" my argument is 42 my argument is 3.1415927 References:
playground example
Error handling - The Rust Book

3.0 - References


LinkDescription
Rust Story Provides enumeration methods and examples of use
rust-by-example Easy to use examples that have sufficient detail for project coding
the rust book Easy to use examples, illustrated with std::fs
  Next Prev Pages Sections About Keys