We should use panics like panic!()
, unwrap()
, expect()
only if we can not handle the situation in a better way. Also if a function contains expressions which can produce either None
or Err
,
- we can handle them inside the same function. Or,
- we can return
None
andErr
types immediately to the caller. So the caller can decide how to handle them.
💡 None
types no need to handle by the caller of the function always. But Rusts’ convention to handle Err
types is, return them immediately to the caller to give more control to the caller to decide how to handle them.
- If an
Option
type hasSome
value or aResult
type has aOk
value, the value inside them passes to the next step. - If the
Option
type hasNone
value or theResult
type hasErr
value, return them immediately to the caller of the function.
Example with Option
type,
fn main() {
if complex_function().is_none() {
println!("X not exists!");
}
}
fn complex_function() -> Option<&'static str> {
let x = get_an_optional_value()?; // if None, returns immidiately; if Some("abc"), set x to "abc"
// some other code, ex
println!("{}", x); // "abc" ; if you change line 19 `false` to `true`
Some("")
}
fn get_an_optional_value() -> Option<&'static str> {
//if the optional value is not empty
if false {
return Some("abc");
}
//else
None
}
Example with Result
Type,
fn main() {
// `main` function is the caller of `complex_function` function
// So we handle errors of complex_function(), inside main()
if complex_function().is_err() {
println!("Can not calculate X!");
}
}
fn complex_function() -> Result<u64, String> {
let x = function_with_error()?; // if Err, returns immidiately; if Ok(255), set x to 255
// some other code, ex
println!("{}", x); // 255 ; if you change line 20 `true` to `false`
Ok(0)
}
fn function_with_error() -> Result<u64, String> {
//if error happens
if true {
return Err("some message".to_string());
}
// else, return valid output
Ok(255)
}
⭐ ?
operator was added in Rust version 1.13. try!()
macro is the old way to propagate errors before that. So we should avoid using this now.
- If a
Result
type hasOk
value, the value inside it passes to the next step. If it hasErr
value, returns it immediately to the caller of the function.
// using `?`
let x = function_with_error()?; // if Err, returns immidiately; if Ok(255), set x to 255
// using `try!()`
let x = try!(function_with_error());
Before Rust version 1.26, we couldn't propagate Result
and Option
types from the main()
function. But now, we can propagate Result
types from the main()
function and it prints the Debug
representation of the Err
.
💡 We are going to discuss about Debug
representations under Error trait section.
use std::fs::File;
fn main() -> std::io::Result<()> {
let _ = File::open("not-existing-file.txt")?;
Ok(()) // Because of the default return value of Rust functions is an empty tuple/ ()
}
// Because of the program can not find not-existing-file.txt , it produces,
// Err(Os { code: 2, kind: NotFound, message: "No such file or directory" })
// While propagating error, the program prints,
// Error: Os { code: 2, kind: NotFound, message: "No such file or directory" }
💯 If you want to know about the all kind of errors
std::fs::File::open()
can produce, check the error list onstd::fs::OpenOptions
.