如何在 Rust 中声明类型化位标志? [英] How to declare typed bitflags in Rust?

查看:55
本文介绍了如何在 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆