How to declare typed bitflags in Rust?

后端 未结 4 1991
-上瘾入骨i
-上瘾入骨i 2021-01-26 05:48

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 fl         


        
相关标签:
4条回答
  • 2021-01-26 06:11

    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.

    0 讨论(0)
  • 2021-01-26 06:16

    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 }
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-26 06:22

    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;
    }
    
    0 讨论(0)
  • 2021-01-26 06:33

    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);
    }
    
    0 讨论(0)
提交回复
热议问题