域名 AXUM.RS 将于2025年10月到期。我们无意再对其进行续费,我们希望你能够接续这个域名,让更多 AXUM 开发者继续受益。现在,我们已启用新域名 AXUM.EU.ORG
  • 方案AXUM.RS 域名 = 3000
如果你有意接续这份 AXUM 情怀,请与我们取得联系。
说明:
  1. 如果有人购买 AXUM.RS 域名,或者该域名到期,本站将使用免费域名 AXUM.EU.ORG 继续提供服务。

前置知识:位运算

你或许有疑问,Web 应用也要位运算?正如你所疑惑的,和底层应用不同,作为高级应用的 Web 应用基本不需要考虑位运算,无论是本章还是后续涉及位运算的章节,都可以用其它数据结构代替。我们之所以会把位运算拿出来给大家介绍,是因为:一、让应用更高效;二、正因为大部分 Web 应用都没用上位运算,所以我们想阐明一件事:对于 Web 应用,位运算也同样能发挥重要作用。

Linux 文件权限

首先要说,以文件(在 Linux 系统中,万物皆文件。这里指的是正常思维下的文件,也就是不包括目录、设备、句柄等在内的简单文件)为例,权限有三个部分,每个部分的取值的含义是一样的,区别只在于不同的用户组。所以,我们取其中一个,比如 755 里的 7 来说明。

所以,7的意思是,可执行+可写+可读,即:1+2+4。相应的,如果权限是3,表示可执行+可写。

我们至少可以总结三点:

  • xwr (习惯写成rwx)它们的取值都是2的n次方,n大于等于0的整数
  • 任意多个值可以组合成新值
    • 这个新值可以表达包含这些组合的值,比如 7 可以表达全部权限,即 rwx
    • 这个新值并不是2的n次方,比如 3 并不是2的n次方

那么,这些操作真的需要进行算术运算吗?答案显而易见,正如本章标题,可以使用位运算来更高效、直观地进行操作。

我们来看一下 Rust 位运算。和 C 不同,Rust 的位运算只支持简单的数字,如需使用枚举,只能使用C风格的枚举,并且需要进行强制类型转换。原因在于,Rust 除了 C 风格的枚举,还支持更多风格的枚举。而 C 枚举可以简单看作是整数的别名(同样地,此论点仅供参考)。

简单地说,Rust 既支持 C 风格的简单的枚举,一个枚举对应一个整数;也支持复杂的枚举,比如嵌入 tuple、结构体等,这也是 Rust 强大之处。我们先来看一下 Rust 的位运算吧。

简单数字的位运算

我们来模拟一下 Linux 文件权限:

fn main() {
    // 定义权限
    /// 读权限
    const R: u8 = 4;
    /// 写权限
    const W: u8 = 2;
    /// 执行权限
    const X: u8 = 1;

    // 组合权限:“或”运算
    let p = R | W;

    // 判断是否有权限:“与”运算,如果结果还是指定的权限,那么说明拥有该权限
    if p & R == R {
        println!("【1】有读权限");
    }

    if p & W == W {
        println!("【1】有写权限");
    }

    if p & X == X {
        println!("【1】有执行权限");
    }

    let p = R | W | X;

    if p & R == R {
        println!("【2】有读权限");
    }

    if p & W == W {
        println!("【2】有写权限");
    }

    if p & X == X {
        println!("【2】有执行权限");
    }

    // 去除权限:组合“与”、“非”运算
    let p = p & !W;
    if p & R == R {
        println!("【3】有读权限");
    } else {
        println!("【3】无读权限")
    }

    if p & W == W {
        println!("【3】有写权限");
    } else {
        println!("【3】无写权限");
    }
    if p & X == X {
        println!("【3】有执行权限");
    } else {
        println!("【3】无执行权限");
    }

    println!("【4】当前权限:{p}");
}

对于权限来说,我们只需要了解三个位运算符,总结起来就是“非与或”。通过这三个运算符,我们能实现:

  • 权限的组合,|运算符(位运算,或)。通过该运算符,可以将多个权限进行组合,例如例子中的 let p = R | W;,此时,p 拥有了 RW 权限
  • 判断是否具有某权限,& 运算符(位运算,与)。通过权限变量和某具体的权限进行&操作,如果结果还是该具体的权限,那么我们说,该权限变量具有该具体的权限,如例子中的if p & R == R
  • 去除某权限,通过组合 & 运算符和 !运算符(位运算,非),可以去除指定的具体权限,如例子中的 let p = p & !W;

本例,可以通过 Rust Play进行查看和运行。

C 风格的枚举值的位运算

  • 必须是 C 风格的枚举,或者可以转换为整数的值
  • 必要的时候,需要手动进行类型转换
enum Per {
    X = 1,
    W = 2,
    R = 4,
}
fn main() {
    let p = Per::R as u8 | Per::W as u8 | Per::X as u8;

    if has_per(p, Per::R as u8) {
        println!("【1】有读权限");
    } else {
        println!("【1】无读权限");
    }

    if has_per(p, Per::W as u8) {
        println!("【1】有写权限");
    } else {
        println!("【1】无写权限");
    }

    if has_per(p, Per::X as u8) {
        println!("【1】有执行权限");
    } else {
        println!("【1】无执行权限");
    }

    let p = p & !(Per::W as u8);
    if has_per(p, Per::R as u8) {
        println!("【2】有读权限");
    } else {
        println!("【2】无读权限");
    }

    if has_per(p, Per::W as u8) {
        println!("【2】有写权限");
    } else {
        println!("【2】无写权限");
    }

    if has_per(p, Per::X as u8) {
        println!("【2】有执行权限");
    } else {
        println!("【2】无执行权限");
    }

    println!("当前权限:{}", p);
}

/// 检查是否具有某权限
fn has_per(p: u8, target: u8) -> bool {
    p & target == target
}

从以上例子可以看出,C 风格枚举的位运算本质就是简单数字的位运算,需要注意的是,我们需要手动进行类型转换。本示例代码可以在Rust Play上查看并运行。

BitFlags

Rust 的另一个强大之处在于,和许多现代语言一样,可以通过包装来实现自定义类型,并进行扩展。和其它语言不同的是,Rust 有宏,借助宏可以做到零成本抽象,把包装器做成和被包装对象一样的效率。

bigflags,用于实现 C 风格枚举的位运算。

模拟 Linux 的文件权限

  • 通过 bitflags ,我们可以很容易的实现 C 风格枚举的位运算
  • 额外的,为了使用 == 运算符,我们需要实现 PartialEq
  • 为了不出现所有权问题,我们需要实现 Clone, Copy

本章代码位于K02.位运算分支。

要查看完整内容,请先登录