Rust errors: Difference between revisions

From wikinotes
No edit summary
 
(14 intermediate revisions by the same user not shown)
Line 1: Line 1:
Rust has two primary methods of handling errors.
Rust has two primary methods of handling errors.
* <code>panic!()</code> halts/exits the program
* <code>panic!()</code> halts/exits the program
* <code>Result</code> types are for handle-able errors
* <code>Result</code> types are for handle-able errors (which frequently pass <code>std::error::Error</code> objects)


= panic =
= panic =
Line 21: Line 21:
= Result =
= Result =
<blockquote>
<blockquote>
The result type is an enum, whose options <code>Ok, Err</code> have been merged into the global scope through <code>prelude</code>.<br>
<code>Err</code> can be any type, but the standard library predominantly uses <code>std::error::Error</code> concretions.
Given this Result producing code:
<syntaxhighlight lang="rust">
use std::error::Error;
use std::fmt;
// A custom std::error::Error impl
#[derive(Debug)]
struct MyError {}
impl Error for MyError{}
impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "i borked because...")
    }
}
// a result type
fn is_one(i: isize) -> Result<String, MyError> {
    if i == 1 {
        Ok("success".to_string())
    } else {
        Err(MyError{})
    }
}
</syntaxhighlight>
{{ expand
| <code>match</code> Ok/Err (optionally break/return/panic on Err)
|
<syntaxhighlight lang="rust">
let result = match is_one(2) {
    Ok(x) => format!("horay: {}", x),
    Err(_) => panic!("an error occurred")
};
println!("{}", result);
</syntaxhighlight>
}}
{{ expand
| Propagate if Err()
|
It's also fairly common that you want to delegate handling the error to the caller.<br>
There is also syntactic sugar for this.
The <code>?</code> operator returns with the <code>Err</code> if it errored.<br>
You'll only need to provide the <code>Ok()</code> value.<br>
(note you can also pass an Option and receive an Option in the same manner)
<syntaxhighlight lang="rust">
fn print_if_result_ok(i: isize) -> Result<String, MyError> {
    let result = is_one(i)?                // return Err() if err
    println!("success value: {}", result);  // unwraps result, and you know it is Ok() now
    Ok("success")
}
</syntaxhighlight>
}}
{{ expand
| Common Result Methods
|
Confirm status
<syntaxhighlight lang="rust">
.is_ok()  // true if is Ok-val
.is_err()  // true if is Err-val
</syntaxhighlight>
Expected Result, or Panic w/ Message
<syntaxhighlight lang="rust">
.expect("error because..")        // panic with this mssage if Err(), otherwise returns Ok-val
.expect_err("error because..")    // panic with this message if Ok(), otherwise return Err-val
</syntaxhighlight>
Unwrap (to raw val)
<syntaxhighlight lang="rust">
// panic variations
.unwrap()                        // Ok-val or panic!
.unwrap_err()                    // Err-val or panic!
// non-panic variations
.into_ok_or_err()                // return Ok-val if Ok(), Err-val if Err()
.unwrap_or(456)                  // (eager) Ok-val, w/ fallback on default
.unwrap_or_else(|_| 1 + 1)        // (lazy)  Ok-val, w/ fallback on default, calculated in closure
.unwrap_or_default()              // Ok-val, w/ fallback on Ok-val-type's default
</syntaxhighlight>
Map (transform Ok/Err vals)
<syntaxhighlight lang="rust">
.map(|v| v.to_string())          // Mutate the Ok-vals, preserve Err-vals, return Result
.map_err(|e| e.as_str())          // Mutate the Err-vals, preserve Ok-vals, return Result
</syntaxhighlight>
Or (try changing Err to Ok)
<syntaxhighlight lang="rust">
// (eager) If Err-val, use closure to try changing it to Ok-val
// (also accepts Err-val), return Result
.or(|r| Ok(r.unrap() * 2))
// (lazy)  If Err-val, use closure to try changing it to Ok-val
// (also accepts Err-val), return Result
.or_else(|r| Ok(r.unwrap() * 2))
</syntaxhighlight>
Convert to Option
<syntaxhighlight lang="rust">
<syntaxhighlight lang="rust">
.ok()  // convert to Option (Ok->Some, Err->None)
.err() // convert to Option (Ok->None, Err->Some)
</syntaxhighlight>


Misc
<syntaxhighlight lang="rust">
.iter()                          // (iterate over contained value? if ok-val is array?)
.contains(2)                      // return true if item is Ok(), and Ok-val matches
</syntaxhighlight>
</syntaxhighlight>
}}
</blockquote><!-- Result -->
</blockquote><!-- Result -->
= std::error::Error =
<blockquote>
The standard library predominantly uses <code>std::error::Error</code> concretions as <code>Err</code> within Result objects.<br>
It's particularly useful to implement errors as enums, to enumerate the various errors it can handle.
Error implementors must:
* be derived from Debug
* implement Display
<syntaxhighlight lang="rust">
use std::error::Error;
use std::fmt;
#[derive(Debug)]
pub enum MyError {
    FileNotAccessible,
    FileIsLocked,
}
impl Error for MyError{}
impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MyError::FileNotAccessible => write!(f, "file not accessible.."),
            MyError::FileIsLocked => write!(f, "file is locked.."),
        }
    }
}
</syntaxhighlight>
</blockquote><!-- std::error::Error -->

Latest revision as of 00:04, 10 February 2023

Rust has two primary methods of handling errors.

  • panic!() halts/exits the program
  • Result types are for handle-able errors (which frequently pass std::error::Error objects)

panic

  • intended for halting application, not control flow
  • have backtraces
panic!("tried to X but couldn't Y")

// convert panic to result
// (not intended for native rust code)
let result = panic::catch_unwind(|| {
    panic!("oh no!");
});

Result

The result type is an enum, whose options Ok, Err have been merged into the global scope through prelude.
Err can be any type, but the standard library predominantly uses std::error::Error concretions.

Given this Result producing code:

use std::error::Error;
use std::fmt;

// A custom std::error::Error impl
#[derive(Debug)]
struct MyError {}
impl Error for MyError{}
impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "i borked because...")
    }
}

// a result type
fn is_one(i: isize) -> Result<String, MyError> {
    if i == 1 {
        Ok("success".to_string())

    } else {
        Err(MyError{})
    }
}

match Ok/Err (optionally break/return/panic on Err)


let result = match is_one(2) {
    Ok(x) => format!("horay: {}", x),
    Err(_) => panic!("an error occurred")
};
println!("{}", result);

Propagate if Err()

It's also fairly common that you want to delegate handling the error to the caller.
There is also syntactic sugar for this.

The ? operator returns with the Err if it errored.
You'll only need to provide the Ok() value.
(note you can also pass an Option and receive an Option in the same manner)

fn print_if_result_ok(i: isize) -> Result<String, MyError> {
    let result = is_one(i)?                 // return Err() if err
    println!("success value: {}", result);  // unwraps result, and you know it is Ok() now
    Ok("success")
}


Common Result Methods


Confirm status

.is_ok()   // true if is Ok-val
.is_err()  // true if is Err-val

Expected Result, or Panic w/ Message

.expect("error because..")        // panic with this mssage if Err(), otherwise returns Ok-val
.expect_err("error because..")    // panic with this message if Ok(), otherwise return Err-val

Unwrap (to raw val)

// panic variations
.unwrap()                         // Ok-val or panic!
.unwrap_err()                     // Err-val or panic!

// non-panic variations
.into_ok_or_err()                 // return Ok-val if Ok(), Err-val if Err()
.unwrap_or(456)                   // (eager) Ok-val, w/ fallback on default
.unwrap_or_else(|_| 1 + 1)        // (lazy)  Ok-val, w/ fallback on default, calculated in closure
.unwrap_or_default()              // Ok-val, w/ fallback on Ok-val-type's default

Map (transform Ok/Err vals)

.map(|v| v.to_string())           // Mutate the Ok-vals, preserve Err-vals, return Result
.map_err(|e| e.as_str())          // Mutate the Err-vals, preserve Ok-vals, return Result

Or (try changing Err to Ok)

// (eager) If Err-val, use closure to try changing it to Ok-val
// (also accepts Err-val), return Result
.or(|r| Ok(r.unrap() * 2))

// (lazy)  If Err-val, use closure to try changing it to Ok-val
// (also accepts Err-val), return Result
.or_else(|r| Ok(r.unwrap() * 2))

Convert to Option

.ok()  // convert to Option (Ok->Some, Err->None)
.err() // convert to Option (Ok->None, Err->Some)

Misc

.iter()                           // (iterate over contained value? if ok-val is array?)
.contains(2)                      // return true if item is Ok(), and Ok-val matches


std::error::Error

The standard library predominantly uses std::error::Error concretions as Err within Result objects.
It's particularly useful to implement errors as enums, to enumerate the various errors it can handle.

Error implementors must:

  • be derived from Debug
  • implement Display
use std::error::Error;
use std::fmt;

#[derive(Debug)]
pub enum MyError {
    FileNotAccessible,
    FileIsLocked,
}

impl Error for MyError{}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MyError::FileNotAccessible => write!(f, "file not accessible.."),
            MyError::FileIsLocked => write!(f, "file is locked.."),
        }
    }
}