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

Shm*_*opy 37 rust

这是Rust的assert_eq!宏实现.为简洁起见,我只复制了第一个分支:

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)
                }
            }
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

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

DK.*_*DK. 47

好吧,让我们删除比赛.

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

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

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);
Run Code Online (Sandbox Code Playgroud)

这将扩展到......

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

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

match只是一种快速,简单的方法,可以将宏的"参数"恰好评估一次,并将它们绑定到变量名称.

  • 实际上,`match expr {x => {...}}`是相当于`let x = expr in ...'的Rust,因为它对于终生临时工作来说是最放松的,"应该总是有效",例如`match e {x => f(x)}`应该总是等于`f(e)`.另一方面,`let x = expr;`更"强制"并且不允许保持嵌套的临时值,例如`let x = f(&g());`只有在`f`使用其引用参数时才编译没有返回它,因为临时它只指向呼叫期间的生命. (32认同)
  • 难道这不可能像'let left = $ left;`这样的东西吗? (6认同)
  • @JeroenBollen这与边缘情况下出现的匹配略有不同,但我不记得细节.我认为*它与临时的确切生命周期有关.由于我不知道,我避免提及它. (4认同)
  • 我之前找不到它,但这是关于临时工生活的博客文章之一(注意它可能并不完全反映当前的Rust):http://smallcultfollowing.com/babysteps/blog/2014/01/09/右值-寿命-在防锈/ (2认同)

Joh*_*ohn 7

Usingmatch确保表达式$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]);
Run Code Online (Sandbox Code Playgroud)

假设这扩展为:

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

在 Rust 中,语句中产生的临时变量的生命周期通常仅限于语句本身。因此,这种扩展是错误的

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

临时vals.collect::<Vec<_>>()需要至少与 一样长left,但实际上它在let语句的末尾被删除。

将此与扩展进行对比

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

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

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

请注意,这种情况在非词法生命周期中没有改变。尽管它的名字,NLL 不会改变任何值的生命周期——即当它们被删除时。它只会使借用的范围更加精确。所以在这种情况下它对我们没有帮助。

  • 恕我直言,另一个答案基本上是不正确的,或者至少忽略了使用`match`的_main_原因。可以使用 `let left = $left; 来避免双重评估。let right = $right;` 我相信 OP 问题的本质是:为什么这不起作用? (6认同)