ideasman42
ideasman42

Reputation: 48178

How to declare typed bitflags in Rust?

It's possible to declare flags in Rust - similar to how it would be done in C.

pub const FOO: u32 = (1 << 0);
pub const BAR: u32 = (1 << 1);

let flag: u32 = (FOO | BAR);

This works well, however it's not type-safe - making it possible to accidentally mix up flag usage.

Is it possible to define a type that can be used to avoid accidental invalid flag use?

For example:

pub type MyOtherFlag = u32;
pub type MyFlag = u32;
pub const FOO: MyFlag = (1 << 0);
pub const BAR: MyFlag = (1 << 1);

let flag: MyOtherFlag = (FOO | BAR);
//        ^^^^^^^^^^^ I'd like this to raise a type error to avoid
//                    confusion between MyOtherFlag and MyFlag.
//                    Currently it doesn't since
//                    type aliases aren't seen as distinct types.

... where mixing in other flag-types will raise an error?

Can this be done with Rust's type system without the overhead of defining a lot of complex internals? Specifically, I mean large macros or types which need to implement binary operators. The bitflags crate has over 300 lines of code for example.

I am aware of the bitflags crate, but would like to know if this can be achieved with Rust's type-system, without having to implement operators which are already available for the underlying type.

Upvotes: 1

Views: 6136

Answers (4)

ideasman42
ideasman42

Reputation: 48178

Posting answer which uses a macro as one possible solution to the question.

Example usage:

struct_bitflag_impl!(pub struct MyFlag(pub u8));
pub struct MyFlag(u8);
struct_bitflag_impl!(MyFlag);

pub struct MyOtherFlag(u32);
struct_bitflag_impl!(MyOtherFlag);
  • Type-safe.
  • Zero overhead compared with plain integer types.
  • Underlying value is accessible from value.0 if needed.
  • Uses a single macro: struct_bitflag_impl which can be re-used and applied to multiple struct types.
    Each declaration is only 2 lines.

The macro:

/// Implements bitflag operators for integer struct, eg:
/// ```
/// pub struct MyFlag(u8);
/// struct_bitflag_impl!(MyFlag);
/// ```
macro_rules! struct_bitflag_impl {
    ($p:ident) => {
        // Possible additions:
        // * left/right shift.
        // * Deref to forward methods to the underlying type.

        impl ::std::ops::BitAnd for $p {
            type Output = $p;
            fn bitand(self, _rhs: $p) -> $p { $p(self.0 & _rhs.0) }
        }
        impl ::std::ops::BitOr for $p {
            type Output = $p;
            fn bitor(self, _rhs: $p) -> $p { $p(self.0 | _rhs.0) }
        }
        impl ::std::ops::BitXor for $p {
            type Output = $p;
            fn bitxor(self, _rhs: $p) -> $p { $p(self.0 ^ _rhs.0) }
        }

        impl ::std::ops::Not for $p {
            type Output = $p;
            fn not(self) -> $p { $p(!self.0) }
        }

        impl ::std::ops::BitAndAssign for $p {
            fn bitand_assign(&mut self, _rhs: $p) { self.0 &= _rhs.0; }
        }
        impl ::std::ops::BitOrAssign for $p {
            fn bitor_assign(&mut self, _rhs: $p) { self.0 |= _rhs.0; }
        }
        impl ::std::ops::BitXorAssign for $p {
            fn bitxor_assign(&mut self, _rhs: $p) { self.0 ^= _rhs.0; }
        }

        // Other operations needed to be generally usable.
        impl PartialEq for $p {
            fn eq(&self, other: &$p) -> bool { self.0 == other.0 }
        }

        impl Copy for $p { }
        impl Clone for $p {
            fn clone(&self) -> $p { $p(self.0) }
        }
    }
}

For an alternative variation on this macro that supports derive which is needed so constants of this type can be used in a match statement can be written.

This also avoids having to define Copy & Clone.

struct_bitflag_impl!(pub struct MyFlag(pub u8));

The macro:

macro_rules! struct_bitflag_impl {
    // pub/pub
    (pub struct $name:ident ( pub $t:tt ) ) => {
        #[derive(PartialEq, Eq, Copy, Clone, Debug)]
        pub struct $name(pub $t);
        _struct_bitflag_gen_impls!($name, $t);
    };
    // private/pub
    (struct $name:ident ( pub $t:tt ) ) => {
        #[derive(PartialEq, Eq, Copy, Clone, Debug)]
        struct $name(pub $t);
        _struct_bitflag_gen_impls!($name, $t);
    };
    // pub/private
    (pub struct $name:ident ( $t:tt ) ) => {
        #[derive(PartialEq, Eq, Copy, Clone, Debug)]
        struct $name($t);
        _struct_bitflag_gen_impls!($name, $t);
    };
    // private/private
    (struct $name:ident ( $t:tt ) ) => {
        #[derive(PartialEq, Eq, Copy, Clone, Debug)]
        struct $name($t);
        _struct_bitflag_gen_impls!($name, $t);
    }
}

macro_rules! _struct_bitflag_gen_impls {
    ($t:ident, $t_base:ident) => {
        impl ::std::ops::BitAnd for $t {
            type Output = $t;
            #[inline]
            fn bitand(self, _rhs: $t) -> $t { $t(self.0 & _rhs.0) }
        }
        impl ::std::ops::BitOr for $t {
            type Output = $t;
            #[inline]
            fn bitor(self, _rhs: $t) -> $t { $t(self.0 | _rhs.0) }
        }
        impl ::std::ops::BitXor for $t {
            type Output = $t;
            #[inline]
            fn bitxor(self, _rhs: $t) -> $t { $t(self.0 ^ _rhs.0) }
        }

        impl ::std::ops::Not for $t {
            type Output = $t;
            #[inline]
            fn not(self) -> $t { $t(!self.0) }
        }

        impl ::std::ops::BitAndAssign for $t {
            #[inline]
            fn bitand_assign(&mut self, _rhs: $t) { self.0 &= _rhs.0; }
        }
        impl ::std::ops::BitOrAssign for $t {
            #[inline]
            fn bitor_assign(&mut self, _rhs: $t) { self.0 |= _rhs.0; }
        }
        impl ::std::ops::BitXorAssign for $t {
            #[inline]
            fn bitxor_assign(&mut self, _rhs: $t) { self.0 ^= _rhs.0; }
        }

        /// Support for comparing with the base type, allows comparison with 0.
        ///
        /// This is used in typical expressions, eg: `if (a & FLAG) != 0 { ... }`
        /// Having to use MyFlag(0) all over is too inconvenient.
        impl PartialEq<$t_base> for $t {
            #[inline]
            fn eq(&self, other: &$t_base) -> bool { self.0 == *other }
        }
    }
}

Upvotes: 4

Francis Gagn&#233;
Francis Gagn&#233;

Reputation: 65832

There is an unstable EnumSet collection in the standard library that works with the also unstable CLike trait. It works like this: you define an enum, whose members take a bit number (not a mask!) as their value, and EnumSet uses the bit at the position designated by the enum value to store whether the enum member is part of the set or not. At runtime, an EnumSet is represented by a single usize. EnumSet is parameterized on the enum type, so sets based on different enums will not have the same type.

#![feature(collections)]
#![feature(enumset)]

extern crate collections;

use collections::enum_set::{CLike, EnumSet};
use std::mem;

#[derive(Clone, Copy, Debug)]
#[repr(usize)]
enum MyFlag {
    Foo,
    Bar,
}

impl CLike for MyFlag {
    fn to_usize(&self) -> usize {
        *self as usize
    }

    fn from_usize(v: usize) -> MyFlag {
        unsafe { mem::transmute(v) }
    }
}

fn main() {
    let mut flags = EnumSet::new();
    flags.insert(MyFlag::Foo);
    flags.insert(MyFlag::Bar);
    println!("{:?}", flags);
}

Upvotes: 2

Jeff Burdges
Jeff Burdges

Reputation: 4261

You should know that type creates a type alias in Rust, not a new type, so you MyFlag and MyOtherFlag have the same type.

If these flags are named but not indexed, and they are not too numerous, then you could just stick a bunch of bool types into a struct.

#[repr(packed)]
struct MyFlags {
    a: bool,
    b: bool
}

In fact, each bool requires a u8 even with #[repr(packed)]. I donno if that originates with supporting references to individual bools, but they take a u8 without #[repr(packed)] too, so not sure. I'd think an RFC or issue could be filed about that though give 1240. If wasting a u8 per flag like this works, then it's likely syntax compatible with bitfields whenever they land.

If you need indexing into the flags, then you'd need some messy or fancy solution in C too.

If you want bitfields with values larger than bool, there are a variety of ways to hack this together along the lines of the previous two comments. And some bitfield crates. You'll find several more discussed in the Rust RFC discussion threads 314 and 1449 on adding bitfield support to Rust. In this case, I'd do it however you like for now, but maybe plan on switching it to bitfields whenever they eventually land.

Upvotes: 0

user6555228
user6555228

Reputation:

You could (I don't know if it is in any way idiomatic) just use Rust's enums :

pub enum MyFlags {
    Meaning1,
    Meaning2,
    Meaning3,
    ...,
    MeaningX
}

This way you have a clear meaning for your flags. Once done, you can write some helper functions around this enum for Rust-to-C conversion.

fn to_u32(flag: &MyFlags) -> u32 {
    match flag {
        &MyFlags::Meaning1 => return (1 << 0),
        &MyFlags::Meaning2 => return (1 << 1),
        &MyFlags::Meaning3 => return (1 << 2),
        &MyFlags::MeaningX => return (1 << 3),
    }
}

fn to_bitflags_flags(flags: &Vec<MyFlags>) -> u32 {
    let mut bitflags = 0u32;

    for flag in flags {
        bitflags |= to_u32(flag);
    }
    return bitflags;
}

Upvotes: 1

Related Questions