Releases: greyblake/nutype
Nutype 0.7.0 - conditional derives and more
What is nutype?
Nutype is a Rust proc macro for type-driven domain modeling: it turns the newtype pattern into refined, branded types with built-in sanitizers (trim, lowercase, custom) and validators (length, range, regex, predicates, custom errors). It's "parse, don't validate" applied to your domain, useful for DDD, schema validation at API boundaries, and making illegal states unrepresentable, even across serde, FromStr, and arbitrary fuzzing.
Changes in v0.7.0
- [BREAKING] Renamed
derive_unsafetoderive_unchecked(both the feature flag and the attribute). - [FEATURE] Support
cfg_attrfor conditional derives (see #188). - [FEATURE] Support
whereclauses in generic newtypes, including Higher-Ranked Trait Bounds (HRTB) (see #160). - [FEATURE] Ability to control constructor visibility with the
constructor(visibility = ...)attribute (see #211). - [FEATURE] Add
len_utf16_minandlen_utf16_maxvalidators for string types (see #162). - [FEATURE] Ability to derive
Valuable(requires thevaluablefeature).
Conditional derives via cfg_attr
A long-standing request (#188): derive traits only when a feature is enabled, only in tests, or under any other cfg predicate.
In v0.7.0 you can now write cfg_attr(...) directly inside #[nutype(...)], mirroring the standard #[cfg_attr] syntax:
use nutype::nutype;
#[nutype(
sanitize(trim, lowercase),
validate(not_empty, len_char_max = 100),
derive(Debug, Clone, PartialEq, AsRef),
cfg_attr(feature = "serde", derive(Serialize, Deserialize))
)]
pub struct Email(String);Here Serialize and Deserialize are only derived when the serde feature is active. Note: nutype/serde must still be enabled at compile time so the macro recognizes those traits, but the actual derive is gated by cfg_attr.
You can use any predicate the standard cfg_attr accepts, including all(...), any(...), and not(...), and you can stack multiple cfg_attr(..) entries:
#[nutype(
validate(not_empty),
derive(Debug),
cfg_attr(test, derive(Clone)),
cfg_attr(feature = "serde", derive(Serialize, Deserialize))
)]
pub struct Tag(String);A complete walkthrough lives in examples/cfg_attr_example.
where clauses and HRTB for generic newtypes
Generic newtypes now accept full where clauses, including Higher-Ranked Trait Bounds (#160):
use nutype::nutype;
#[nutype(
validate(predicate = |c| c.into_iter().next().is_some()),
derive(Debug)
)]
struct NonEmpty<C>(C)
where
for<'a> &'a C: IntoIterator;
let xs = NonEmpty::try_new(vec![1, 2, 3]).unwrap();Inline bounds and where clauses can also be combined:
#[nutype(derive(Debug, Clone))]
struct Combined<T: Clone>(T)
where
T: Default;Constructor visibility
Until now, ::new() / ::try_new() were always pub. In v0.7.0 you can pin the constructor to any visibility you like via constructor(visibility = ...) (#211):
use nutype::nutype;
#[nutype(
constructor(visibility = pub(crate)),
validate(not_empty),
derive(Debug, AsRef),
)]
pub struct InternalId(String);Supported values are pub, pub(crate), pub(super), pub(in path), and private. With private, the constructor is only callable from the module where the type is defined, useful for types that should only be obtained through a higher-level factory.
UTF-16 length validators
JavaScript and a few other ecosystems count string length in UTF-16 code units rather than Unicode characters or bytes. To make interop straightforward, v0.7.0 adds len_utf16_min and len_utf16_max (#162):
use nutype::nutype;
#[nutype(
validate(len_utf16_min = 1, len_utf16_max = 280),
derive(Debug, AsRef),
)]
pub struct Tweet(String);These complement the existing len_char_min / len_char_max (Unicode chars) validators.
Deriving Valuable
When the valuable feature is enabled, you can now derive Valuable on your newtypes, useful for structured logging with tracing and similar instrumentation:
use nutype::nutype;
use valuable::Valuable;
#[nutype(derive(Valuable))]
pub struct Age(u32);
#[nutype(derive(Valuable))]
pub struct Name(String);
assert_eq!(format!("{:?}", Age::new(25).as_value()), "Age(25)");Add it to your Cargo.toml as:
nutype = { version = "0.7", features = ["valuable"] }
valuable = { version = "0.1", features = ["derive"] }Links
Nutype 0.6.2 - derive_unsafe
What is nutype?
Nutype is a proc macro that adds sanitization_ and validation to newtypes, ensuring values always pass checks.
Changes in v0.6.2
- [FEATURE] Introduce
derive_unsafe(..)attribute to derive any arbitrary trait (requiresderive_unsafefeature to be enabled).
- [FIX] Updated the Rust edition: 2021 β 2024.
- [FIX] Improved error messages for
len_char_maxandlen_char_minvalidators. They are now clearer and easier to understand.
derive_unsafe
You can now use the new derive_unsafe(..) attribute to derive arbitrary traits, including third-party ones, which are not known to nutype.
Unlike derive(..), this mechanism bypasses nutypeβs internal safety checks, meaning it's possible to violate validation rules at runtime if you're deriving a trait that has methods that instantiate or mutate a value.
It requires derive_unsafe feature flag to be enabled.
Example (do not copy blindly):
use derive_more::{Deref, DerefMut};
use nutype::nutype;
#[nutype(
derive(Debug, AsRef),
derive_unsafe(Deref, DerefMut),
validate(greater_or_equal = 0.0, less_or_equal = 2.0)
)]
struct LlmTemperature(f64);
fn main() {
let mut temperature = LlmTemperature::try_new(1.5).unwrap();
// This is not what nutype is designed for!
*temperature = 2.5;
// OH no, we've just violated the validation rule!
assert_eq!(temperature.as_ref(), &2.5);
}The takeaway: derive_unsafe gives you raw powerβbut youβre on your own to ensure your typeβs invariants aren't broken.
On that note, have a nice day!.
Nutype 0.6.1 - tiny bug fix release
Changes
- Fix
derive(Deserialize)for no_std (see #207)
Nutype v0.6.0 - const_fn and into_iterator
What is nutype?
Nutype is a proc macro that adds sanitizatio_ and validation to newtypes, ensuring values always pass checks, even with serde deserialization.
Changes in v0.6.0
- [FEATURE] You can now instantiate types in a
constcontext if they are declared with theconst_fnflag. - [FEATURE] You can now derive
IntoIteratorfor types wrapping inner types that implementIntoIterator. - [FIX]
&'a stris now supported as an inner type. - [BREAKING] The fallible
::new()constructor has been removed (it was deprecated in 0.4.3).
Const functions
The #[nutype] macro can now accept the const_fn flag, which instructs it to generate const fn new() / const fn try_new() functions. This allows you to create instances in a const context:
use nutype::nutype;
#[nutype(
const_fn,
validate(greater_or_equal = -1.0, less_or_equal = 1.0)
)]
struct Correlation(f64);
// Since Result::unwrap() is not yet supported in a const context, we need to handle the result manually:
const ZERO_CORRELATION: Correlation = match Correlation::try_new(0.0) {
Ok(c) => c,
Err(_) => panic!("Invalid Correlation value"),
};Because manually unwrapping in a const context can be tedious, you can use a helper macro like the one below (not part of nutype):
macro_rules! nutype_const {
($name:ident, $ty:ty, $value:expr) => {
const $name: $ty = match <$ty>::try_new($value) {
Ok(value) => value,
Err(_) => panic!("Invalid value"),
};
};
}
nutype_const!(ZERO_CORRELATION, Correlation, 0.0);IntoIterator
Types that wrap a collection can now derive IntoIterator. This automatically provides both a consuming iterator (impl IntoIterator for T) and an iterator over references (impl IntoIterator for &T).
Example:
use nutype::nutype;
#[nutype(derive(IntoIterator))]
struct Names(Vec<String>);
fn main() {
let names = Names::new(vec![
"Alice".to_string(),
"Bob".to_string(),
]);
// Iterate over references
for name in &names {
println!("{}", name);
}
// Iterate over owned values (consuming iterator)
for name in names {
println!("{}", name);
}
}Nutype 0.5.1 - enhanced no_std support and bug fixes
I am excited to announce the release of Nutype 0.5.1, which brings some fixes for an improved developer experience. Below is an overview of what's included in this version:
π New Features
no_stdSupport for::core::error::Error
Nutype now generates an implementation of::core::error::Errorinno_stdenvironments when using Rust version 1.81 or higher. This enhancement ensures better compatibility with modern Rust ecosystems.
π οΈ Bug Fixes
-
Custom Error Paths
You can now specify custom errors using a path, providing greater flexibility for your error-handling needs.
(#186, #187) -
DeserializeDerive Compatibility
Resolved an issue where derivingDeserializecaused compilation errors when using bothno_stdandserdefeatures.
(#182) -
Lint Warnings
Fixed unnecessary lint warnings related to the inner generated module for cleaner builds. -
Conflict with
borschCrate
Addressed a conflict with theborschcrate to ensure seamless integration.
(#195)
Nutype 0.5.0 with custom errors
Changes
- [FEATURE] Added support for custom error types and validation functions via the
errorandwithattributes. - [BREAKING] Replaced
lazy_staticwithstd::sync::LazyLockfor regex validation. This requires Rust 1.80 or higher and may cause compilation issues on older Rust versions due to the use ofstd::sync::LazyLock. If upgrading Rust isn't an option, you can still uselazy_staticexplicitly as a workaround. - [BREAKING] The fallible
::new()constructor has been fully replaced by::try_new().
Highlights
Custom errors
Previously, custom validation logic in nutype could be achieved by passing a predicate attribute, as shown below:
#[nutype(validate(predicate = |n| n % 2 == 1))]
struct OddNumber(i64);This would automatically generate a simple error type:
enum OddNumberError {
PredicateViolated,
}However, this approach often lacked flexibility. Many users needed more detailed error handling. For example, some users wanted to attach additional information to errors or provide more descriptive error messages. Others preferred to use a single error type across their application, but found it cumbersome to map very specific errors to a more general error type.
To address these needs, nutype now introduces the with attribute for custom validation functions and the error attribute for specifying custom error types:
use nutype::nutype;
// Define a newtype `Name` with custom validation logic and a custom error type `NameError`.
// If validation fails, `Name` cannot be instantiated.
#[nutype(
validate(with = validate_name, error = NameError),
derive(Debug, AsRef, PartialEq),
)]
struct Name(String);
// Custom error type for `Name` validation.
// You can use `thiserror` or similar crates to provide more detailed error messages.
#[derive(Debug, PartialEq)]
enum NameError {
TooShort { min: usize, length: usize },
TooLong { max: usize, length: usize },
}
// Validation function for `Name` that checks its length.
fn validate_name(name: &str) -> Result<(), NameError> {
const MIN: usize = 3;
const MAX: usize = 10;
let length = name.encode_utf16().count();
if length < MIN {
Err(NameError::TooShort { min: MIN, length })
} else if length > MAX {
Err(NameError::TooLong { max: MAX, length })
} else {
Ok(())
}
}With this enhancement, users have full control over the error type and how errors are constructed during validation, making the error handling process more powerful and adaptable to different use cases.
Transition from fallible::new() to ::try_new()
In version 0.4.3, the fallible ::new() constructor was deprecated but still available. Now, it has been fully replaced by ::try_new(). For example, to initialize a Name from the previous example:
let name = Name::try_new("Anton").unwrap();This change ensures a more consistent and explicit error-handling approach when creating instances.
Note that ::new() is still used as a non-fallible constructor if there a newtype has no validation.
The sponsors β€οΈ
A big shoutout to the true sponsors of this release - my in-laws! Thanks for taking care of my wife and kid, giving me a free weekend to work on Nutype!
Links
Nutype 0.4.3
Changes
- Support generics
- [DEPRECATION] Fallible (when a newtype has validation) constructor
::new()is deprecated. Users should use::try_new()instead. - [FIX] Use absolute path for
::core::result::Resultwhen generating code forderive(TryFrom).
Highlights
This release comes with support of generic types for newtypes!
The example below defines SortedNotEmptyVec<T> wrapper around Vec<T> which is guaranteed to be not empty and sorted.
Note, that type bound T: Ord enables invocation of v.sort() in the sanitization function.
use nutype::nutype;
#[nutype(
sanitize(with = |mut v| { v.sort(); v }),
validate(predicate = |vec| !vec.is_empty()),
derive(Debug, PartialEq, AsRef),
)]
struct SortedNotEmptyVec<T: Ord>(Vec<T>);
let wise_friends = SortedNotEmptyVec::try_new(vec!["Seneca", "Zeno", "Plato"]).unwrap();
assert_eq!(wise_friends.as_ref(), &["Plato", "Seneca", "Zeno"]);
let numbers = SortedNotEmptyVec::try_new(vec![4, 2, 7, 1]).unwrap();
assert_eq!(numbers.as_ref(), &[1, 2, 4, 7]);Links
Nutype 0.4.2
Changes
- Support
no_std( the dependency needs to be declared asnutype = { default-features = false }) - Support integration with
arbitrarycrate (seearbitraryfeature).- Support
Arbitraryfor integer types - Support
Arbitraryfor float types - Support
Arbitraryfor string inner types - Support
Arbitraryfor any inner types
- Support
- Possibility to specify boundaries (
greater,greater_or_equal,less,less_or_equal,len_char_min,len_char_max) with expressions or named constants. - Add
#[inline]attribute to trivial functions - Improve error messages
Highlights
Here is an example of nutype and arbitrary playing together:
use nutype::nutype;
use arbtest::arbtest;
use arbitrary::Arbitrary;
#[nutype(
derive(Arbitrary, AsRef),
sanitize(trim),
validate(
not_empty,
len_char_max = 100,
),
)]
pub struct Title(String);
fn main() {
arbtest(|u| {
// Generate an arbitrary valid Title
let title = Title::arbitrary(u)?;
// The inner string is guaranteed to be non-empty
assert!(!title.as_ref().is_empty());
// The inner string is guaranteed not to exceed 100 characters
assert!(title.as_ref().chars().count() <= 100);
Ok(())
});
}As you can see the derived implementation of Arbitrary respects the validation rules.
In the similar way Arbitrary can be derived for integer and float based types.
Links
Nutype 0.4.0
Acknowledgements
A heartfelt thanks to Daniyil Glushko for his invaluable assistance and exceptional work on this release. Daniyil, located in Zaporizhzhia, Ukraine, is a proficient Rust developer open to remote opportunities. I highly recommend reaching out to him for Rust development roles.
Changes
- [FEATURE] Support of arbitrary inner types with custom sanitizers and validators.
- [FEATURE] Add numeric validator
greater - [FEATURE] Add numeric validator
less - [BREAKING] Removal of asterisk derive
- [BREAKING] Use commas to separate high level attributes
- [BREAKING] Traits are derived with
#[nutype(derive(Debug))]. The regular#[derive(Debug)]syntax is not supported anymore. - [BREAKING] Validator
withhas been renamed topredicateto reflect the boolean nature of its range - [BREAKING] String validator
min_lenhas been renamed tolen_char_minto reflect that is based on UTF8 chars. - [BREAKING] String validator
max_lenhas been renamed tolen_char_maxto reflect that is based on UTF8 chars. - [BREAKING] Rename numeric validator
maxtoless_or_equal - [BREAKING] Rename numeric validator
mintogreater_or_equal - [BREAKING] Rename error variants to follow the following formula:
<ValidationRule>Violated. This implies the following renames:TooShort->LenCharMinViolatedTooLong->LenCharMaxViolatedEmpty->NotEmptyViolatedRegexMismatch->RegexViolatedInvalid->PredicateViolatedTooBig->LessOrEqualViolatedTooSmall->GreaterOrEqualViolatedNotFinite->FiniteViolated
- Better error messages: in case of unknown attribute, validator or sanitizer the possible values are listed.
- [FIX] Make derived
Deserializework with RON format
Feature highlights
Arbitrary inner type
Previously #[nutype] worked only with String, integers and floats.
Now it's possible to use it with any arbitrary type (e.g. Vec<String>):
#[nutype(
validate(predicate = |friends| !friends.is_empty() ),
)]
pub struct Frieds(Vec<String>);New numeric validators
Instead of former min and max integers and floats can now be validated with:
greater_or_equal- Inclusive lower boundgreater- Exclusive lower boundless_or_equal- Inclusive upper boundless- Exclusive upper bound
Example:
#[nutype(
validate(
greater_or_equal = 0,
less_or_equal = 59,
),
)]
pub struct Minute(u8);Derive
Deriving of traits now has to be done explicitly with #[nutype(derive(...))] instead of #[derive(...)]:
Example:
#[nutype(
validate(with = |n| n % 2 == 1),
derive(Debug, Clone, Copy)
)]
pub struct OddNumber(u64);This makes it clear, that deriving is fully handled by #[nutype] and prevents a potential confusion.
Links
Nutype 0.3.1
Changes
- Add ability to derive
Derefon String, integer and float based types.
Examples
use nutype::nutype;
#[nutype]
#[derive(Deref)]
struct Email(String);
let email = Email::new("foo@bar.com")
// Call .len() which is delegated to the inner String due to the deref-coercion mechanism
assert_eq!(email.len(), 11);