如何在 Rust 中声明类型化位标志? [英] How to declare typed bitflags in Rust?
问题描述
可以在 Rust 中声明标志 - 类似于在 C 中的做法.
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 flag: u32 = (FOO | BAR);
这很有效,但它不是类型安全的 - 可能会意外混淆标志使用.
This works well, however it's not type-safe - making it possible to accidentally mix up flag usage.
是否可以定义一种可以用来避免意外使用无效标志的类型?
Is it possible to define a type that can be used to avoid accidental invalid flag use?
例如:
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 raise an error?
这可以用 Rust 的类型系统来完成,而无需定义大量复杂的内部结构吗?具体来说,我的意思是需要实现二元运算符的大型宏或类型.例如,bitflags crate 有超过 300 行代码.
Can this be done with Rust's type system without the overhead of defining a lot of complex internals? Specifically, I mean large macros or types which need to implement binary operators. The bitflags crate has over 300 lines of code for example.
我知道 bitflags crate,但想知道这是否可以通过 Rust 的类型系统实现,而无需实现已经可用于底层类型的运算符.
I am aware of the bitflags crate, but would like to know if this can be achieved with Rust's type-system, without having to implement operators which are already available for the underlying type.
推荐答案
发布使用宏作为问题的一种可能解决方案的答案.
Posting answer which uses a macro as one possible solution to the question.
示例用法:
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
,它可以重复使用并应用于多种结构类型.
每个声明只有 2 行. - 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.
宏:
/// 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
的此宏的替代变体,这是必需的,因此可以在 match
语句中使用这种类型的常量.
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.
这也避免了必须定义 Copy &克隆.
This also avoids having to define Copy & Clone.
struct_bitflag_impl!(pub struct MyFlag(pub u8));
宏:
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 }
}
}
}
这篇关于如何在 Rust 中声明类型化位标志?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!