Support Ukraine. DONATE.
A blog about software development.

Announcing Nutype 0.4.0 Release

Serhii Potapov November 22, 2023 #rust #macro #newtype #nutype

I'm excited to announce the release of Nutype 0.4.0.

What is Nutype?

Nutype is a Rust crate featuring a procedural macro designed to enhance the regular newtype pattern with additional capabilities such as sanitization and validation. This enhancement ensures that values can only be instantiated after successfully passing predefined checks.

Support of arbitrary types

Previously, the #[nutype] macro in Nutype 0.3.x was limited to newtypes encapsulating integers, floats, or strings. With the latest update, it now supports any inner type, allowing for custom sanitization and validation.

Consider the GuestList example: a wrapper around Vec<String>. We define a custom sanitization function to ensure alphabetical sorting of the list, alongside a validation predicate that prohibits empty vectors:

    use nutype::nutype;

    #[nutype(
        derive(Debug, PartialEq, Deref, AsRef),
        sanitize(with = |mut guests| { guests.sort(); guests }),
        validate(predicate = |guests| !guests.is_empty() ),
    )]
    pub struct GuestList(Vec<String>);

    // Empty list is not allowed
    assert_eq!(
        GuestList::new(vec![]),
        Err(GuestListError::PredicateViolated)
    );

    // Create the list of our guests
    let guest_list = GuestList::new(vec![
        "Seneca".to_string(),
        "Marcus Aurelius".to_string(),
        "Socrates".to_string(),
        "Epictetus".to_string(),
    ]).unwrap();

    // The list is sorted (thanks to sanitize)
    assert_eq!(
        guest_list.as_ref(),
        &[
            "Epictetus".to_string(),
            "Marcus Aurelius".to_string(),
            "Seneca".to_string(),
            "Socrates".to_string(),
        ]
    );

Renaming error variants for clarity

This release introduces several breaking changes, particularly in the naming of error variants. Validation errors now adhere to a consistent naming scheme: <ValidationRule>Violated. For instance, a not_empty validator violation in struct Name(String) which previously yielded NameError::Empty will now be NameError::NotEmptyViolated. This change, while initially seeming cumbersome, facilitates a clearer mental mapping between errors and their corresponding validation rules.

Renamed error variants

There is a lot of breaking changes related to the renamed error variants.

Now the validation errors match the naming schema <ValidationRule>Violated.

For example, previously violation of not_empty validator on a struct Name(String) type would result into NameError::Empty. Now it is NameError::NotEmptyViolated. It may seem cluttered and weird at first, but following the strict naming schema allows to easily draw a metal connection between the validation error and a validation rule that causes it.

Enhanced numeric validation

Responding to community feedback, we've expanded numeric validation rules. Instead of min and max, Nutype 0.4.0 introduces:

Example:

    #[nutype(
        validate(
            greater_or_equal = 0,
            less_or_equal = 23,
        ),
    )]
    pub struct Hour(u8);

Trait Derivation

Trait derivation must now be explicitly specified within #[nutype(derive(...))] instead of using #[derive(...)]. This change clarifies that all trait derivations are handled by #[nutype], reducing potential confusion.

Example:

    #[nutype(
        validate(with = |n| n % 2 == 1),
        derive(Debug, Clone, Copy)
    )]
    pub struct OddNumber(u64);

Acknowledgements

Special thanks to Daniyil Glushko for his voluntary contribution in addressing issues in this release. His work was valuable. Based in Zaporizhzhia, Ukraine, Daniyil is a proficient Rust developer open to remote opportunities. I highly recommend him to anyone seeking skilled Rust developers.

Back to top