rust 如何在bitflags枚举上实现按位操作?

6uxekuva  于 2023-08-05  发布在  其他
关注(0)|答案(4)|浏览(179)

我有一个enum,看起来像这样:

#[repr(u8)]
pub enum PublicFlags {
    PublicFlagVersion = 0x01,
    PublicFlagReset = 0x02,
    NoncePresent = 0x04,
    IdPresent = 0x08,
    PktNumLen4 = 0x30,
    PktNumLen2 = 0x20,
    PktNumLen1 = 0x10,
    Multipath = 0x40,
}

字符串
我想对几个枚举值进行按位运算。然而,Rust编译器会抱怨:

an implementation of `std::ops::BitAnd` might be missing for `PublicFlags`.

kyvafyod

kyvafyod1#

Rust中的enum不打算用作位标志。PublicFlags可以 * 仅 * 接受枚举中给定的值(而不是组合)。例如,下面的match语句是详尽的:

let flags: PublicFlags;
...
match flags {
    PublicFlagVersion => {...}
    PublicFlagReset => {...}
    NoncePresent => {...}
    IdPresent => {...}
    PktNumLen4 => {...}
    PktNumLen2 => {...}
    PktNumLen1 => {...}
    Multipath => {...}
}

字符串
没有办法让PublicFlags变量具有这些标志的组合。
解决方案是将值实际存储为u8,然后使用常量存储每个标志的值。这可能很麻烦,但值得庆幸的是bitflags crate为您将所有样板文件打包在一个宏中。下面是一个如何使用位标志创建结构体的示例:

bitflags::bitflags! {
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
    struct PublicFlags: u8 {
        const PUBLIC_FLAG_VERSION = 0x01;
        const PUBLIC_FLAG_RESET = 0x02;
        const NONCE_PRESENT = 0x04;
        const ID_PRESENT = 0x08;
        const PKT_NUM_LEN_4 = 0x30;
        const PKT_NUM_LEN_2 = 0x20;
        const PKT_NUM_LEN_1 = 0x10;
        const MULTIPATH = 0x40;
    }
}

fn main() {
    let flag = PublicFlags::PUBLIC_FLAG_VERSION | PublicFlags::ID_PRESENT;
    assert!((flag & PublicFlags::MULTIPATH).is_empty()); 
    assert!(flag.contains(PublicFlags::ID_PRESENT));
}

knpiaxh1

knpiaxh12#

这可以作为没有新的依赖性的替代答案。

pub mod PublicFlags {
    pub const PublicFlagVersion: u8 = 0x01;
    pub const PublicFlagReset: u8 = 0x02;
    pub const NoncePresent: u8 = 0x04;
    pub const IdPresent: u8 = 0x08;
    pub const PktNumLen4: u8 = 0x30;
    pub const PktNumLen2: u8 = 0x20;
    pub const PktNumLen1: u8 = 0x10;
    pub const Multipath: u8 = 0x40;
}

字符串
您可以像使用PublicFlags::PublicFlagVersion的枚举一样引用这些值,或者如果不指定命名空间而引用这些值更简洁,则添加use PublicFlags::*;

t9aqgxwy

t9aqgxwy3#

我不知道这在当时是否存在,但是一点as的魔力在没有任何bitor/bitand trait实现的情况下完成了这项工作(使代码有点长,但非常简单)

enum BITS {
    ONE = 0b0001,
    TWO = 0b0010,
    FOUR = 0b0100,
    EIGHT = 0b1000,
}

fn main() {
    let flags: u8;

    flags = BITS::ONE as u8 | BITS::FOUR as u8 | BITS::EIGHT as u8;

    println!("{flags:b}")

    use_flags(flags);
}

fn use_flags(flags:u8){
    let flag_one = flags & BITS::ONE as u8 == 1;
    if flag_one {
        println!("flag ONE is {flag_one}")
    }
}

字符串
PS:如果你不使用代码中的所有枚举,你会得到这个形状的警告:“warning:variant TWO is never constructed”,但是,同样,这只是一个可以优雅处理的警告。
注意:当使用枚举进行按位操作时,项值不应重叠。在主要问题的代码中,至少PktNumLen1将与PublicFlagResetIdPresent重叠,0x 10是0 b1010,0x 8是0 b1000,0x 2是0 b 0010(第2位和第4位重叠)

72qzrwbm

72qzrwbm4#

我已经有了一个关于如何设置和使用枚举的答案,如果我们想要按位操作。
另一方面,我们可以将枚举值组合在一个向量(动态数组)中,并按原样使用它们:

#[derive(Debug)]
enum BITS {
    ONE = 0b0001,
    TWO = 0b0010,
    FOUR = 0b0100,
    EIGHT = 0b1000,
}

fn main() {
    let flags: Vec<BITS>;

    flags = vec![BITS::ONE, BITS::FOUR, BITS::EIGHT];

    println!("{flags:?}");

    use_flags(flags);
}

fn use_flags(flags:Vec<BITS>){
    for flag in flags{
        match flag {
            BITS::ONE => println!("flag ONE is set"),
            BITS::TWO => println!("flag TWO is set"),
            BITS::FOUR => println!("flag FOUR is set"),
            BITS::EIGHT => println!("flag EIGHT is set")
        }
    }
}

字符串
这里的事情是在我们的函数中设置默认值(使用mut),并在稍后的代码中使用match来自定义它们。
它易于编码,但这种方法的缺点是向量内每个标志的存储器使用。
在位操作方法中,ENUM值的位不应重叠。在这一方法中,预先思考过程至关重要。而且,解包比特字段也不容易,但是将它们存储在比特字段中使得存储器高效。所以也检查一下我的其他答案。(https://stackoverflow.com/a/76603396/9512475

相关问题