https://fasterthanli.me/articles/a-rust-match-made-in-hell

    我经常会写一些展示代码用于展示Rust可以如何棒的为你工作,Rust可以如何让你构建强大的抽象,避免你犯一系列错误。

    其中某些代码令我感到非常难受。

    我写过的文章 Some mistakes Rust doesn’t catch (Rust不会捕获的错误)总是会受到一些由于我说了Rust的好话而生气的人的反对。

    所以,自然地,质疑就来了:“你对其他语言不公平!Rust不可能那么棒,你肯定隐瞒了什么,我在2014年试过Rust,感觉很糟!”。

    好吧,你们这些等着幸灾乐祸的家伙,我们来看下我遇到的,花费了我,一个Rust拥护者,足足一周的一个footgun(译者注:footgun指在一个产品上添加一个新东西,容易让枪打着自己脚。表明设计不好,促使用户不敢加东西。对应到这里,就是用来实现这个产品的语言不够好)。

    基本情况

    我们先来复习下Rust语法!

    Rust如你理解的命令式语言一样,有if else两个关键字。

    1. fn is_good() -> bool {
    2. true
    3. }
    4. fn main() {
    5. if is_good() {
    6. println!("It is good");
    7. } else {
    8. println!("It isn't good, yet");
    9. }
    10. }
    1. cargo run --quiet
    2. It is good

    他们不是声明,它们是表达式!可以组成三元运算表达式:

    1. fn is_good() -> bool {
    2. true
    3. }
    4. fn main() {
    5. let msg = if is_good() {
    6. "It is good"
    7. } else {
    8. "It isn't good, yet"
    9. };
    10. println!("{msg}");
    11. }

    而由于它们是表达式,它们可以用在任何地方:

    1. fn is_good() -> bool {
    2. true
    3. }
    4. fn main() {
    5. println!(
    6. "{}",
    7. if is_good() {
    8. "It is good"
    9. } else {
    10. "It isn't good, yet"
    11. }
    12. )
    13. }

    但是,Rust也有match,它像一个更强大的switch。

    1. fn is_good() -> bool {
    2. true
    3. }
    4. fn main() {
    5. let msg = match is_good() {
    6. true => "It is good",
    7. false => "It isn't good, yet",
    8. };
    9. println!("{msg}")
    10. }

    酷熊:这里也没看出更强大啊

    确实,但是你可以用多种模式匹配被检查项目,有多个”arms” ( 在上面的例子就是 true => {}, false => {} ).

    1. use rand::Rng;
    2. fn main() {
    3. let msg = match rand::thread_rng().gen_range(0..=10) {
    4. // match only 10
    5. 10 => "Overwhelming victory",
    6. // match anything 5 or above
    7. 5.. => "Victory",
    8. // match anything else (fallback case)
    9. _ => "Defeat",
    10. };
    11. println!("{msg}")
    12. }
    1. $ cargo run --quiet
    2. Victory
    3. $ cargo run --quiet
    4. Overwhelming victory
    5. $ cargo run --quiet
    6. Defeat

    我们等会看到更多的match。
    Rust也有enum类型。它们不只是一组integer或者String,它们是一堆真正的类型的组合。
    假设我们有一个函数叫process,它们可以安全,或者不安全的工作。

    1. fn process(secure: bool) {
    2. if secure {
    3. println!("No hackers plz");
    4. } else {
    5. println!("Come on in");
    6. }
    7. }
    8. fn main() {
    9. process(false)
    10. }

    从调用方的角度很难搞懂参数到底在干什么。如果你在类似VSCode这样的编辑器中用了 Rust Analyzer 支持嵌入提示,那就比较明显了,这里加了参数名。
    但是我们还是更想要个枚举。

    1. pub enum Protection {
    2. Secure,
    3. Insecure,
    4. }
    5. fn process(prot: Protection) {
    6. match prot {
    7. Protection::Secure => {
    8. println!("No hackers plz");
    9. }
    10. Protection::Insecure => {
    11. println!("Come on in");
    12. }
    13. }
    14. }
    15. fn main() {
    16. process(Protection::Insecure)
    17. }

    因为这样的话,调用方即使脱离ide(比如 github上review PR,gitlab的MR,或者是邮件的补丁)也具有可读性。

    并且,由于Protection::Secure 和 Protection::Insecure 是唯一的符号(相对于true和false),借助于我的IDE,我可以通过查找谁引用了Protection::Insecure 找出不安全处理的代码。
    image.png
    并且,比如,我也可以把它标记为废弃,那么在不改变类型签名的前提下,任何调用它的地方都会产生警告。

    1. pub enum Protection {
    2. Secure,
    3. #[deprecated = "using secure mode everywhere is now strongly recommended"]
    4. Insecure,
    5. }
    6. fn process(prot: Protection) {
    7. match prot {
    8. Protection::Secure => {
    9. println!("No hackers plz");
    10. }
    11. // We still need to handle this case
    12. #[allow(deprecated)]
    13. Protection::Insecure => {
    14. println!("Come on in");
    15. }
    16. }
    17. }
    18. fn main() {
    19. process(Protection::Insecure)
    20. }
    1. cargo check
    2. Checking lox v0.1.0 (/home/amos/bearcove/lox)
    3. warning: use of deprecated unit variant `Protection::Insecure`: using secure mode everywhere is now strongly recommended
    4. --> src/main.rs:21:25
    5. |
    6. 21 | process(Protection::Insecure)
    7. | ^^^^^^^^
    8. |
    9. = note: `#[warn(deprecated)]` on by default
    10. warning: `lox` (bin "lox") generated 1 warning
    11. Finished dev [unoptimized + debuginfo] target(s) in 0.28s

    由于我的IDE有 Error Lens 这个插件,它甚至直接在行内展示了出来。
    image.png
    如果你想知道为什么我花费大量时间展示这些工具,那是因为这是“Rust学习经历”中重要的一部分:强大的诊断功能?这是一个feature;能看到所有对一个符号的引用?这是一个feature,或者简单的重命名一个符号?这是一个feature。这是java早就有的东西(通过eclipse,netbean),但是在Python和C++中实现非常困难。

    酷熊:C++现在的IDE支持了吗?

    并非不存在,但是一些语言功能使它非常难以优化。

    酷熊:Rust的宏编程不是有同样的问题吗?

    某种程度上是的,尽管 rust-analyzer 在这方面做的已经非常好了,甚至过程宏都能处理了。但是还是会有“哦 这里没有自动补全”,“哦 这里没有导入建议”。但是情况在过去几个月内已经好多了。

    枚举可以有关联数据。

    1. pub enum Protection {
    2. Secure { version: u64 },
    3. Insecure,
    4. }
    5. fn process(prot: Protection) {
    6. match prot {
    7. Protection::Secure { version } => {
    8. println!("Hacker-safe thanks to protocol v{version}");
    9. }
    10. Protection::Insecure => {
    11. println!("Come on in");
    12. }
    13. }
    14. }
    15. fn main() {
    16. process(Protection::Secure { version: 2 })
    17. }
    1. $ cargo run --quiet
    2. Hacker-safe thanks to protocol v2

    你能看到在第一个match匹配项中,我们解构了变量,从中提取出version。最终变成一个u64的变量绑定。这里嵌入提示也非常有用:
    image.png
    我们不大可能有大量版本的安全协议,因此我们在真正的安全协议中通常会用固定大小的整数,我们直接在代码里处理它们,我们也许会想把它们显示展示出来:

    1. pub enum Protection {
    2. Secure(SecureVersion),
    3. Insecure,
    4. }
    5. #[derive(Debug)]
    6. pub enum SecureVersion {
    7. V1,
    8. V2,
    9. V2_1,
    10. }
    11. fn process(prot: Protection) {
    12. match prot {
    13. Protection::Secure(version) => {
    14. println!("Hacker-safe thanks to protocol {version:?}");
    15. }
    16. Protection::Insecure => {
    17. println!("Come on in");
    18. }
    19. }
    20. }
    21. fn main() {
    22. process(Protection::Secure(SecureVersion::V2_1))
    23. }
    1. $ cargo run --quiet
    2. Hacker-safe thanks to protocol V2_1

    Clone和Copy
    我们的Protection 类型,目前为止既不是Copy也不是Clone。这意味着,我们传给process时,是把参数move进去的。一旦值被move进某个东西时,我们就不再有它的所有权了。所以这样的代码就不能工作了:

    1. fn main() {
    2. let prot = Protection::Secure(SecureVersion::V2_1);
    3. process(prot);
    4. process(prot);
    5. }
    1. $ cargo run --quiet
    2. error[E0382]: use of moved value: `prot`
    3. --> src/main.rs:27:13
    4. |
    5. 25 | let prot = Protection::Secure(SecureVersion::V2_1);
    6. | ---- move occurs because `prot` has type `Protection`, which does not implement the `Copy` trait
    7. 26 | process(prot);
    8. | ---- value moved here
    9. 27 | process(prot);
    10. | ^^^^ value used here after move
    11. For more information about this error, try `rustc --explain E0382`.
    12. error: could not compile `lox` due to previous error

    Protection 是POD(https://stackoverflow.com/questions/146452/what-are-pod-types-in-c)所以它做bit级别的Copy到别的地方是无害的:它会有相同的行为。
    所以此处,我们可以用过程宏Clone和Copy,所以此处并没有move进process中,将会是copy。

    1. #[derive(Clone, Copy)]
    2. pub enum Protection {
    3. Secure(SecureVersion),
    4. Insecure,
    5. }
    1. cargo run --quiet
    2. error[E0204]: the trait `Copy` may not be implemented for this type
    3. --> src/main.rs:1:17
    4. |
    5. 1 | #[derive(Clone, Copy)]
    6. | ^^^^
    7. 2 | pub enum Protection {
    8. 3 | Secure(SecureVersion),
    9. | ------------- this field does not implement `Copy`
    10. |
    11. = note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)
    12. For more information about this error, try `rustc --explain E0204`.
    13. error: could not compile `lox` due to previous error

    嗯,好吧, 我们还需要SecureVersion实现Copy,我们来写下:、

    1. #[derive(Clone, Copy)]
    2. pub enum Protection {
    3. Secure(SecureVersion),
    4. Insecure,
    5. }
    6. // 👇 👇
    7. #[derive(Clone, Copy, Debug)]
    8. pub enum SecureVersion {
    9. V1,
    10. V2,
    11. V2_1,
    12. }
    1. $ cargo run --quiet
    2. Hacker-safe thanks to protocol V2_1
    3. Hacker-safe thanks to protocol V2_1

    实现Copy和Clone的过程宏对这些类型非常有意义,但是假如我们由于学习的原因,我们不实现,我们回到这样的代码:

    1. pub enum Protection {
    2. Secure(SecureVersion),
    3. Insecure,
    4. }
    5. #[derive(Debug)]
    6. pub enum SecureVersion {
    7. V1,
    8. V2,
    9. V2_1,
    10. }
    11. fn process(prot: Protection) {
    12. match prot {
    13. Protection::Secure(version) => {
    14. println!("Hacker-safe thanks to protocol {version:?}");
    15. }
    16. Protection::Insecure => {
    17. println!("Come on in");
    18. }
    19. }
    20. }
    21. fn main() {
    22. let prot = Protection::Secure(SecureVersion::V2_1);
    23. process(prot);
    24. process(prot);
    25. }

    然后它编译失败了:

    1. $ cargo run --quiet
    2. error[E0382]: use of moved value: `prot`
    3. --> src/main.rs:27:13
    4. |
    5. 25 | let prot = Protection::Secure(SecureVersion::V2_1);
    6. | ---- move occurs because `prot` has type `Protection`, which does not implement the `Copy` trait
    7. 26 | process(prot);
    8. | ---- value moved here
    9. 27 | process(prot);
    10. | ^^^^ value used here after move
    11. For more information about this error, try `rustc --explain E0382`.
    12. error: could not compile `lox` due to previous error

    我们无法将一个值多次将同一个Protection传给process?
    好吧,我们不能传值(包括move和copy),我们可以传引用。

    1. fn process(prot: &Protection) {
    2. match prot {
    3. Protection::Secure(version) => {
    4. println!("Hacker-safe thanks to protocol {version:?}");
    5. }
    6. Protection::Insecure => {
    7. println!("Come on in");
    8. }
    9. }
    10. }
    11. fn main() {
    12. let prot = Protection::Secure(SecureVersion::V2_1);
    13. // 👇
    14. process(&prot);
    15. // 👇
    16. process(&prot);
    17. }
    1. $ cargo run --quiet
    2. Hacker-safe thanks to protocol V2_1
    3. Hacker-safe thanks to protocol V2_1

    这样可以!
    我们改下process函数的签名,但是我们没有改match的部分:它仍是这样:

    1. match prot {
    2. Protection::Secure(version) => {
    3. println!("Hacker-safe thanks to protocol {version:?}");
    4. }
    5. Protection::Insecure => {
    6. println!("Come on in");
    7. }
    8. }

    这意味着这里的代码仍然能正常工作,无论prot是Protection还是&Protection。这里很有趣…,非常有趣。
    这里我给你们提个问题,第一个match项的version类型是什么?
    我们都不用想,因为我能看到潜入的提示:
    image.png
    这里很有趣了,我们能想匹配Protection一样匹配&Protection,在传值的情况下,我们拿到的是SecureVersion,在传引用的情况下,我们拿到的是&SecureVersion。
    这看起来是个好事情,因为我们正想让代码这样工作。
    现在,我们试点不一样的。
    Locks
    Rust不会让你在同一时间创建多于一个的可变引用。这是加强内存安全的一种方式。
    例如:这样的代码不能编译:

    1. fn main() {
    2. let mut counter = 0_u64;
    3. crossbeam::scope(|s| {
    4. for _ in 0..3 {
    5. s.spawn(|_| {
    6. counter += 1;
    7. });
    8. }
    9. })
    10. .unwrap();
    11. }

    因为我们不能把同一个值的可变引用给三个线程。

    1. $ cargo run --quiet
    2. error[E0499]: cannot borrow `counter` as mutable more than once at a time
    3. --> src/main.rs:6:21
    4. |
    5. 4 | crossbeam::scope(|s| {
    6. | - has type `&Scope<'1>`
    7. 5 | for _ in 0..3 {
    8. 6 | s.spawn(|_| {
    9. | - ^^^ `counter` was mutably borrowed here in the previous iteration of the loop
    10. | _____________|
    11. | |
    12. 7 | | counter += 1;
    13. | | ------- borrows occur due to use of `counter` in closure
    14. 8 | | });
    15. | |______________- argument requires that `counter` is borrowed for `'1`
    16. For more information about this error, try `rustc --explain E0499`.
    17. error: could not compile `lox` due to previous error

    解决此问题的一种方式是Mutex,Rust中,mutex拥有自己所保护的数据,所以此处我们想要的是Mutex

    1. use parking_lot::Mutex;
    2. fn main() {
    3. let counter = Mutex::new(0_u64);
    4. crossbeam::scope(|s| {
    5. for _ in 0..3 {
    6. s.spawn(|_| {
    7. // let's increment it a "couple" times
    8. for _ in 0..100_000 {
    9. *counter.lock() += 1;
    10. }
    11. });
    12. }
    13. })
    14. .unwrap();
    15. let counter = counter.into_inner();
    16. println!("final count: {counter}");
    17. }
    1. $ cargo run --quiet
    2. final count: 300000

    类型系统和Mutex的设计,保证了我们只有在拥有锁时才能读写数据。
    parking_alot::Mutext::lock的签名如下:

    1. pub fn lock(&self) -> MutexGuard<'_, R, T> {

    它的意思是:

    • 获取一个Mutex的不可变引用
    • 返回一个生命周期不长于Mutex的不可变引用的 MutexGuard
    • 参数化锁类型R (此处是RawMutex)。
    • 参数化被保护的类型T,此处是u64。

    MutexGuard实现了Deref和DerefMut,这意味着它可以作为指向T的智能指针。
    换句话说,在乏味的类型注解的帮助下,我们可以这么玩:

    1. use parking_lot::Mutex;
    2. fn main() {
    3. let counter = Mutex::new(0_u64);
    4. let mut guard = counter.lock();
    5. // Using `DerefMut`
    6. let mutable_ref: &mut u64 = &mut guard;
    7. *mutable_ref = 42;
    8. // Using `Deref`
    9. let immutable_ref: &u64 = &guard;
    10. dbg!(immutable_ref);
    11. }