tartan_c_enum/lib.rs
1//! A simple macro to define FFI-safe enums that support unknown values.
2//!
3//! Rust's `enum` types trigger undefined behavior when they are assigned unknown
4//! discriminant values (e.g., through a pointer cast or transmutation). While this
5//! enables useful complier optimizations, it also means that `enum`s are not safe for use
6//! in FFI, since C treats enums as integral types that can take any value within range of
7//! the underlying integer type.
8//!
9//! This crate offers an alternative kind of enumeration which is more similar to C.
10//! Enumerations defined with the `c_enum` macro are simple wrappers for an integer type.
11//! Known variants are defined as constants, and can be associated with their names
12//! defined in code (e.g., for `Debug` output), but unknown values are fully supported.
13//! Since they have transparent representations, they do not trigger undefined behavior
14//! when transmuting from arbitrary values (as long as you use a built-in integer type)
15//! and are safe to use in FFI structs and functions.
16//!
17//! ```
18//! # use tartan_c_enum::c_enum;
19//! c_enum! {
20//! pub enum Example(u16) {
21//! Foo,
22//! Bar = 2,
23//! Quux,
24//! Baz = 0xffff,
25//! }
26//! }
27//!
28//! // Known value
29//! let x = Example::Bar;
30//! assert_eq!(x, Example::from(2));
31//! assert_eq!(u16::from(x), 2);
32//! assert_eq!(x.name(), Some("Bar"));
33//!
34//! // Omitted variant values assigned in sequence
35//! assert_eq!(u16::from(Example::Foo), 0);
36//! assert_eq!(u16::from(Example::Quux), 3);
37//!
38//! // Unknown value
39//! let y = Example::from(0xcafe);
40//! assert_eq!(u16::from(y), 0xcafe);
41//! assert_eq!(y.name(), None);
42//!
43//! // Use in an FFI-safe struct
44//! #[repr(C)]
45//! #[derive(Debug, PartialEq)]
46//! pub struct Quux(Example, u8, u8);
47//! unsafe {
48//! assert_eq!(
49//! core::mem::transmute::<[u8; 4], Quux>([0xff, 0xff, 0x8c, 0xf2]),
50//! Quux(Example::Baz, 0x8c, 0xf2),
51//! );
52//! assert_eq!(
53//! core::mem::transmute::<[u8; 4], Quux>([0xab, 0xab, 0x05, 0x3b]),
54//! Quux(Example::from(0xabab), 0x05, 0x3b),
55//! );
56//! }
57//! ```
58//!
59//! For lots more examples, see the [Tartan OS](https://github.com/cimbul/tartan-os)
60//! project that this crate was spun off from. This macro also works well in combination
61//! with the [Tartan Bitfield](https://github.com/cimbul/tartan-bitfield) crate.
62
63#![no_std]
64#![warn(missing_docs)]
65#![warn(clippy::pedantic)]
66
67/// Trait implemented by all [`c_enum`] types.
68///
69/// This mainly exists for documentation, to show which traits and methods are available
70/// on all C-style enums defined with this crate. (Click **"Show Declaration"** in Rustdoc
71/// to see the supertraits.)
72pub trait CEnum<T>
73where
74 Self: core::fmt::Debug
75 + Default
76 + Clone
77 + Copy
78 + PartialEq
79 + core::hash::Hash
80 + PartialOrd
81 + Ord
82 + core::convert::From<T>,
83 T: core::convert::From<Self>,
84{
85 /// The name of the enum variant in code, if one is defined for this value.
86 fn name(self) -> Option<&'static str>;
87}
88
89/// Define a struct that wraps an integer type and acts like a C-style enum.
90///
91/// The new struct will be a transparent newtype wrapper for the underlying integral type.
92/// It will have an associated constant for each variant listed in the definition, and it
93/// will implement [`CEnum`] and all its supertraits.
94///
95/// See crate documentation for examples.
96#[macro_export]
97macro_rules! c_enum {
98 [
99 $( #[$meta:meta] )*
100 $vis:vis enum $name:ident($repr_type:ty) {
101 $(
102 $( #[$variant_meta:meta] )*
103 $variant_name:ident $( = $variant_value:expr )?
104 ),*
105 $(,)?
106 }
107 ] => {
108 $(#[$meta])*
109 #[repr(transparent)]
110 #[derive(Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
111 $vis struct $name($repr_type);
112
113 impl $name {
114 $crate::c_enum! {
115 @variants
116 default = 0;
117 $(
118 $( #[$variant_meta] )*
119 $variant_name $( = $variant_value )?
120 ),*
121 }
122
123 /// The name of the enum variant in code, if one is defined for this value.
124 ///
125 /// An identical method is available through the `CEnum` trait, but this
126 /// version is declared `const` (which is currently unstable on traits).
127 pub const fn name(self) -> Option<&'static str> {
128 match self {
129 $(
130 $(#[$variant_meta])*
131 Self::$variant_name => Some(stringify!($variant_name)),
132 )*
133 _ => None,
134 }
135 }
136 }
137
138 impl $crate::CEnum<$repr_type> for $name {
139 fn name(self) -> Option<&'static str> {
140 // Delegate to const method in inherent impl
141 self.name()
142 }
143 }
144
145 impl core::fmt::Debug for $name {
146 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
147 match self.name() {
148 Some(name) => core::write!(
149 f,
150 concat!(stringify!($name), "::{}"),
151 name,
152 ),
153 None => f.debug_tuple(stringify!($name))
154 .field(&self.0)
155 .finish(),
156 }
157 }
158 }
159
160 impl core::convert::From<$repr_type> for $name {
161 fn from(repr: $repr_type) -> Self {
162 Self(repr)
163 }
164 }
165
166 impl core::convert::From<$name> for $repr_type {
167 fn from(enum_value: $name) -> Self {
168 enum_value.0
169 }
170 }
171 };
172
173 // No variants
174 [
175 @variants
176 default = $default_value:expr;
177 ] => {
178 // Done
179 };
180
181 // Explicit value
182 [
183 @variants
184 default = $default_value:expr;
185 $( #[$current_meta:meta] )*
186 $current_name:ident = $current_value:expr
187 $(
188 ,
189 $( #[$rest_meta:meta] )*
190 $rest_name:ident $( = $rest_value:expr )?
191 )*
192 ] => {
193 $(#[$current_meta])*
194 #[allow(non_upper_case_globals)]
195 pub const $current_name: Self = Self($current_value);
196
197 $crate::c_enum! {
198 @variants
199 default = $current_value + 1;
200 $(
201 $(#[$rest_meta])*
202 $rest_name $( = $rest_value )?
203 ),*
204 }
205 };
206
207 // Default value
208 [
209 @variants
210 default = $default_value:expr;
211 $( #[$current_meta:meta] )*
212 $current_name:ident
213 $(
214 ,
215 $( #[$rest_meta:meta] )*
216 $rest_name:ident $( = $rest_value:expr )?
217 )*
218 ] => {
219 $(#[$current_meta])*
220 #[allow(non_upper_case_globals)]
221 pub const $current_name: Self = Self($default_value);
222
223 $crate::c_enum! {
224 @variants
225 default = $default_value + 1;
226 $(
227 $(#[$rest_meta])*
228 $rest_name $( = $rest_value )?
229 ),*
230 }
231 };
232}