如何在Rust中声明类型化的位标志?

时间:2016-10-30 18:16:35

标签: rust

可以在Rust中声明标志 - 类似于在C中完成的方式。

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

let flag: u32 = (FOO | BAR);

这很好用,但它不是类型安全的 - 可以意外混淆标记的使用。

是否可以定义一种可用于避免意外无效标志使用的类型?

例如:

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.

...在其他标志类型中混合会引发错误吗?

这可以用Rust的类型系统完成,而不需要定义很多复杂内部的开销吗?具体来说,我的意思是需要实现二元运算符的大型宏或类型。例如,bitflags crate有超过300行代码。

我知道bitflags crate,但是想知道这是否可以用Rust的类型系统实现,而不必实现已经可用于底层类型的运算符。

4 个答案:

答案 0 :(得分:2)

发布使用宏作为问题的一种可能解决方案的答案。

使用示例:

403
400
2000 ( this is a leap year , yet it doesn't print true)
2004
  • 类型安全。
  • 与普通整数类型相比零开销。
  • 如果需要,可以从struct_bitflag_impl!(pub struct MyFlag(pub u8)); pub struct MyFlag(u8); struct_bitflag_impl!(MyFlag); pub struct MyOtherFlag(u32); struct_bitflag_impl!(MyOtherFlag); 访问基础价值。
  • 使用单个宏:value.0可以重复使用并应用于多种结构类型。
    每个声明只有2行。

宏:

struct_bitflag_impl

对于支持/// 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) } } } } 的此宏的替代变体,可以编写derive语句中可以使用此类型的常量。

这也避免了必须定义Copy&amp;克隆

match

宏:

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

答案 1 :(得分:1)

你可以(我不知道它是否以任何方式惯用)只使用Rust的枚举:

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

这样你就可以清楚地了解你的旗帜。完成后,您可以围绕此枚举编写一些辅助函数,以进行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;
}

答案 2 :(得分:1)

标准库中有一个不稳定的EnumSet集合,它与不稳定的CLike特征一起使用。它的工作方式如下:你定义一个枚举,其成员取一个位数(一个掩码!)作为它们的值,EnumSet使用枚举值指定的位置的位存储枚举成员是否是集合的一部分。在运行时,EnumSet由单个usize表示。 EnumSet在枚举类型上进行参数化,因此基于不同枚举的集合将不具有相同的类型。

#![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);
}

答案 3 :(得分:0)

您应该知道type在Rust中创建了一个类型别名,而不是新类型,因此MyFlagMyOtherFlag具有相同的类型。

如果这些标志已命名但未编入索引,并且它们不是太多,那么您可以将一堆bool类型粘贴到结构中。

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

事实上,每个bool要求u8甚至#[repr(packed)]。我不知道如果它源于对个别bool的支持引用,但它们也会u8而不是#[repr(packed)],所以不确定。我认为可以提交有关该问题的RFC或问题,但请1240。如果像这样浪费u8每个标志有效,那么每当它们着陆时,它的语法可能与位域兼容。

如果您需要索引标记,那么您在C中也需要一些混乱或奇特的解决方案。

如果你想要值大于bool的位域,有很多种方法可以像前两条注释那样将它们组合在一起。和一些位域板条箱。您将在Rust RFC讨论线程3141449中找到更多关于向Rust添加位域支持的讨论。在这种情况下,我现在就喜欢这样做,但也许计划在它们最终降落时将其切换到位域。