$30 off During Our Annual Pro Sale. View Details »

Failing in Rust

Failing in Rust

A quick talk at a meetup about using failure.

Armin Ronacher

April 24, 2018
Tweet

More Decks by Armin Ronacher

Other Decks in Programming

Transcript

  1. Failing in Rust
    Armin @mitsuhiko Ronacher

    View Slide

  2. View Slide

  3. 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

    View Slide

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

    View Slide

  5. Why do we care?

    View Slide

  6. 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

    View Slide

  7. ways to fail greatly

    View Slide

  8. Mechanisms
    •Result
    •Option
    •panic!

    View Slide

  9. Result Propagation vs Panic
    • Results/Options are for handling

    • panics are for recovering at best

    View Slide

  10. Examples of Panics
    • out of bound access

    • runtime

    View Slide

  11. Examples of Option
    • safe signalling absence of data

    • "the one obvious error"

    View Slide

  12. — Douglas Adams

    View Slide

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

    View Slide

  14. let's talk results

    View Slide

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

    View Slide

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

    View Slide

  17. let val = expr?;

    View Slide

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

    View Slide

  19. error propagation can be hooked!

    View Slide

  20. The Err in Result can be anything :-/

    View Slide

  21. So let's use some traits for Err

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  25. Problems
    • Generic errors give no guarantees

    • no Send / Sync / Debug

    • causes() returns non static errors

    • description() is useless

    • no backtraces

    View Slide

  26. Enter Failure

    View Slide

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

    View Slide

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

    View Slide

  29. failure 0.1 ➠ failure 1.0

    View Slide

  30. 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;
    }

    View Slide

  31. Fail can be derived

    View Slide

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

    View Slide

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

    }

    View Slide

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

    }

    View Slide

  35. Fail & Error

    View Slide

  36. Fail ⟷ Error

    View Slide

  37. Fail for libraries
    Error for applications

    View Slide

  38. What's in the Package

    View Slide

  39. Main Functionality
    • Fail trait

    • Error type

    • Context

    View Slide

  40. 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!

    View Slide

  41. — rustc
    an Error is not a Fail

    View Slide

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

    View Slide

  43. Examples

    View Slide

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

    View Slide

  45. #[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

    View Slide

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

    View Slide

  47. #[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

    View Slide

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

    View Slide

  49. 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

    View Slide

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

    View Slide

  51. 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

    View Slide

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

    View Slide

  53. ?

    View Slide