I also need to catch any error that might be generated by these functions. So here's my implementation below. This is reminiscent of the "callback hell" in javascript. It's hard to read and confusing to debug when the code is long.
fn main(){
let result = do_something()
if let Some(i) = result {
let summary = summarize_something(i)
if let Some(j) = summary {
let processed = process_something(j)
if let Some(k) = processed {
// .... and etc. This will be repeated for quite some time
} else {
println!("Error happened in process_something()");
}
} else {
println!("Error happened in summarize_something()");
}
} else {
println!("Error happened in do_something()");
}
}
How to refactor this complex code ?. Am I missed something ? Or it is what it is ... ?
You could refactor to handle the errors as values. Error handling in Rust is a large topic, but as a very brief introduction to illustrate how it can reduce the nesting and other boilerplate:
pub enum SomeError {
Doing,
Summarizing,
Processing,
}
// The `foo = x?` below do something similar to:
// foo = match x {
// Ok(foo) => foo,
// Err(e) => return Err(e),
// };
// So errors get returned out of the entire function,
// while non-errors get assigned and the function continues
fn do_something() -> Result<(), SomeError> {
// do_thing returns e.g. Result<_, SomeError>
let result = do_thing()?;
// summarize_something returns Option<_>
// The .ok_or() turns it into a Result<_, SomeError>
let summary = summarize_something(i).ok_or(SomeError::Summarizing)?;
// process_something returns Result<_, SomeOtherErrorType>
// The .map_err() turns it into a Result<_, SomeError>
let processed = process_something(summary).map_err(|e| SomeError::Processing)?;
}
fn main() {
match do_something() {
Ok(_) => {},
Err(e) => handle_your_error_somehow(e),
}
}
Here's a similar version that shows one way to mix Option and Result and str types:
fn do_stuff() -> Result<(), &'static str> {
let result = do_something().ok_or("Error happened in do_something()")?;
let summary = summarize_something(result).ok_or("Error happened in summarize_something()")?;
let processed = process_something(summary).ok_or("Error happened in process_something()")?;
// .... and etc. This will be repeated for quite some time
Ok(())
}
fn main() {
if let Err(e) = do_stuff() {
println!("{}", e);
}
}
Not with Option, no. You need to convert everything to Result, like mbrubeck did, to keep track of which one failed. If you don't want to use the ? operator, you can do the job using Result::and_then
let processed = do_something().ok_or("Error happened in do_something()")
.and_then(|i| summarize_something(i).ok_or("Error happened in summarize_something()"))
.and_then(|j| process_something(j).ok_or("Error happened in process_something()"));