C 里位标志很常见:
#define FLAG_READ (1u << 0)
#define FLAG_WRITE (1u << 1)
#define FLAG_ERROR (1u << 2)
uint32_t flags = FLAG_READ | FLAG_WRITE;
if (flags & FLAG_ERROR) {
handle_error();
}
Rust 当然也能做位运算。但有两个习惯要先改:
C: if (flags & FLAG_ERROR)
Rust: if flags & FLAG_ERROR != 0
Rust 的 if 条件必须是 bool。整数不会自动当作真假值。
位标志仍然可以用 const
C 里用宏定义 bit mask:
#define FLAG_READ (1u << 0)
#define FLAG_WRITE (1u << 1)
#define FLAG_ERROR (1u << 2)
Rust 可以用 const:
const FLAG_READ: u32 = 1 << 0;
const FLAG_WRITE: u32 = 1 << 1;
const FLAG_ERROR: u32 = 1 << 2;
组合标志:
let flags = FLAG_READ | FLAG_WRITE;
判断某一位:
if flags & FLAG_ERROR != 0 {
handle_error();
}
设置、清除、翻转也很直接:
let mut flags = 0_u32;
flags |= FLAG_READ; // set
flags &= !FLAG_WRITE; // clear
flags ^= FLAG_ERROR; // toggle
注意 Rust 的按位取反是 !,不是 C 的 ~。
用函数或方法藏住位运算细节
裸 u32 到处传,和 C 里到处传 uint32_t flags 一样,容易把含义散掉。Rust 可以先用函数收口:
fn has_flag(flags: u32, flag: u32) -> bool {
flags & flag != 0
}
if has_flag(flags, FLAG_READ) {
read();
}
如果这是一个稳定的业务概念,可以用 newtype:
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Permissions(u32);
impl Permissions {
const READ: u32 = 1 << 0;
const WRITE: u32 = 1 << 1;
const EXECUTE: u32 = 1 << 2;
fn empty() -> Self {
Self(0)
}
fn from_bits(bits: u32) -> Self {
Self(bits)
}
fn bits(self) -> u32 {
self.0
}
fn contains(self, flag: u32) -> bool {
self.0 & flag != 0
}
fn insert(&mut self, flag: u32) {
self.0 |= flag;
}
fn remove(&mut self, flag: u32) {
self.0 &= !flag;
}
}
使用时:
let mut p = Permissions::empty();
p.insert(Permissions::READ);
if p.contains(Permissions::READ) {
println!("can read");
}
这比到处写 flags & (1 << n) 更容易维护。外部仍然可以通过 bits() 拿到底层整数,用于文件、协议或 FFI。
多位字段用 mask 和 shift
C 里不只会用单 bit 标志,也会在一个整数里塞多位字段:
#define MODE_MASK (0x3u << 4)
#define MODE_SHIFT 4
uint32_t mode = (reg & MODE_MASK) >> MODE_SHIFT;
reg = (reg & ~MODE_MASK) | ((new_mode << MODE_SHIFT) & MODE_MASK);
Rust 写法类似:
const MODE_MASK: u32 = 0x3 << 4;
const MODE_SHIFT: u32 = 4;
fn mode(reg: u32) -> u32 {
(reg & MODE_MASK) >> MODE_SHIFT
}
fn set_mode(reg: u32, value: u32) -> u32 {
(reg & !MODE_MASK) | ((value << MODE_SHIFT) & MODE_MASK)
}
如果值有范围,应该检查:
fn set_mode(reg: u32, value: u32) -> Option<u32> {
if value > 0x3 {
return None;
}
Some((reg & !MODE_MASK) | (value << MODE_SHIFT))
}
C 里很多代码靠调用方保证 value 不越界。Rust 里可以让函数签名把失败路径写出来。
C bit-field 不要直接照搬
C 有 bit-field:
struct Control {
unsigned enable : 1;
unsigned mode : 2;
unsigned error : 1;
};
它看起来很适合硬件寄存器和协议头,但布局细节并不适合直接跨语言依赖。bit-field 的分配顺序、对齐、底层存储单元等都和编译器、ABI、目标平台有关。
Rust 没有稳定的 C 风格结构体位域语法。面对外部二进制布局时,更稳的做法是用明确宽度整数保存原始 bits,再用方法读写字段:
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Control(u32);
impl Control {
const ENABLE: u32 = 1 << 0;
const MODE_MASK: u32 = 0x3 << 1;
const MODE_SHIFT: u32 = 1;
const ERROR: u32 = 1 << 3;
fn from_bits(bits: u32) -> Self {
Self(bits)
}
fn bits(self) -> u32 {
self.0
}
fn enabled(self) -> bool {
self.0 & Self::ENABLE != 0
}
fn mode(self) -> u32 {
(self.0 & Self::MODE_MASK) >> Self::MODE_SHIFT
}
fn has_error(self) -> bool {
self.0 & Self::ERROR != 0
}
}
这比把 C bit-field 布局复制到 Rust 里更明确:外部数据是 u32,字段规则由 mask 和 shift 控制。
协议和文件格式要写清字节序
如果位字段来自网络包或文件,先把字节序处理清楚:
fn parse_control(data: &[u8]) -> Option<Control> {
if data.len() < 4 {
return None;
}
let raw = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
Some(Control::from_bits(raw))
}
不要直接把字节数组强转成结构体位域。这里涉及:
- 长度检查
- 大端/小端
- 对齐
- padding
- 编译器位域布局差异
显式解析虽然多几行,但规则不会藏在编译器布局里。
FFI 里按 C 头文件保持原始表示
如果 C API 接收的是整数 flags:
int open_device(uint32_t flags);
Rust FFI 声明也先按整数来:
unsafe extern "C" {
fn open_device(flags: u32) -> i32;
}
Rust 内部可以用 newtype 封装:
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct OpenFlags(u32);
impl OpenFlags {
const READ: u32 = 1 << 0;
const WRITE: u32 = 1 << 1;
fn bits(self) -> u32 {
self.0
}
}
调用边界再取出 bits:
let flags = OpenFlags(OpenFlags::READ | OpenFlags::WRITE);
let rc = unsafe { open_device(flags.bits()) };
边界用 C 能理解的整数,内部用 Rust 能表达语义的类型。
这一层先记住什么
C 位标志和位域到 Rust 的第一层对应关系是:
#define FLAG (1u << n) -> const FLAG: u32 = 1 << n
flags & FLAG as condition -> flags & FLAG != 0
set / clear / toggle -> |= / &= ! / ^=
C bit-field -> 明确宽度整数 + mask/shift 方法
协议/文件位字段 -> 先处理字节序,再封装字段访问
FFI flags -> ABI 边界用 u32,内部可用 newtype
Rust 没有取消位运算。它要求你把真假条件、字段宽度、字节序和布局边界写得更明确。对库 API 来说,这通常是好事:调用方看到的是 contains()、mode()、bits(),而不是一堆散落的 magic mask。