Support Ukraine. DONATE.
A blog about software development.

Nutype 0.5.0 - custom errors!

Serhii Potapov September 03, 2024 #rust #macro #newtype #nutype #error

I'm pleased to announce the release of Nutype version 0.5.0! This update introduces several valuable features and improvements to the crate. You can find the full release notes on GitHub.

What is Nutype?

Nutype is a Rust crate that extends the newtype pattern with helpful features like sanitization and validation through procedural macros. It ensures that values meet defined constraints before instantiation, offering a safer and more practical alternative to traditional newtypes.

What’s New in Nutype 0.5.0

Custom Errors and Validation Functions

A key addition in Nutype 0.5.0 is support for custom error types and validation functions, giving developers more flexibility and control over error handling. This change should make it easier to manage errors across different parts of an application and tailor them to specific use cases.

Previously, validation in Nutype could be handled using the predicate attribute, which is still available and automatically generates a simple error type:

#[nutype(validate(predicate = |n| n % 2 == 1))]
struct OddNumber(i64);

// The generated error type
enum OddNumberError {
    PredicateViolated,
}

While this approach worked well for simple cases, it was limited in terms of customization. Some users needed more detailed error messages or wanted to map specific errors to a more general error type.

To address this, Nutype now introduces the with attribute for custom validation functions and the error attribute for specifying custom error types. Here’s an example that shows how this can be used:

#[nutype(
    validate(with = validate_name, error = NameError),
    derive(Debug, AsRef, PartialEq),
)]
struct Name(String);

// Custom error type
#[derive(Debug, PartialEq)]
enum NameError {
    TooShort,
    TooLong,
}

// Custom validation function
fn validate_name(name: &str) -> Result<(), NameError> {
    let count = name.encode_utf16().count();
    if count < 3 {
        Err(NameError::TooShort)
    } else if count > 10 {
        Err(NameError::TooLong)
    } else {
        Ok(())
    }
}

With this enhancement, you have more control over how errors are handled, which can lead to clearer and more robust code.

Transition to ::try_new() Constructor

Nutype 0.5.0 also brings a breaking change by fully replacing the fallible ::new() constructor with ::try_new(). In earlier versions, ::new() was deprecated in favor of ::try_new(), which provided a clearer approach to error handling. Now, ::try_new() is the only method for constructing newtypes when validation is required.

For example, using the Name newtype from the earlier example:

let name = Name::try_new("Anton").unwrap();

This approach helps ensure more consistent and explicit error handling. It’s worth noting that ::new() is still used as a non-fallible constructor if a newtype has no validation.

Moving from lazy_static to std::sync::LazyLock

Another important change in Nutype 0.5.0 is the move from lazy_static to std::sync::LazyLock for regex validation. This update requires Rust 1.80 or higher and is intended to offer a more modern approach for handling lazy static values.

For example, a validation using regular expressions now relies on std::sync::LazyLock instead of lazy_static:

#[nutype(
    validate(regex = "^[a-zA-Z]+$"),
    derive(Debug, PartialEq),
)]
struct Name(String);

For those who cannot upgrade Rust at this time, using lazy_static explicitly remains an option.

We hope you enjoy the new features in Nutype 0.5.0!

Back to top