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}