Skip to content

berestovskyy/newtype-tools

Repository files navigation

Newtype Tools

Discussions CI Status CD Status no_std Status Coverage Status Docs.rs Crates.io ChangeLog

Ergonomic utilities for the Rust newtype idiom.

Motivation

This crate avoids the bloat of general-purpose "derive-all" libraries, offering instead a minimalist set of powerful tools specifically for the newtype idiom.

Development is focused on three core pillars:

  1. Conversions between newtypes and between newtypes and their inner types.
  2. Operations directly on newtype values.
  3. Iteration over ranges of newtypes.

Usage

cargo add newtype-tools

Examples

The simplest way to use the crate is to define a tuple struct using the newtype attribute:

# #[cfg(feature = "derive")]
# {
// Derive `newtype` with `Amount` properties (see below for more details).
#[newtype_tools::newtype(Amount)]
// More traits can be easily derived:
#[derive(serde::Serialize)]
struct Apples(u64);

// The `newtype` can also be easily extended:
impl core::fmt::Display for Apples {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        core::fmt::Display::fmt(&self.0, f)
    }
}

// Now the `Apples`behave pretty much as their inner type `u64`:
let apple1 = Apples(2);
// `Apples` can be converted from the inner type:
let apple2 = Apples::from(3);
// `Apples` can be added, subtracted and compared:
assert_eq!(apple1 + apple2, Apples(5));
// `Apples` can be multiplied by the inner factor:
assert_eq!(apple1 * 2_u64, Apples(4));
// `Apples` can be divided, returning a inner ratio:
assert_eq!(apple2 / apple1 , 1);
# }

The crate supports two main "presets": Amount and Id. See below for more details.

Instead of using predefined sets, the implementation allows for the derivation of only the specific traits required for a given use case.

newtype conversions:

# #[cfg(feature = "derive")]
# {
use newtype_tools::Newtype;

#[derive(Newtype)]
#[newtype(
    into(Oranges, with = |apples| Oranges((apples.0 / 2) as u32))
)]
struct Apples(u64);
struct Oranges(u32);

let apples = Apples(42);
// `Oranges` can now be created from `Apples`:
let oranges = Oranges::from(apples);

assert_eq!(oranges.0, 21);
# }

newtype operations:

# #[cfg(feature = "derive")]
# {
use newtype_tools::Newtype;

#[derive(Debug, Newtype)]
#[newtype(
    partial_eq(Oranges, with = |apples, oranges| apples.0 == oranges.0 as u64 * 2)
)]
struct Apples(u64);
struct Oranges(u32);

let apples = Apples(42);
let oranges = Oranges(21);

// `Apples` and `Oranges` can now be compared:
assert!(apples == oranges);
# }

newtype range iteration:

# #[cfg(feature = "derive")]
# {
use newtype_tools::{Newtype, Iter};

#[derive(Debug, Newtype)]
struct Apples(u64);

let range = Apples(0)..Apples(42);
// The range of `Apples` can now be iterated:
for apple in range.iter() {
    println!("{apple:?}");
}
# }

Note: This will become even more ergonomic once the Rust Step trait is stabilized.

Newtype Kinds

The crate supports predefined sets of newtype properties. The concept is similar to the phantom_newtype crate but avoids Rust orphan rule limitations by defining the newtype locally. This allows additional traits to be implemented easily and makes the set of derived traits simple to extend.

The supported newtype kinds are:

Trait #[newtype(Amount)] #[newtype(Id)]
Clone
Copy
Debug
Default
Eq¹
Hash¹
Ord¹
PartialEq
PartialOrd
From<Repr>
Add<Self>
AddAssign<Self>
Sub<Self>
SubAssign<Self>
Mul<Repr>
MulAssign<Repr>
Div<Self>
  1. Eq, Ord and Hash are only implemented for integer inner types.

Alternatives

  1. derive_more -- A large library (~10K SLoC) focused on supporting a wide range of traits for any data type. It can be used alongside newtype-tools to derive additional traits as needed: crate | repo.

    The unique value of this newtype-tools crate lies in its lightweight design, support for property sets like #[newtype(Amount)], and built-in range iteration via (Newtype(0)..Newtype(42)).iter().

  2. nutype -- A comprehensive library (7.5K SLoC) primarily focused on newtype validation: crate | repo.

  3. phantom_newtype -- Provides 19 trait implementations out of the box, but lacks a mechanism for custom trait implementations due to the Rust orphan rule: crate | repo.

  4. newtype_derive -- Legacy crate that relies on the custom_derive! declarative macro: crate | repo.

  5. newtype-derive-2018 -- Less outdated, but still based on the declarative macro: crate | repo.

  6. newtype -- An older crate following a similar approach but no longer actively maintained: crate | repo.

References

  1. Rust newtype idiom.
  2. Rust Step trait.

About

Ergonomic utilities for the Rust newtype idiom.

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Packages

 
 
 

Contributors