Skip to content

Commit f2fd76e

Browse files
authored
Merge pull request #197 from greyblake/const
Const
2 parents 811d3d8 + 49c279a commit f2fd76e

File tree

25 files changed

+497
-63
lines changed

25 files changed

+497
-63
lines changed

CHANGELOG.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
### v0.x.x - 2025-xx-xx
2-
3-
* Placeholder
2+
* **[FEATURE]** Ability to instantiate types in `const` context, when declared with `const_fn` flag.
43

54
### v0.5.1 - 2024-12-20
65

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ members = [
2222
"examples/string_arbitrary",
2323
"examples/any_generics",
2424
"examples/custom_error",
25+
"examples/const_example",
2526
]

README.md

+41
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Nutype is a proc macro that allows adding extra constraints like _sanitization_
2121
* [Quick start](#quick-start)
2222
* [Inner types](#inner-types) ([String](#string) | [Integer](#integer) | [Float](#float) | [Other](#other-inner-types-and-generics))
2323
* [Custom](#custom-sanitizers) ([sanitizers](#custom-sanitizers) | [validators](#custom-validators) | [errors](#custom-validation-with-a-custom-error-type))
24+
* [Constants](#constants)
2425
* [Recipes](#recipes)
2526
* [Breaking constraints with new_unchecked](#breaking-constraints-with-new_unchecked)
2627
* [Feature Flags](#feature-flags)
@@ -345,6 +346,46 @@ struct Name(String);
345346

346347
It's important to ensure that the type specified in the `error` attribute matches the error type returned by the validation function.
347348

349+
## Constants
350+
351+
You can mark a type with the `const_fn` flag. In that case, its `new` and `try_new` functions will be declared as `const`:
352+
353+
```rust
354+
#[nutype(
355+
const_fn,
356+
derive(Debug),
357+
validate(greater_or_equal = -273.15),
358+
)]
359+
pub struct Celsius(f64);
360+
```
361+
362+
Since `Result::unwrap()` is not allowed in `const` contexts, we must manually handle the `Result` when creating constants. Any attempt to instantiate an invalid `Celsius` at compile time will trigger a compilation error:
363+
364+
```rust
365+
const FREEZING_POINT: Celsius = match Celsius::try_new(0.0) {
366+
Ok(value) => value,
367+
Err(_) => panic!("Invalid value"),
368+
};
369+
```
370+
371+
Alternatively, you can use a helper macro like this:
372+
373+
```rust
374+
macro_rules! nutype_const {
375+
($name:ident, $ty:ty, $value:expr) => {
376+
const $name: $ty = match <$ty>::try_new($value) {
377+
Ok(value) => value,
378+
Err(_) => panic!("Invalid value"),
379+
};
380+
};
381+
}
382+
383+
nutype_const!(WATER_BOILING_POINT, Celsius, 100.0);
384+
```
385+
386+
Note that `const` works only for stack allocated types.
387+
If you are dealing with a heap allocated type (e.g. `String`) you should consider using `static` with [`LazyLock`](https://doc.rust-lang.org/beta/std/sync/struct.LazyLock.html).
388+
348389
## Recipes
349390

350391
### Derive `Default`

dummy/src/main.rs

+15-27
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,23 @@
11
use nutype::nutype;
22

3-
fn validate_name(name: &str) -> Result<(), NameError> {
4-
if name.len() < 3 {
5-
Err(NameError::TooShort)
6-
} else if name.len() > 10 {
7-
Err(NameError::TooLong)
8-
} else {
9-
Ok(())
10-
}
11-
}
12-
13-
#[derive(Debug, PartialEq)]
14-
enum NameError {
15-
TooShort,
16-
TooLong,
17-
}
18-
193
#[nutype(
20-
validate(with = validate_name, error = NameError),
21-
derive(Debug, AsRef, PartialEq),
4+
const_fn,
5+
derive(Debug),
6+
validate(greater_or_equal = -273.15),
227
)]
23-
struct Name(String);
8+
pub struct Celsius(f64);
249

25-
fn main() {
26-
let name = Name::try_new("John").unwrap();
27-
assert_eq!(name.as_ref(), "John");
10+
macro_rules! nutype_const {
11+
($name:ident, $ty:ty, $value:expr) => {
12+
const $name: $ty = match <$ty>::try_new($value) {
13+
Ok(value) => value,
14+
Err(_) => panic!("Invalid value"),
15+
};
16+
};
17+
}
2818

29-
assert_eq!(
30-
Name::try_new("JohnJohnJohnJohnJohn"),
31-
Err(NameError::TooLong)
32-
);
19+
nutype_const!(WATER_BOILING_POINT, Celsius, 100.0);
3320

34-
assert_eq!(Name::try_new("Jo"), Err(NameError::TooShort));
21+
fn main() {
22+
println!("{:?}", WATER_BOILING_POINT);
3523
}

examples/const_example/Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "const_example"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
nutype = { path = "../../nutype", features = ["new_unchecked"] }

examples/const_example/src/main.rs

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Adding `const_fn` flag to #[nutype] macro will make the generated `new` and `try_new` functions
2+
// be marked with `const` keyword.
3+
use nutype::nutype;
4+
5+
#[nutype(const_fn, validate(greater_or_equal = 18))]
6+
pub struct Age(u8);
7+
8+
// Unfortunately `Result::unwrap` is not a `const` function, so we have
9+
// to unwrap it ourselves.
10+
const DRINKING_AGE: Age = match Age::try_new(21) {
11+
Ok(age) => age,
12+
Err(_) => panic!("Invalid Age"),
13+
};
14+
15+
// Until `const` functions are stabilized, we recommend to define in application
16+
// and reuse `nutype_const!` macro to create constants from `nutype` types
17+
#[nutype(
18+
const_fn,
19+
validate(greater_or_equal = -1.0, less_or_equal = 1.0)
20+
)]
21+
struct Correlation(f32);
22+
23+
macro_rules! nutype_const {
24+
($name:ident, $ty:ty, $value:expr) => {
25+
const $name: $ty = match <$ty>::try_new($value) {
26+
Ok(value) => value,
27+
Err(_) => panic!("Invalid value"),
28+
};
29+
};
30+
}
31+
32+
nutype_const!(MAX_CORRELATION, Correlation, 1.0);
33+
34+
// Not recommended, but it's possible to use `new_unchecked` with `const_fn` flag.
35+
#[nutype(
36+
const_fn,
37+
new_unchecked,
38+
validate(greater_or_equal = 1, less_or_equal = 6,)
39+
)]
40+
struct TaxClass(u8);
41+
42+
const DEFAULT_TAX_CLASS: TaxClass = unsafe { TaxClass::new_unchecked(1) };
43+
44+
// Note: `into_inner()` is const function as well.
45+
nutype_const!(DOUBLE_AGE, Age, DRINKING_AGE.into_inner() * 2);
46+
47+
fn main() {
48+
assert_eq!(DRINKING_AGE.into_inner(), 21);
49+
assert_eq!(DEFAULT_TAX_CLASS.into_inner(), 1);
50+
assert_eq!(MAX_CORRELATION.into_inner(), 1.0);
51+
assert_eq!(DOUBLE_AGE.into_inner(), 42);
52+
}

nutype/src/lib.rs

+43
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,49 @@
396396
//!
397397
//! It's important to ensure that the type specified in the `error` attribute matches the error type returned by the validation function.
398398
//!
399+
//!
400+
//! ## Constants
401+
//!
402+
//! You can mark a type with the `const_fn` flag. In that case, its `new` and `try_new` functions will be declared as `const`:
403+
//!
404+
//! ```
405+
//! use nutype::nutype;
406+
//!
407+
//! #[nutype(
408+
//! const_fn,
409+
//! derive(Debug),
410+
//! validate(greater_or_equal = -273.15),
411+
//! )]
412+
//! pub struct Celsius(f64);
413+
//!
414+
//! // Since `Result::unwrap()` is not allowed in `const` contexts,
415+
//! // we must manually handle the `Result` when creating constants.
416+
//! // Any attempt to instantiate an invalid `Celsius` at compile time
417+
//! // will trigger a compilation error:
418+
//! const FREEZING_POINT: Celsius = match Celsius::try_new(0.0) {
419+
//! Ok(value) => value,
420+
//! Err(_) => panic!("Invalid value"),
421+
//! };
422+
//!
423+
//! assert_eq!(FREEZING_POINT.into_inner(), 0.0);
424+
//!
425+
//! // Alternatively, you can use a helper macro like this:
426+
//! macro_rules! nutype_const {
427+
//! ($name:ident, $ty:ty, $value:expr) => {
428+
//! const $name: $ty = match <$ty>::try_new($value) {
429+
//! Ok(value) => value,
430+
//! Err(_) => panic!("Invalid value"),
431+
//! };
432+
//! };
433+
//! }
434+
//!
435+
//! nutype_const!(WATER_BOILING_POINT, Celsius, 100.0);
436+
//!
437+
//! assert_eq!(WATER_BOILING_POINT.into_inner(), 100.0);
438+
//! ```
439+
//! Note that `const` works only for stack allocated types.
440+
//! If you are dealing with a heap allocated type (e.g. `String`) you should consider using `static` with [`LazyLock`](https://doc.rust-lang.org/beta/std/sync/struct.LazyLock.html).
441+
//!
399442
//! ## Recipes
400443
//!
401444
//! ### Derive `Default`

nutype_macros/src/any/gen/mod.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::common::{
1111
gen::{
1212
tests::gen_test_should_have_valid_default_value, traits::GeneratedTraits, GenerateNewtype,
1313
},
14-
models::{ErrorTypePath, Guard, TypeName, TypedCustomFunction},
14+
models::{ConstFn, ErrorTypePath, Guard, TypeName, TypedCustomFunction},
1515
};
1616

1717
use self::error::gen_validation_error_type;
@@ -32,6 +32,7 @@ impl GenerateNewtype for AnyNewtype {
3232
fn gen_fn_sanitize(
3333
inner_type: &Self::InnerType,
3434
sanitizers: &[Self::Sanitizer],
35+
const_fn: ConstFn,
3536
) -> TokenStream {
3637
let transformations: TokenStream = sanitizers
3738
.iter()
@@ -52,7 +53,7 @@ impl GenerateNewtype for AnyNewtype {
5253
.collect();
5354

5455
quote!(
55-
fn __sanitize__(mut value: #inner_type) -> #inner_type {
56+
#const_fn fn __sanitize__(mut value: #inner_type) -> #inner_type {
5657
#transformations
5758
value
5859
}
@@ -63,6 +64,7 @@ impl GenerateNewtype for AnyNewtype {
6364
inner_type: &Self::InnerType,
6465
error_type_path: &ErrorTypePath,
6566
validators: &[Self::Validator],
67+
const_fn: ConstFn,
6668
) -> TokenStream {
6769
let validations: TokenStream = validators
6870
.iter()
@@ -98,7 +100,7 @@ impl GenerateNewtype for AnyNewtype {
98100
// Since this code is generic which is used for different inner types (not only Cow), we cannot easily fix it to make
99101
// clippy happy.
100102
#[allow(clippy::ptr_arg)]
101-
fn __validate__<'nutype_a>(val: &'nutype_a #inner_type) -> ::core::result::Result<(), #error_type_path> {
103+
#const_fn fn __validate__<'nutype_a>(val: &'nutype_a #inner_type) -> ::core::result::Result<(), #error_type_path> {
102104
#validations
103105
Ok(())
104106
}

nutype_macros/src/any/parse.rs

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub fn parse_attributes(
2626
sanitizers,
2727
validation,
2828
new_unchecked,
29+
const_fn,
2930
default,
3031
derive_traits,
3132
} = attrs;
@@ -36,6 +37,7 @@ pub fn parse_attributes(
3637
let guard = validate_any_guard(raw_guard, type_name)?;
3738
Ok(Attributes {
3839
new_unchecked,
40+
const_fn,
3941
guard,
4042
default,
4143
derive_traits,

0 commit comments

Comments
 (0)