use core::{convert::Infallible, fmt};
pub trait FromBits<B>: Sized {
type Error: fmt::Display;
const BITS: u32;
fn try_from_bits(bits: B) -> Result<Self, Self::Error>;
fn into_bits(self) -> B;
}
#[macro_export]
macro_rules! enum_from_bits {
(
$(#[$meta:meta])* $vis:vis enum $Type:ident<$uN:ident> {
$(#[$var1_meta:meta])*
$Variant1:ident = $value1:expr,
$(
$(#[$var_meta:meta])*
$Variant:ident = $value:expr
),* $(,)?
}
) => {
$(#[$meta])*
#[repr($uN)]
#[derive(Copy, Clone)]
$vis enum $Type {
$(#[$var1_meta])*
$Variant1 = $value1,
$(
$(#[$var_meta])*
$Variant = $value
),*
}
impl $Type {
const VARIANTS: &'static [Self] = &[
Self::$Variant1,
$(
Self::$Variant,
)*
];
const MAX_VARIANT: Self = {
let mut max = Self::VARIANTS[0];
let mut i = 0;
while i < Self::VARIANTS.len() {
if Self::VARIANTS[i] as $uN > max as $uN {
max = Self::VARIANTS[i];
}
i += 1;
}
max
};
const MIN_VARIANT: Self = {
let mut min = Self::VARIANTS[0];
let mut i = 0;
while i < Self::VARIANTS.len() {
if (Self::VARIANTS[i] as $uN) < min as $uN {
min = Self::VARIANTS[i];
}
i += 1;
}
min
};
const NEEDED_BITS: u32 = {
let max = Self::MAX_VARIANT as $uN;
<$uN>::BITS - max.leading_zeros()
};
const ERROR: &'static str = concat!(
"invalid value for ",
stringify!($Type),
": expected one of [",
stringify!($value1),
$(
", ", stringify!($value),
)*
"]"
);
}
#[automatically_derived]
impl core::convert::TryFrom<$uN> for $Type {
type Error = &'static str;
#[inline]
fn try_from(value: $uN) -> Result<Self, Self::Error> {
match value {
$value1 => Ok(Self::$Variant1),
$(
$value => Ok(Self::$Variant),
)*
_ => Err(Self::ERROR),
}
}
}
#[automatically_derived]
impl $crate::FromBits<$uN> for $Type {
type Error = &'static str;
const BITS: u32 = Self::NEEDED_BITS;
#[inline]
fn try_from_bits(u: $uN) -> Result<Self, Self::Error> {
Self::try_from(u)
}
#[inline]
fn into_bits(self) -> $uN {
self as $uN
}
}
$crate::enum_from_bits!(@bigger $uN, $Type);
};
(@bigger u8, $Type:ty) => {
$crate::enum_from_bits! { @impl u8, $Type, u16, u32, u64, u128, usize }
};
(@bigger u16, $Type:ty) => {
$crate::enum_from_bits! { @impl u16, $Type, u32, u64, u128, usize }
};
(@bigger u32, $Type:ty) => {
$crate::enum_from_bits! { @impl u32, $Type, u64, u128, usize }
};
(@bigger u64, $Type:ty) => {
$crate::enum_from_bits! { @impl u128 }
};
(@bigger $uN:ty, $Type:ty) => {
compile_error!(
concat!(
"repr for ",
stringify!($Type),
" must be one of u8, u16, u32, u64, or u128 (got ",
stringify!($uN),
")",
));
};
(@impl $uN:ty, $Type:ty, $($bigger:ty),+) => {
$(
#[automatically_derived]
impl $crate::FromBits<$bigger> for $Type {
type Error = &'static str;
const BITS: u32 = Self::NEEDED_BITS;
#[inline]
fn try_from_bits(u: $bigger) -> Result<Self, Self::Error> {
Self::try_from(u as $uN)
}
#[inline]
fn into_bits(self) -> $bigger {
self as $bigger
}
}
#[automatically_derived]
impl core::convert::TryFrom<$bigger> for $Type {
type Error = &'static str;
#[inline]
fn try_from(u: $bigger) -> Result<Self, Self::Error> {
Self::try_from(u as $uN)
}
}
)+
};
}
macro_rules! impl_frombits_for_ty {
($(impl FromBits<$($F:ty),+> for $T:ty {})+) => {
$(
$(
impl FromBits<$F> for $T {
const BITS: u32 = <$T>::BITS;
type Error = Infallible;
fn try_from_bits(f: $F) -> Result<Self, Self::Error> {
Ok(f as $T)
}
fn into_bits(self) -> $F {
self as $F
}
}
)*
)+
}
}
macro_rules! impl_frombits_for_bool {
(impl FromBits<$($F:ty),+> for bool {}) => {
$(
impl FromBits<$F> for bool {
const BITS: u32 = 1;
type Error = Infallible;
fn try_from_bits(f: $F) -> Result<Self, Self::Error> {
Ok(if f == 0 { false } else { true })
}
fn into_bits(self) -> $F {
if self {
1
} else {
0
}
}
}
)+
}
}
impl_frombits_for_bool! {
impl FromBits<u8, u16, u32, u64, u128, usize> for bool {}
}
impl_frombits_for_ty! {
impl FromBits<u8, u16, u32, u64, u128> for u8 {}
impl FromBits<u16, u32, u64, u128> for u16 {}
impl FromBits<u32, u64, u128> for u32 {}
impl FromBits<u64, u128> for u64 {}
impl FromBits<u128> for u128 {}
impl FromBits<u8, u16, u32, u64, u128> for i8 {}
impl FromBits<u16, u32, u64, u128> for i16 {}
impl FromBits<u32, u64, u128> for i32 {}
impl FromBits<u64, u128> for i64 {}
impl FromBits<usize> for u8 {}
impl FromBits<usize> for i8 {}
impl FromBits<usize> for u16 {}
impl FromBits<usize> for i16 {}
impl FromBits<usize> for usize {}
impl FromBits<usize> for isize {}
impl FromBits<u128> for usize {}
}
#[cfg(target_pointer_width = "16")]
impl_frombits_for_ty! {
impl FromBits<u16, u32, u64> for usize {}
impl FromBits<u16, u32, u64> for isize {}
}
#[cfg(target_pointer_width = "32")]
impl_frombits_for_ty! {
impl FromBits<u32, u64> for usize {}
impl FromBits<u32, u64> for isize {}
impl FromBits<usize> for u32 {}
impl FromBits<usize> for i32 {}
}
#[cfg(target_pointer_width = "64")]
impl_frombits_for_ty! {
impl FromBits<u64> for usize {}
impl FromBits<u64> for isize {}
impl FromBits<usize> for u32 {}
impl FromBits<usize> for i32 {}
impl FromBits<usize> for u64 {}
impl FromBits<usize> for i64 {}
}
#[cfg(test)]
mod tests {
use super::*;
enum_from_bits! {
#[derive(Debug, PartialEq, Eq)]
enum Test<u8> {
Foo = 0b0000,
Bar = 0b0001,
Baz = 0b1000,
Qux = 0b0111,
}
}
#[test]
fn enum_max_variant() {
assert_eq!(Test::MAX_VARIANT, Test::Baz);
}
#[test]
fn enum_min_variant() {
assert_eq!(Test::MIN_VARIANT, Test::Foo);
}
#[test]
fn enum_needed_bits() {
assert_eq!(Test::NEEDED_BITS, 4);
}
#[test]
fn enum_roundtrips() {
for variant in [Test::Foo, Test::Bar, Test::Baz, Test::Qux] {
let bits = dbg!(variant as u8);
assert_eq!(dbg!(Test::try_from_bits(bits)), Ok(variant));
assert_eq!(dbg!(Test::try_from_bits(bits as u16)), Ok(variant));
assert_eq!(dbg!(Test::try_from_bits(bits as u32)), Ok(variant));
assert_eq!(dbg!(Test::try_from_bits(bits as u64)), Ok(variant));
assert_eq!(dbg!(Test::try_from_bits(bits as u128)), Ok(variant));
}
}
#[test]
fn enum_invalid() {
for value in [0b1001u8, 0b1000_0000u8, 0b1000_0001u8, 0b1111u8] {
dbg!(value);
assert!(dbg!(Test::try_from_bits(value)).is_err());
}
}
}