Slide 1

Slide 1 text

Failing in Rust Armin @mitsuhiko Ronacher

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

800°C 36° 2' 0.4662" N 118° 15' 38.7792" W 795°C 789°C 797°C 793°C 805°C 782°C we show you your crashes

Slide 4

Slide 4 text

— Robert F. Kennedy “Only those who dare to fail greatly can ever achieve greatly.”

Slide 5

Slide 5 text

Why do we care?

Slide 6

Slide 6 text

Errors are Important • Errors are part of your API • Exceptions let you forget about this easily • A lot more relevant when you can catch them and there are multiple versions of libraries involved

Slide 7

Slide 7 text

ways to fail greatly

Slide 8

Slide 8 text

Mechanisms •Result •Option •panic!

Slide 9

Slide 9 text

Result Propagation vs Panic • Results/Options are for handling • panics are for recovering at best

Slide 10

Slide 10 text

Examples of Panics • out of bound access • runtime

Slide 11

Slide 11 text

Examples of Option • safe signalling absence of data • "the one obvious error"

Slide 12

Slide 12 text

— Douglas Adams

Slide 13

Slide 13 text

But when you do •panic!("…"); •unreachable!();

Slide 14

Slide 14 text

let's talk results

Slide 15

Slide 15 text

But if you don't panic … how do you result?

Slide 16

Slide 16 text

fn square_a_number() -> Result { let num = get_a_random_float()?; Ok(num * num) }

Slide 17

Slide 17 text

let val = expr?;

Slide 18

Slide 18 text

let val = match Try::into_result(expr) { Ok(v) => v, Err(e) => return Try::from_error(From::from(e)); };

Slide 19

Slide 19 text

error propagation can be hooked!

Slide 20

Slide 20 text

The Err in Result can be anything :-/

Slide 21

Slide 21 text

So let's use some traits for Err

Slide 22

Slide 22 text

pub trait Error: Debug + Display { fn description(&self) -> &str; fn cause(&self) -> Option<&Error>; }

Slide 23

Slide 23 text

impl Error + 'static { pub fn downcast_ref(&self) -> Option<&T> where T: Error + 'static; }

Slide 24

Slide 24 text

— Charles Darwin “To kill std::error is as good a service as, and sometimes even better than, the establishing of a new trait”

Slide 25

Slide 25 text

Problems • Generic errors give no guarantees • no Send / Sync / Debug • causes() returns non static errors • description() is useless • no backtraces

Slide 26

Slide 26 text

Enter Failure

Slide 27

Slide 27 text

— Winston Churchill “Success consists of going from std::error to failure without loss of enthusiasm”

Slide 28

Slide 28 text

some std errors are fails nice! impl Fail for E where E: StdError + Send + Sync + 'static

Slide 29

Slide 29 text

failure 0.1 ➠ failure 1.0

Slide 30

Slide 30 text

pub trait Fail: Display + Debug + Send + Sync + 'static { fn cause(&self) -> Option<&Fail>; fn backtrace(&self) -> Option<&Backtrace>; fn context(self, context: D) -> Context where D: Display + Send + Sync + 'static, Self: Sized; }

Slide 31

Slide 31 text

Fail can be derived

Slide 32

Slide 32 text

#[derive(Fail, Debug)] #[fail(display = "my failure happened")] pub struct MyFailure;

Slide 33

Slide 33 text

#[derive(Fail, Debug)] #[fail(display = "my failure happened")] pub struct MyFailure { backtrace: failure::Backtrace,
 }

Slide 34

Slide 34 text

#[derive(Fail, Debug)] #[fail(display = "my failure happened")] pub struct MyFailure { backtrace: failure::Backtrace, #[fail(cause)] io_cause: ::std::io::Error,
 }

Slide 35

Slide 35 text

Fail & Error

Slide 36

Slide 36 text

Fail ⟷ Error

Slide 37

Slide 37 text

Fail for libraries Error for applications

Slide 38

Slide 38 text

What's in the Package

Slide 39

Slide 39 text

Main Functionality • Fail trait • Error type • Context

Slide 40

Slide 40 text

Bonus Points • Fail works with no_std • Fail works with many std::errors • error-chain is deprecating itself for failure • actix and others are already using it!

Slide 41

Slide 41 text

— rustc an Error is not a Fail

Slide 42

Slide 42 text

failure 0.1: error.cause() failure 1.0: error.as_fail() Error to &Fail

Slide 43

Slide 43 text

Examples

Slide 44

Slide 44 text

#[derive(Debug, Fail, PartialEq, Eq, PartialOrd, Ord)] #[fail(display = "invalid value for project id")] pub struct ProjectIdParseError; Parse Errors

Slide 45

Slide 45 text

#[derive(Debug, Fail)] pub enum DsnParseError { #[fail(display = "no valid url provided")] InvalidUrl, #[fail(display = "no valid scheme")] InvalidScheme, #[fail(display = "username is empty")] NoUsername, #[fail(display = "no project id")] NoProjectId, #[fail(display = "invalid project id")] InvalidProjectId(#[fail(cause)] ProjectIdParseError), } Complex Parse Errors

Slide 46

Slide 46 text

fn parse(url: Url) -> Result { let project_id: i64 = url.path() .trim_matches('/') .parse() .map_err(DsnParseError::InvalidProjectId)?; Ok(Dsn { project_id }) } Mapping Errors

Slide 47

Slide 47 text

#[derive(Debug, Fail, Copy, Clone, PartialEq, Eq, Hash)] pub enum ErrorKind { #[fail(display = "governor spawn failed")] TroveGovernSpawnFailed, #[fail(display = "governor shutdown failed")] TroveGovernShutdownFailed, } Error Kinds

Slide 48

Slide 48 text

#[derive(Debug)] pub struct Error { inner: Context, } Custom Errors

Slide 49

Slide 49 text

impl Fail for Error { fn cause(&self) -> Option<&Fail> { self.inner.cause() } fn backtrace(&self) -> Option<&Backtrace> { self.inner.backtrace() } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.inner, f) } } Error Pass Through

Slide 50

Slide 50 text

pub fn run(config: Config) -> Result<(), Error> { let trove = Arc::new(Trove::new(config)); trove.govern().context(ErrorKind::TroveGovernSpawnFailed)?; // … } Example Usage

Slide 51

Slide 51 text

use failure::{Error, ResultExt}; pub fn attach_logfile(&mut self, logfile: &str) -> Result<(), Error> { let f = fs::File::open(logfile) .context("Could not open logfile")?; let reader = BufReader::new(f); for line in reader.lines() { let line = line?; User Facing with Error

Slide 52

Slide 52 text

A person who never made a mistake never had to write an error API

Slide 53

Slide 53 text

?