Rust lang series episode #22 — try! macro (#rust-series)

in #rust-series8 years ago (edited)

Hello everyone, new episode of rust series is here, today we will discuss more about Error handling in Rust. If you missed previous episode about error handling I recommend to go back and start there. Today we will find more about Option and Result types and explain very useful try! macro.

Before we go to the macro let's recapitulate that we have two standard return types in Rust which are Option and Result. Let's check basic methods these types provide. This is just a short summary of basic (and mostly very simple) methods for a brief overview but it's good to know what's provided so we can use it. Some of them we've already met .

Option methods

  • is_some - returns true if option is Some value
  • is_none - returns true if option is None value
  • as_ref - returns Option<&T>
  • as_mut - returns Option<&mut T>
  • expect(msg: &str) - returns value of Some , panic with message otherwise
  • unwrap - returns value of Some, panics otherwise
  • unwrap_or(default : T) - returns value of Some, default otherwise
  • unwrap_or_else(f: F) - returns value of Some or closure computed value otherwise
  • map(f:F) - maps Option<T> to Option<U> by using closure F
  • map_or(default: U, f: F) - applies function or returns default
  • map_or_else - applies function, or computes default
  • ok_or(err: E) - transforms Option<T> to Result<T,E>
  • ok_or_else(err: F) - transforms Option<T> to Result<T,E> with using err()
  • iter - returns iterator over possibly contained value
  • iter_mut - return mutable iterator over the possibly contained value
  • and(optb: Option<U>) - returns None if option is None, otherwise optb
  • and_then(F) - returns None if option is None, calls f otherwise and return result
  • or(optb) - returns option if it contains value, otherwise returns optb
  • or_else(F) - returns the option if it contains value, otherwise f and returns result
  • take - takes value of the option and leave it None;
  • cloned - maps Option<&T> to an Option<T>
  • unwrap_or_default - returns the contained value or a default value for that type

Result methods

For Result type, there are many similar methods for Result as for Option with the same or very similar behavior: as_ref, as_mut, map, map_err, iter,iter_mut_ and, and_then, or, or_else, unwrap, unwrap_or_else and some specific methods:

  • is_ok - returns true if result is Ok
  • is_err - returns true if result is Err
  • ok - converts Result<T,E> to Option<T>
  • err - converts Result<T,E> to Option<E>

These methods are mainly useful when we are evaluating results, performing logical operations and translating them each other.

let r1 : Result<i32,i32> = Ok(1);
let r2 : Result<i32,i32> = Ok(2);

let r3 = r1.or(r2);

println!("{:?}", r3);

# output
Ok(1)

try! macro

It's time to mention try! macro which is widely used because it's very handy for error handling. While dealing with standard I/O operations flow, we can for example start with calling File::open("non-existing.txt"). This function returns some Result return type value. Now what? We need to destructure it and handle possible error somehow.

Handling without try!

First try it with an approach using a facility we already know:

use std::io;
use std::fs::{File};

fn work_with_file() -> std::io::Result<()> {
    let result = File::open("non-existing.txt");
    match result {
        Err(e) => return Err(e),
        Ok(f) => f
    };
    // maybe some more work with file
    // ...

    // anyway now everything went fine and we need to return and report it
    Ok(())
}

fn main() {
    let result = work_with_file();

    // error handling
    if let Err(e) = result {
        println!("{:?}", e);
    } else {
        println!("Everything went just fine");
    }
}

# output
Error { repr: Os { code: 2, message: "No such file or directory" } }

Good, it works fine. But sooner or later you will find that matching for these simple cases is very bothering. All right, now let's try to simplify it with try! macro

Handling with try!

use std::io;
use std::fs::{File};

fn work_with_file() -> std::io::Result<()> {
    let result = File::open("newfile4.txt");
    try!(result);
    // maybe some more work with file
    // ...

    // anyway now everything went fine and we need to return and report it
    Ok(())
}

fn main() {
    let result = work_with_file();

    // error handling
    if let Err(e) = result {
        println!("Cannot open file: {:?}", e);
    } else {
        println!("Everything's fine");
    }
}

# output
Error { repr: Os { code: 2, message: "No such file or directory" } }

You see, all the match statement can be replaced with one single macro. Cool, right? We saved three lines of code and get the same thing. Imagine you have several such operations and now you can see the value. So when you need to propagate error to a caller and work with Ok result try! macro is exactly what you need as you can see from its definition.

macro_rules! try {
    ($e:expr) => (match $e {
        Ok(val) => val,
        Err(err) => return Err(err),
    });
}

std::io::ErrorKind

One more thing. We usually don't want to report error messages like this. There are many ways howto handle it. One of them (speaking with I/O errors) is using ErrorKind. When get Error we get std::io::Error struct. When we want to have more details about the error, we can call kind() to get ErrorKind which gives us specific variant of that error. Then we match and map it to our custom errors or error messages or whatever we need.

pub enum ErrorKind {
    NotFound,
    PermissionDenied,
    ConnectionRefused,
    ConnectionReset,
    ConnectionAborted,
    NotConnected,
    AddrInUse,
    AddrNotAvailable,
   // ... and many others
}

and then we can obtain specific error variant for our further needs.

let errorKind : ErrorKind = result.kind();
println!("{:?}", errorKind);

# output
NotFound

Now, with this information we can easily map the error type to our desired error palette to report it to a user in some "nice" way or to do with it whatever we wish.

We will not go into other things today but there is more to error handling and around this topic in general and so don't worry we will surely return to this important issues in some coming episode. Anyway, now you should be able to perform basic error handling quite fine.

Postfix

That's all for today, thank you for your appreciations, feel free to comment and point out possible mistakes (first 24 hours works the best but any time is fine). May Jesus bless your programming skills, use them wisely and see you next time.

Meanwhile you can also check the official documentation for more details if you wish.

#rust-series
#rust-lang
#rust

Sort:  

This post has been linked to from another place on Steem.

Learn more about linkback bot v0.4. Upvote if you want the bot to continue posting linkbacks for your posts. Flag if otherwise.

Built by @ontofractal

Coin Marketplace

STEEM 0.26
TRX 0.11
JST 0.033
BTC 64498.18
ETH 3079.08
USDT 1.00
SBD 3.86