为什么Rust的assert_eq!使用匹配实现?

问题描述 投票:37回答:2

这是Rust的assert_eq! macro implementation。为简洁起见,我只复制了第一个分支:

macro_rules! assert_eq {
    ($left:expr, $right:expr) => ({
        match (&$left, &$right) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    panic!(r#"assertion failed: `(left == right)`
  left: `{:?}`,
 right: `{:?}`"#, left_val, right_val)
                }
            }
        }
    });
}

这里match的目的是什么?为什么不检查不平等?

rust
2个回答
47
投票

好吧,让我们删除比赛。

    macro_rules! assert_eq_2 {
        ($left:expr, $right:expr) => ({
            if !($left == $right) {
                panic!(r#"assertion failed: `(left == right)`
  left: `{:?}`,
 right: `{:?}`"#, $left, $right)
            }
        });
    }

现在,让我们选择一个完全随机的例子......

fn really_complex_fn() -> i32 {
    // Hit the disk, send some network requests,
    // and mine some bitcoin, then...
    return 1;
}

assert_eq_2!(really_complex_fn(), 1);

这将扩展到......

{
    if !(really_complex_fn() == 1) {
        panic!(r#"assertion failed: `(left == right)`
  left: `{:?}`,
 right: `{:?}`"#, really_complex_fn(), 1)
    }
}

如您所见,我们两次调用该函数。这不太理想,如果函数的结果每次调用时都会改变,那就更不理想了。

match只是一种快速,简单的方法,可以精确地评估宏的“参数”并将它们绑定到变量名称。


2
投票

使用match确保表达式$left$right每次仅被评估一次,并且在评估期间创建的任何临时值至少与结果绑定leftright一样长。

多次使用$left$right的扩展 - 一次执行比较时,再次插入错误消息时 - 如果任一表达式具有副作用,则会出现意外行为。但为什么扩张不能像let left = &$left; let right = &$right;那样?

考虑:

let vals = vec![1, 2, 3, 4].into_iter();
assert_eq!(vals.collect::<Vec<_>>().as_slice(), [1, 2, 3, 4]);

假设这扩展到:

let left = &vals.collect::<Vec<_>>().as_slice();
let right = &[1,2,3,4];
if !(*left == *right) {
    panic!("...");
}

在Rust中,声明中生成的临时生命的生命周期通常仅限于声明本身。因此,这个扩展is an error

error[E0597]: borrowed value does not live long enough
  --> src/main.rs:5:21
   |
5  |         let left = &vals.collect::<Vec<_>>().as_slice();
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^           - temporary value dropped here while still borrowed
   |                     |
   |                     temporary value does not live long enough

暂时的vals.collect::<Vec<_>>()需要至少与left一样长,但实际上它在let声明结束时被删除。

将此与扩展进行对比

match (&vals.collect::<Vec<_>>().as_slice(), &[1,2,3,4]) {
    (left, right) => {
        if !(*left == *right) {
            panic!("...");
        }
    }
}

这会生成相同的临时值,但它的生命周期会扩展到整个匹配表达式 - 足够长,以便我们比较leftright,并在比较失败时将它们插入到错误消息中。

从这个意义上讲,match是Rust的let ... in构造。

请注意,这种情况与non-lexical lifetimes不变。尽管它的名字,NLL并没有改变任何值的生命周期 - 即它们被丢弃时。它只会使借用范围更加精确。所以在这种情况下它并没有帮助我们。

© www.soinside.com 2019 - 2024. All rights reserved.