You can create NonZero numeric types and check at compile time that they are in fact, nonzero like so:
const { NonZeroU8::new(4).unwrap() }
But, it requires using a const
block and an unwrap
. It would be great to be able to just have a literal like 4
coerce into a NonZeroU8
similarly to how a literal such as 4
can coerce into a u8
or a u32
I'm proposing some kind of mechanism to allow users to define automatic coercion from an untyped numeric literal (such as 4
, -11
, but not 4u8
) into an arbitrary type. For example via some const
Trait.
Then, types such as NonZeroU8
, NonZeroU32
could implement this Trait. You could then freely use the untyped numeric literals in places where NonZeroU8
and etc are expected.
An example:
use std::num::{NonZeroU8, NonZeroU16};
fn takes_nonzero_u8(n: std::num:::NonZeroU8) {}
fn main() {
// 4 can coerce into a NonZeroU8, which is checked for correctness at compile time
let a: NonZeroU8 = 4;
takes_nonzero_u8(44);
// this would not compile:
let b: NonZeroU8 = -4;
// similar to:
// let b = const { NonZeroU8::new(-4).unwrap() }
}
Example definition of the u8::Coerce
trait:
#[const_trait]
trait Coerce {
fn coerce(n: u8) -> Self;
}
Example Implementation and usage of u8::Coerce
for a struct NonZeroU16
:
struct NonZeroU16 {
inner: u16,
}
/// Any numeric type that can coerce to
/// a u8 will also be allowed to coerce to a NonZeroU16
impl const u8::Coerce for NonZeroU16 {
fn coerce(n: u8) -> Self {
if n == 0 {
panic!("Cannot be zero");
}
Self { inner: n as u16 }
}
}
fn main() {
// compiles
let a: NonZeroU16 = 4;
// compile error: expected NonZeroU16, found u8
let a: NonZeroU16 = 4u8;
// compile error: Cannot be zero
let c: NonZeroU16 = 0;
}
This trait (u8::Coerce
) would only work on untyped numeric literals that can coerce to a u8. All of the below examples will fail to compile:
fn main() {
let a: u16 = 4;
// compile error: expected NonZeroU16, found u16
let b: NonZeroU16 = a;
// compile error: expected NonZeroU16, found u16
let c: NonZeroU16 = 4u16;
// compile error: 1000 cannot coerce to u8
let d: NonZeroU16 = 1000;
// compile error: expected NonZeroU16, found u16
let e: NonZeroU16 = 4_u8 as u16;
}
However, if we also implement u16::Coerce
then those examples will compile:
impl const u16::Coerce for NonZeroU8 {
fn coerce(n: u16) -> Self {
if n == 0 {
panic!("Cannot be zero");
}
Self { inner: n }
}
}
fn main() {
// compiles now
let d: NonZeroU16 = 1000;
// literals that can coerce to *either* u8 or u16 can
// coerce to a NonZeroU16
}
Note: Since we implemented both u16::Coerce
and u8::Coerce
for NonZeroU16
, we could remove the u8::Coerce
implementation as untyped literals that can coerce into a u8
can also coerce into a u16
.
Summary
We would add the following traits:
std::u8::Coerce
std::u16::Coerce
- ...
std::i8::Coerce
std::i16::Coerce
- ...
std::f16::Coerce
std::f32::Coerce
- ...
std::usize::Coerce
std::isize::Coerce
When some type T
implements $num_type::Coerce
(where $num_type
is any of the numeric types u8
, i8
, f32
... ), any untyped numeric literal (Such as 4
, -12.4
, but not 100_u8
) that can coerce to $num_type
will be able to be implicitly converted to T
by using $num_type::Coerce::coerce
Advantages
- Improves ergonomics when creating number literals that must adhere to specific invariants checked at compile time
- Allows implementing this trait for custom structs, such as a
NonZeroU32
that must never be zero orEvenU32
that must always store an even number. - Does not rely on new syntax (such as adding new numeric type suffixes)
Disadvantages
- Verbose implementation: Adds a new trait to the standard library for each numeric type
- Adds more implicit coercions to the language
- Relies on
#![feature(const_trait_impl)]
- Cannot be implemented in Rust, requires changes to the compiler itself
Alternatives
- Specifically for
NonZero*
, we could add custom suffixes such as16_nzu8
forstd::num::NonZeroU8
. This means we will lose the flexibility of a generic implementation, as well as adding extra syntax - Do nothing, and when we want to create a type like
NonZeroU8
useconst { NonZeroU8::new(4).unwrap() }