]> git.lizzy.rs Git - enumset.git/blobdiff - enumset/src/lib.rs
Add `repr` option to derive.
[enumset.git] / enumset / src / lib.rs
index e36c22b0018c5279ad66075a1c2bd46337f5eacc..384f458166578149549eb4a20e1a7eedf9af0276 100644 (file)
@@ -166,6 +166,11 @@ use crate::repr::EnumSetTypeRepr;
 /// * `#[enumset(crate_name = "enumset2")]` may be used to change the name of the `enumset` crate
 ///   used in the generated code. When the `std` feature is enabled, enumset parses `Cargo.toml`
 ///   to determine the name of the crate, and this flag is unnecessary.
+/// * `#[enumset(repr = "u8")]` may be used to specify the in-memory representation of `EnumSet`s
+///   of this enum type. The effects of this are described in [the `EnumSet` documentation under
+///   “FFI, Safety and `repr`”][EnumSet#ffi-safety-and-repr]. Allowed types are `u8`, `u16`, `u32`,
+///   `u64` and `u128`. If this is not used, then the derive macro will choose a type to best fit
+///   the enum, but there are no guarantees about which type will be chosen.
 ///
 /// When the `serde` feature is used, the following features may also be specified. These options
 /// may be used (with no effect) when building without the feature enabled:
@@ -221,6 +226,20 @@ pub use enumset_derive::EnumSetType;
 /// [`#[derive(EnumSetType)]`](./derive.EnumSetType.html).
 pub unsafe trait EnumSetType: Copy + Eq + EnumSetTypePrivate { }
 
+/// An [`EnumSetType`] for which [`EnumSet`]s have a guaranteed in-memory representation.
+///
+/// An implementation of this trait is generated by using
+/// [`#[derive(EnumSetType)]`](./derive.EnumSetType.html) with the annotation
+/// `#[enumset(repr = "…")]`, where `…` is `u8`, `u16`, `u32`, `u64` or `u128`.
+///
+/// For any type `T` that implements this trait, the in-memory representation of `EnumSet<T>`
+/// is guaranteed to be `Repr`. This guarantee is useful for FFI. See [the `EnumSet` documentation
+/// under “FFI, Safety and `repr`”][EnumSet#ffi-safety-and-repr] for an example.
+pub unsafe trait EnumSetTypeWithRepr: EnumSetType + EnumSetTypePrivate<Repr = <Self as EnumSetTypeWithRepr>::Repr> {
+    /// The guaranteed representation.
+    type Repr: EnumSetTypeRepr;
+}
+
 /// An efficient set type for enums.
 ///
 /// It is implemented using a bitset stored using the smallest integer that can fit all bits
@@ -260,6 +279,50 @@ pub unsafe trait EnumSetType: Copy + Eq + EnumSetTypePrivate { }
 /// In addition, the `#[enumset(serialize_as_list)]` attribute causes the `EnumSet` to be
 /// instead serialized as a list of enum variants. This requires your enum type implement
 /// [`Serialize`] and [`Deserialize`]. Note that this is a breaking change.
+///
+/// # FFI, Safety and `repr`
+///
+/// If an enum type `T` is annotated with [`#[enumset(repr = "R")]`][derive@EnumSetType#options], then
+/// several things happen:
+///
+/// * `T` will implement <code>[EnumSetTypeWithRepr]&lt;Repr = R&gt;</code> in addition to
+///   [`EnumSetType`].
+/// * The `EnumSet` methods with `repr` in their name, such as [`as_repr`][EnumSet::as_repr] and
+///   [`from_repr`][EnumSet::from_repr], will be available for `EnumSet<T>`.
+/// * The in-memory representation of `EnumSet<T>` is guaranteed to be `R`.
+///
+/// That last guarantee makes it sound to send `EnumSet<T>` across an FFI boundary. For example:
+///
+/// ```
+/// # use enumset::*;
+/// #
+/// # mod ffi_impl {
+/// #     // This example “foreign” function is actually written in Rust, but for the sake
+/// #     // of example, we'll pretend it's written in C.
+/// #     #[no_mangle]
+/// #     extern "C" fn some_foreign_function(set: u32) -> u32 {
+/// #         set & 0b100
+/// #     }
+/// # }
+/// #
+/// extern "C" {
+///     // This function is written in C like:
+///     // uint32_t some_foreign_function(uint32_t set) { … }
+///     fn some_foreign_function(set: EnumSet<MyEnum>) -> EnumSet<MyEnum>;
+/// }
+///
+/// #[derive(Debug, EnumSetType)]
+/// #[enumset(repr = "u32")]
+/// enum MyEnum { A, B, C }
+///
+/// let set: EnumSet<MyEnum> = enum_set!(MyEnum::A | MyEnum::C);
+///
+/// let new_set: EnumSet<MyEnum> = unsafe { some_foreign_function(set) };
+/// assert_eq!(new_set, enum_set!(MyEnum::C));
+/// ```
+///
+/// When an `EnumSet<T>` is received via FFI, all bits that don't correspond to an enum variant
+/// of `T` must be set to 0. Behavior is **undefined** if any of these bits are set to 1.
 #[derive(Copy, Clone, PartialEq, Eq)]
 #[repr(transparent)]
 pub struct EnumSet<T: EnumSetType> {
@@ -428,6 +491,90 @@ impl <T: EnumSetType> EnumSet<T> {
     pub fn iter(&self) -> EnumSetIter<T> {
         EnumSetIter::new(*self)
     }
+
+    /// Returns a `T::Repr` representing the elements of this set.
+    ///
+    /// Unlike the other `as_*` methods, this method is zero-cost and guaranteed not to fail,
+    /// panic or truncate any bits.
+    ///
+    /// In order to use this method, the definition of `T` must have the `#[enumset(repr = "…")]`
+    /// annotation.
+    #[inline(always)]
+    pub fn as_repr(&self) -> <T as EnumSetTypeWithRepr>::Repr
+    where
+        T: EnumSetTypeWithRepr,
+    {
+        self.__priv_repr
+    }
+
+    /// Constructs a bitset from a `T::Repr` without checking for invalid bits.
+    ///
+    /// Unlike the other `from_*` methods, this method is zero-cost and guaranteed not to fail,
+    /// panic or truncate any bits, provided the conditions under “Safety” are upheld.
+    ///
+    /// In order to use this method, the definition of `T` must have the `#[enumset(repr = "…")]`
+    /// annotation.
+    ///
+    /// # Safety
+    ///
+    /// All bits in the provided parameter `bits` that don't correspond to an enum variant of
+    /// `T` must be set to 0. Behavior is **undefined** if any of these bits are set to 1.
+    #[inline(always)]
+    pub unsafe fn from_repr_unchecked(bits: <T as EnumSetTypeWithRepr>::Repr) -> Self
+    where
+        T: EnumSetTypeWithRepr,
+    {
+        Self { __priv_repr: bits }
+    }
+
+    /// Constructs a bitset from a `T::Repr`.
+    ///
+    /// If a bit that doesn't correspond to an enum variant is set, this
+    /// method will panic.
+    ///
+    /// In order to use this method, the definition of `T` must have the `#[enumset(repr = "…")]`
+    /// annotation.
+    #[inline(always)]
+    pub fn from_repr(bits: <T as EnumSetTypeWithRepr>::Repr) -> Self
+    where
+        T: EnumSetTypeWithRepr,
+    {
+        Self::try_from_repr(bits).expect("Bitset contains invalid variants.")
+    }
+
+    /// Attempts to constructs a bitset from a `T::Repr`.
+    ///
+    /// If a bit that doesn't correspond to an enum variant is set, this
+    /// method will return `None`.
+    ///
+    /// In order to use this method, the definition of `T` must have the `#[enumset(repr = "…")]`
+    /// annotation.
+    #[inline(always)]
+    pub fn try_from_repr(bits: <T as EnumSetTypeWithRepr>::Repr) -> Option<Self>
+    where
+        T: EnumSetTypeWithRepr,
+    {
+        let mask = Self::all().__priv_repr;
+        if bits.and_not(mask).is_empty() {
+            Some(EnumSet { __priv_repr: bits })
+        } else {
+            None
+        }
+    }
+
+    /// Constructs a bitset from a `T::Repr`, ignoring invalid variants.
+    ///
+    /// In order to use this method, the definition of `T` must have the `#[enumset(repr = "…")]`
+    /// annotation.
+    #[inline(always)]
+    pub fn from_repr_truncated(bits: <T as EnumSetTypeWithRepr>::Repr) -> Self
+    where
+        T: EnumSetTypeWithRepr,
+    {
+        let mask = Self::all().as_repr();
+        let bits = bits & mask;
+        EnumSet { __priv_repr: bits }
+    }
 }
 
 /// Helper macro for generating conversion functions.