How to declare typed bitflags in Rust?

You can declare flags in Rust - similar to how it will 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 is not type safe, which makes it possible to accidentally mix the use of flags.

Is it possible to determine the type that can be used to prevent the use of a random invalid flag?

For instance:

 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 cause an error?

Can this be done with a system like Rust without the overhead of defining many complex internal elements? In particular, I mean large macros or types that need to implement binary operators. For example, a bitfage box has more than 300 lines of code.

I know that the box is bitflags, but I would like to know if this can be achieved using a system such as Rust, without the need to implement operators that are already available for the base type.

0
source share
4 answers

Sending an answer that uses a macro as one of the possible solutions to the question.

Usage example:

 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-level overhead compared to regular integer types.
  • The base value is available from value.0 , if necessary.
  • Uses one macro: struct_bitflag_impl , which can be reused and applied to several types of structures.
    Each ad has only 2 lines.

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 alternate version of this macro that derive supports, which is needed, so constants of this type can be used in the match statement.

It also avoids the need to copy and clone.

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

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 } } } } 
+2
source

You could (I don't know if this is idiomatic) just use Rust enums:

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

This way you have a clear meaning for your flags. After execution, you can write some helper functions around this listing to convert Rust-to-C.

 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; } 
+1
source

There is an unstable EnumSet collection in the standard library that works with the unstable CLike . It works as follows: you define an enumeration whose members accept a bit (not a mask!) As their value, and EnumSet uses the bit at the position indicated by the enum value to preserve whether the enumeration element is part of a set or not. At runtime, EnumSet is represented by one usize . EnumSet parameterized by type of enumeration, therefore, sets based on different enumerations 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); } 
+1
source

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

If these flags are named but not indexed, and they are not too numerous, you can simply insert a bunch of bool types into the structure.

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

In fact, for every bool u8 is required even with #[repr(packed)] . I do not know if this is due to the support of links to individual bool s, but they accept u8 without #[repr(packed)] too, so they are not sure. I think an RFC or question may be asked, but give 1240 . If you spend u8 on one flag, how it works, then it is probably the syntax compatible with bit fields when they land.

If you need to index flags, you will need some kind of dirty or fancy solution in C.

If you need bit fields with values ​​larger than bool, there are many ways to crack them together along the lines of the previous two comments. And some bit boxes. You'll find some more discussion in the Rust RFC 314 and 1449 discussion threads on adding support for Beat Codes to Rust. In this case, I would do it as you like now, but maybe plan to switch it to bitfields when they eventually land.

0
source

Source: https://habr.com/ru/post/1259677/


All Articles