Support Ukraine. DONATE.
A blog about software development.

Nutype 0.3.0 released

Serhii Potapov June 25, 2023 #rust #macro #newtype #nutype

I have just released Nutype 0.3.0. In this blog post, I would like to highlight some of the new features.

What is Nutype?

Nutype is a Rust library powered by proc macros that takes type safety to the next level. For an introduction, please refer to the following resources:

Deriving Eq and Ord on f32 and f64 types

Rust has a well-known problem: f32 and f64 types do not implement Ord and Eq traits. The reason for this is the presence of NaN (not a number) value, which cannot be compared to any number.

As a result, even if you create a new float-based newtype it cannot derive Ord or Eq:

#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct Distance(f64);

This will result in compiler errors:

error[E0277]: the trait bound `f64: Ord` is not satisfied
 --> src/main.rs:2:17
  |
1 | #[derive(PartialEq, Eq, PartialOrd, Ord)]
  |                                     --- in this derive macro expansion
2 | struct Distance(f64);
  |                 ^^^ the trait `Ord` is not implemented for `f64`


error[E0277]: the trait bound `f64: Eq` is not satisfied
   --> src/main.rs:2:17
    |
1   | #[derive(PartialEq, Eq, PartialOrd, Ord)]
    |                     -- in this derive macro expansion
2   | struct Distance(f64);
    |                 ^^^ the trait `Eq` is not implemented for `f64`

This limitation can be annoying when you want to use floats in other structures that need to implement Eq, and you know that your values are never going to be NaN.

With Nutype, you can use finite validation, which ensures that is_finite predicate is satisfied. This excludes NaN values and enables correct implementation of Eq and Ord traits:

use nutype::nutype;

#[nutype(validate(finite))]
#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct Distance(f64);

fn main() {
    let distance = Distance::new(12.0).unwrap();
}

Deriving Default

In Nutype 0.3.0, it's now possible to derive Default trait. Therefore, the following code works:

use nutype::nutype;

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

fn main() {
    let odd = OddNumber::default();
    assert_eq!(odd.into_inner(), 1);
}

Here is one tricky aspect: since the validation is dynamic, how can nutype guarantee that the default value is valid at compile time? Unfortunately it's not possible. So the following compiles:

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

However, it would panic when attempting to obtain an invalid OddNumber by invoking OddNumber::default(). Additionally, nutype generates a unit test to ensure that OddNumber::default() returns a valid value.

Running cargo test with an example above fails:

test __nutype_private_OddNumber__::should_have_valid_default_value ... FAILED

failures:

thread '__nutype_private_OddNumber__::should_have_valid_default_value' panicked at '
Default value for type OddNumber is invalid.
ERROR: invalid

This should be sufficient to catch potential errors at an early stage of development

Back to top