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

时间:2018-02-11 13:57:05

标签: rust

这是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的目的是什么?为什么不检查不平等?

2 个答案:

答案 0 :(得分: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只是一种快速,简单的方法,可以将宏的“参数”恰好评估一次并将它们绑定到变量名称。

答案 1 :(得分: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!("...");
        }
    }
}

这会产生相同的临时变量,但是其生存期会遍及整个match表达式-足够长的时间供我们比较leftright,如果比较失败,则会将它们插值到错误消息中。

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

请注意,non-lexical lifetimes的情况不变。尽管名称如此,但NLL不会更改任何值的生命周期-即何时删除它们。这只会使借贷的范围更加精确。因此,在这种情况下对我们没有帮助。