maskforge_core/
primitives.rs1use std::num::TryFromIntError;
4
5use bincode::{Decode, Encode};
6
7macro_rules! id_newtype {
9 ($(#[$meta:meta])* $name:ident($inner:ty)) => {
10 $(#[$meta])*
11 #[repr(transparent)]
12 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default, Encode, Decode)]
13 pub struct $name(pub $inner);
14
15 impl $name {
16 #[inline]
18 #[must_use]
19 pub const fn get(self) -> $inner {
20 self.0
21 }
22 }
23
24 impl From<$name> for $inner {
25 #[inline]
26 fn from(id: $name) -> $inner {
27 id.0
28 }
29 }
30
31 impl TryFrom<usize> for $name {
32 type Error = TryFromIntError;
33 #[inline]
35 fn try_from(value: usize) -> Result<Self, Self::Error> {
36 <$inner>::try_from(value).map(Self)
37 }
38 }
39 };
40}
41
42id_newtype!(
43 StateId(u32)
45);
46
47impl StateId {
48 #[inline]
51 #[must_use]
52 pub fn cast_slice(raw: &[u32]) -> &[StateId] {
53 unsafe { std::slice::from_raw_parts(raw.as_ptr().cast::<StateId>(), raw.len()) }
56 }
57}
58id_newtype!(
59 TokenId(u32)
61);
62id_newtype!(
63 MintermId(u32)
65);
66id_newtype!(
67 ByteClassId(u16)
69);
70id_newtype!(
71 NodeId(u32)
73);
74id_newtype!(
75 TrieNodeId(u32)
77);
78
79#[cfg(test)]
80mod tests {
81 use std::mem::{align_of, size_of};
82
83 use super::*;
84
85 #[test]
86 fn newtypes_are_zero_cost() {
87 assert_eq!(size_of::<StateId>(), size_of::<u32>());
88 assert_eq!(align_of::<StateId>(), align_of::<u32>());
89 assert_eq!(size_of::<TokenId>(), size_of::<u32>());
90 assert_eq!(size_of::<MintermId>(), size_of::<u32>());
91 assert_eq!(size_of::<NodeId>(), size_of::<u32>());
92 assert_eq!(align_of::<NodeId>(), align_of::<u32>());
93 assert_eq!(size_of::<ByteClassId>(), size_of::<u16>());
94 assert_eq!(align_of::<ByteClassId>(), align_of::<u16>());
95 }
96
97 #[test]
98 fn cast_slice_matches_element_by_element_construction_with_no_reallocation() {
99 let raw = [0u32, 7, 42, u32::MAX];
100 let cast = StateId::cast_slice(&raw);
101 let built: Vec<StateId> = raw.iter().copied().map(StateId).collect();
102 assert_eq!(cast, &built[..]);
103 assert_eq!(
104 cast.as_ptr().cast::<u32>(),
105 raw.as_ptr(),
106 "cast_slice must reinterpret raw's own allocation, not build a new one"
107 );
108 }
109
110 #[test]
111 fn cast_slice_of_empty_is_empty() {
112 let raw: [u32; 0] = [];
113 assert!(StateId::cast_slice(&raw).is_empty());
114 }
115
116 #[test]
117 fn checked_conversion_fails_out_of_range() {
118 assert_eq!(TokenId::try_from(7usize).map(TokenId::get), Ok(7));
119 if let Some(too_big) = usize::try_from(u32::MAX)
120 .ok()
121 .and_then(|m| m.checked_add(1))
122 {
123 assert!(TokenId::try_from(too_big).is_err());
124 }
125 assert!(ByteClassId::try_from(usize::from(u16::MAX) + 1).is_err());
126 if let Some(too_big) = usize::try_from(u32::MAX)
127 .ok()
128 .and_then(|m| m.checked_add(1))
129 {
130 assert!(NodeId::try_from(too_big).is_err());
131 }
132 }
133}