Why Rust's error handling is awesome
This post is about the process of transforming something you would write as a
one-off script in Python (or any other scripting language) into a library
including error handling.
One aspect of Rust I prefer over Python’s way is that error handling is
explicit. If try you access a non existant entry in a Python dict you get an
KeyError
exception. Not having to handle errors is great if you explore an
API or write a one-off script. Often these scripts go into production though
and fail in the worst possible situations.
Of course you can handle Python’s exceptions, but you have to know where they
might occur, or catch them very unspecific.
Here’s an example of getting JSON formatted data from a weather API with Python
without error handling:
1
2
3
4
5
6
|
import requests
if __name__ == '__main__':
title = requests.get("https://www.metaweather.com/api/location/44418/")
.json()['title']
print title
|
The Rust implementation without error handling could look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
extern crate reqwest;
extern crate serde;
extern crate serde_json;
fn main() {
let url = "https://www.metaweather.com/api/location/44418/";
let location: serde_json::Value = reqwest::get(url)
.unwrap()
.json()
.unwrap();
let title = location.get("title").unwrap();
println!("{}", title);
}
|
In this example we see Rust’s explicit .unwrap()
s. While they might feel
verbose, this really helps to see where we might want to add error handling.
The first refactoring is to introduce a struct to get type safety and make the
parsing more robust. In cases when there is are no title
or woeid
, or they
had different types the parsing in line 18 would fail with an JSON error like
““invalid type: string "44418", expected u32”.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
extern crate reqwest;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
#[derive(Deserialize, Debug)]
struct Location {
pub title: String,
pub woeid: u32,
}
fn main() {
let url = "https://www.metaweather.com/api/location/44418/";
let location: Location = reqwest::get(url)
.unwrap()
.json()
.unwrap();
println!("{}", location.title);
}
|
The next refactoring is extracting a function that returns a Result
. Also we
switch from unwrap
s to the ?
operator. The benefit will be more obvious in
the next step, but already we gain reusability for our pseudo libarary.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
extern crate reqwest;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate failure;
use failure::Error;
#[derive(Deserialize, Debug)]
struct Location {
title: String,
woeid: u32,
}
fn get_location(url: &str) -> Result<Location, Error> {
let location: Location = reqwest::get(url)?
.json()?;
return Ok(location);
}
fn main() {
let url = "https://www.metaweather.com/api/location/44418/";
match get_location(url) {
Ok(location) => println!("{}", location.title),
Err(e) => eprintln!("{:?}", e),
}
}
|
By adding failure::ResultExt
we gain the ability to add context to results, so
we always know where something went wrong.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
extern crate reqwest;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate failure;
use failure::{Error, ResultExt};
#[derive(Deserialize, Debug)]
struct Location {
title: String,
woeid: u32,
}
fn get_location(url: &str) -> Result<Location, Error> {
let location: Location = reqwest::get(url)
.context("error while getting location")?
.json()
.context("Could not parse JSON")?;
return Ok(location);
}
fn main() {
let url = "https://www.metaweather.com/api/location/44418/";
match get_location(url) {
Ok(location) => println!("{}", location.title),
Err(e) => eprintln!("{:?}", e),
}
}
|
Now we can pull our Location
and get_location
to a lib.rs
and reuse it
wherever we need it; building statically linked binaries we can distribute and
run without the need to setup rvm, virtualenv or ay other environment management
tool.
Thanks for reading.