Support Ukraine. DONATE.
A blog about software development.

Nutype 0.4.3 - generic newtypes!

Serhii Potapov July 08, 2024 #rust #macro #newtype #nutype #generics

I'm excited to announce the latest update to Nutype: version 0.4.3! You can find the full release notes on GitHub.

What is Nutype?

Nutype is a Rust crate that enhances the newtype pattern. It leverages procedural macros to add features like sanitization and validation, ensuring values meet predefined criteria before instantiation. Unlike the regular newtype pattern, Nutype makes it practically impossible to instantiate a value of a given type that violates the defined constraints.

Support for Generics in Newtypes

This release introduces support for generics, which is particularly useful when you want to enforce constraints on collections. For example, the following is now possible:

use nutype::nutype;

#[nutype(
    validate(predicate = |vec| !vec.is_empty()),
    derive(Debug, AsRef, PartialEq),
)]
struct NonEmpty<T>(Vec<T>);

// Non-empty vector of i32
let numbers = NonEmpty::try_new(vec![1, 2, 3]).unwrap();
assert_eq!(numbers.as_ref(), &[1, 2, 3]);

// Non-empty vector of chars
let chars = NonEmpty::try_new(vec!['a', 'b']).unwrap();
assert_eq!(chars.as_ref(), &['a', 'b']);

// It's not possible to instantiate an empty vector
let result = NonEmpty::<i32>::try_new(vec![]);
assert_eq!(result, Err(NonEmptyError::PredicateViolated));

As you can see, it is still possible to derive most other traits, including Serialize and Deserialize (requires the serde feature to be enabled).

Setting Up Type Bounds

Let's extend the example above and turn it into SortedNonEmpty. We will use sanitize with sorting to ensure that the inner vector gets sorted when a value is instantiated.

#[nutype(
    validate(predicate = |vec| !vec.is_empty()),
    sanitize(with = |mut v| { v.sort(); v }),
    derive(Debug, PartialEq, AsRef),
)]
struct SortedNonEmpty<T>(Vec<T>);

This fails to compile:

14  |     sanitize(with = |mut v| { v.sort(); v }),
    |                                 ^^^^ the trait `Ord` is not implemented for `T`

Vec::sort requires T to implement Ord. Let's set this bound on our structure:

use std::cmp::Ord;

#[nutype(
    validate(predicate = |vec| !vec.is_empty()),
    sanitize(with = |mut v| { v.sort(); v }),
    derive(Debug, PartialEq, AsRef),
)]
struct SortedNonEmpty<T: Ord>(Vec<T>);

Now it compiles!

let names = SortedNonEmpty::try_new(vec!["Zeno", "Aristotle", "Plato"]).unwrap();
assert_eq!(names.as_ref(), &["Aristotle", "Plato", "Zeno"]);

I hope you find these updates helpful! If you do, please spread the word.

Back to top