4

SNAFU 0.7 Released

 2 years ago
source link: https://users.rust-lang.org/t/snafu-0-7-released/69766/2
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

What is it?

SNAFU (Situation Normal: All Fouled Up) is a procedural macro and support library to generate strongly-typed errors that add valuable context to runtime failures. This improves the effectiveness and actionability of those errors to both the end-user and the programmer.

// We support struct errors ...
#[derive(Debug, Snafu)]
#[snafu(display("An error occurred for {username}"))]
struct OneKindOfError { username: String }

// ... enum errors ...
#[derive(Debug, Snafu)]
enum AnotherKindOfError {
    #[snafu(display("Unable to finish situation one"))]
    SituationOne { source: OneKindOfError },

    #[snafu(display("Unable to finish situation two for {user_id}"))]
    SituationTwo { source: OneKindOfError, user_id: u32 },
}

// ... and opaque errors, great for exposing as part of a public API.
#[derive(Debug, Snafu)]
pub struct Error(AnotherKindOfError);

What's new?

The big-ticket changes in 0.7 are:

  • SNAFU's context selectors now have the suffix Snafu.
  • Context selectors can be placed into a module.
  • A batteries-included Whatever type allows getting started quickly.
  • Shorthand formatting syntax simplifies defining error messages.

These changes are discussed later in this post. A more detailed list of changes can be found in the changelog 28.

What's still there?

  • Custom error types using structs and enums.
  • Extension traits for Result, Option, Future, and Stream.
  • Suitable for libraries and applications.
  • no_std compatibility.
  • Errors containing generic types and lifetimes.

How do I upgrade?

This release contains breaking changes. However, most of those changes can be automatically fixed by using the SNAFU upgrade assistant 15. This is designed to automatically apply changes that address the compiler errors caused by the upgrade from 0.6 to 0.7.

If you don't feel comfortable with the automated tool, there are a few main changes:

  1. Renaming context selectors from enums to add Snafu.
  2. Renaming context selectors from structs to remove Context and add Snafu.
  3. Adding an argument to most calls to with_context
  4. Removal of the snafu(visibility = "foo") attribute format.

Tell me more about the new features!

The Snafu suffix for context selectors

SNAFU's core innovation was the introduction of context selectors, small types that allow categorizing a lower-level error into something more semantic while adding additional detail. For example, you can
categorize an io::Error into either UnableToReadConfiguration or PaymentRequestDenied.

However, the original implementation of this used the name of the enum variant as the context selector's name. This made sense to SNAFU's author and anyone else brave enough to dive into the code generated by the procedural macro, but proved to be a large stumbling block for almost everyone else.

When struct error support was added in 0.6.9, using the same name for the context selector would have conflicted with the error type, so we added the suffix Context.

In version 0.7, we apply that suffix, now written as Snafu, on all context selectors by default. Any existing Error suffix is removed before Snafu is added:

#[derive(Debug, Snafu)]
struct StructError;

#[derive(Debug, Snafu)]
enum EnumError {
    VariantError,
}

// In SNAFU 0.6, the two context selectors differ from each other, and
// the variant context selector is often confused with the variant
// itself.
ensure!(false, StructContext);
ensure!(false, VariantError);

// In SNAFU 0.7, both types of context selector end with `Snafu` and
// the variant context selector is less confusable with the enum
// variant.
ensure!(false, StructSnafu);
ensure!(false, VariantSnafu);

If you prefer a different suffix, you can configure it. Check out the documentation 12 for complete details.

Grouping context selectors in a module

When making extensive use of SNAFU, you may end up with multiple error types creating context selectors with the same name in one module. Other people prefer namespacing their context selectors for
organizational reasons. SNAFU 0.7 offers that as a first-class option:

#[derive(Debug, Snafu)]
#[snafu(module)]
struct Error { name: String }

fn example() -> Result<i32, Error> {
    error::Snafu { name: "jumping josie" }.fail()
}

This can be used with the suffix configuration feature to remove the Snafu suffix, relying solely on the module to distinguish between the error type or enum variants and the related context selectors.

The Whatever type and stringly-typed errors

When adding errors to a new or existing project, you may not want to deal with the effort needed to define distinct error types. SNAFU 0.7offers stringly-typed errors to accomodate these cases.

The simplest starting point is the provided Whatever type paired with the whatever! macro:

fn leaf_example() -> Result<(), Whatever> {
    whatever!("This is a leaf error {}", "some value");
}

fn wrapping_example() -> Result<(), Whatever> {
    std::fs::read_to_string("/this/does/not/exist")
        .whatever_context("This wraps another error")?;
    Ok(())
}

As your project matures, you will find yourself wanting more structure in your errors. To ease the transition, you can combine stringly- and strongly-typed errors:

#[derive(Debug, Snafu)]
enum Error {
    #[snafu(display("{name} tried to do something bad"))]
    StronglyTyped { name: String },

    #[snafu(whatever, display("{message}"))]
    Whatever {
        message: String,
        #[snafu(source(from(Box<dyn std::error::Error>, Some)))]
        source: Option<Box<dyn std::error::Error>>,
    },
}

Shorthand formatting syntax

Taking inspriation from RFC 2795 7, SNAFU now allows a shorthand syntax for the display attribute:

#[derive(Debug, Snafu)]
#[snafu(display("{} tried to put in {} bytes", name, size))]
struct Snafu06Example { name: String, size: u32 }

#[derive(Debug, Snafu)]
#[snafu(display("{name} tried to put in {size} bytes"))]
struct Snafu07Example { name: String, size: u32 }

Contributors

The following community members have contributed code to this release:

I'd also like to thank Dennis Duda and Carol (Nichols || Goulding) 6 for their great help providing a sounding board for design advice as well as documentation proofreading.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK