可以在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的类型系统实现,而不必实现已经可用于底层类型的运算符。
答案 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
可以重复使用并应用于多种结构类型。宏:
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中创建了一个类型别名,而不是新类型,因此MyFlag
和MyOtherFlag
具有相同的类型。
如果这些标志已命名但未编入索引,并且它们不是太多,那么您可以将一堆bool
类型粘贴到结构中。
#[repr(packed)]
struct MyFlags {
a: bool,
b: bool
}
事实上,每个bool
要求u8
甚至#[repr(packed)]
。我不知道如果它源于对个别bool
的支持引用,但它们也会u8
而不是#[repr(packed)]
,所以不确定。我认为可以提交有关该问题的RFC或问题,但请1240。如果像这样浪费u8
每个标志有效,那么每当它们着陆时,它的语法可能与位域兼容。
如果您需要索引标记,那么您在C中也需要一些混乱或奇特的解决方案。
如果你想要值大于bool的位域,有很多种方法可以像前两条注释那样将它们组合在一起。和一些位域板条箱。您将在Rust RFC讨论线程314和1449中找到更多关于向Rust添加位域支持的讨论。在这种情况下,我现在就喜欢这样做,但也许计划在它们最终降落时将其切换到位域。