Const-compatible constructor for type with invariants, or how to avoid one with hygiene hacks

Hello! I'm trying to figure out a way of making it possible to construct an enumflags2 BitFlags in a const context.

The relevant types look like this:

pub trait BitFlag {
    type Numeric;
    // also some constants and functions we won't be using today
}

pub struct BitFlags<T: BitFlag> {
    val: T::Numeric,
}

The user writes something like this:

#[bitflags]
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum MyFlag {
    Foo = 1 << 0,
    Bar = 1 << 1,
    Baz = 1 << 2,
}

This expands into an implementation of BitFlag:

impl BitFlag for MyFlag {
    type Numeric = u8;
}

The invariant I want to uphold is that all set bits in val correspond to a variant of the enum. As such, I can't just make val a pub field.

Currently, most ways of creating a BitFlags rely on traits: MyFlag::Foo.into(), MyFlag::Bar | MyFlag::Baz. Of course, const traits are unstable, so there's no hope of making these work in a const. I'm considering the possibility of providing a macro, like this:

const FLAGS: BitFlags<MyFlag> = bitflags!(MyFlag::{Bar | Baz});

This way, I can hide arbitrary amounts of sausage-factory detail in the macro. However, I would still need a way, even most primitive, to construct a BitFlags. This is quite hard, as even a function as simple as

impl<T: BitFlag> BitFlags<T> {
    const unsafe fn new_unchecked(val: T::Numeric) -> Self {
        BitFlags { val }
    }
}

errors out with

error[E0723]: trait bounds other than `Sized` on const fn parameters are unstable
 --> src/lib.rs:9:6
  |
9 | impl<T: BitFlag> BitFlags<T> {
  |      ^
  |
  = note: see issue #57563 <https://github.com/rust-lang/rust/issues/57563> for more information
  = help: add `#![feature(const_fn)]` to the crate attributes to enable

At this point I can only see two options:

  • firstly, perhaps there is a way to use hygiene tricks to convince rustc that the code generated by the bitflags! macro comes from the same crate as the definition of BitFlags, letting me construct the value with BitFlags { val: _ } in the macro?
  • alternatively, I could make the field pub, but name it something like _internal_value and give it a #[doc(hidden)] attribute. FWIW, this is similar to what is already being done with some traits used by #[bitflags], but I'd still rather avoid this, as a field like this is more likely to interfere in an IDE than an enumflags2::_internal module...

Dear Rust wizards, what shall I do?

Here is an implementation of that using a few workarounds:

It took me a while to figure out these workarounds when writing my crates that use const fns, so don't be surprised if you don't see this anywhere else.

2 Likes

Thank you! I haven't even thought of the subsequent problem of figuring out the numeric type I should cast to, and BitFlagsRepr is a wonderfully clever solution! How should I credit you? Would you like to be added as an author of the crate?

Sure, you can add me as an author (https://github.com/rodrimati1992).

This makes me realize, that it would be quite possible to implement union and intersect in const fn. I'm wondering, though, whether there's a trick for complement (also known as not) – I would need to access an associated constant in BitFlag, and the type alias approach does not scale because const generics aren't available yet...

Well, you could define an AllFlagsSet like this:

pub struct AllFlagsSet<T, R>(ManuallyDrop<BitFlagsInner<T, R>>);

impl<T, R: Copy> Copy for AllFlagsSet<T, R> {}

impl<T, R: Copy> Clone for AllFlagsSet<T, R> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T, R> AllFlagsSet<T, R> {
    ///
    /// # Safety
    ///
    /// `all` must contain all flags for `T`.
    pub const unsafe fn new(all: BitFlagsInner<T, R>) -> Self {
        Self(ManuallyDrop::new(all))
    }

    pub const fn get(self) -> BitFlagsInner<T, R> {
        ManuallyDrop::into_inner(self.0)
    }
}

the derive macro would then generate something like this:

impl BitFlag for MyFlag {
    type Numeric = u8;
    
    pub const ALL: AllFlagsSet<MyFlag, u8> = unsafe{
        AllFlagsSet::new(bitflags!(MyFlag::{Foo | Bar | Baz}))
    };
    
    /*
    Other items
    */
}

and it would be usable by methods that need it by passing it in as a parameter.

The const not method would look like this:

impl<T> BitFlagsInner<T, u8> {
    pub const fn const_not(mut self, all: AllFlagsSet<T, u8>) -> Self {
        self.val ^= all.get().val;
        self
    }
}

It's up to you whether you consider the worsened ergonomics worth making this run at compile-time.

The original code with all the changes I talked about here:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.