当 PartialEq 的实现不合适时,如何断言两个变量相等?

How to assert two variables are equal when the implementation of PartialEq isn't appropriate?

在我的程序中,我用以下结构表示"events":

struct Event {
    value: &'static str,
    timestamp: usize,
}

到目前为止,我使用 PartialEq 来比较 Event 变量:大多数时候,我认为两个 Event 是相等的,如果它们的 value 是相同:

impl PartialEq for Event {
    fn eq(&self, other: &Self) -> bool {
        self.value == other.value
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_loose_equality() {
        let a = Event { value: "a-value", timestamp: 12345 };
        let b = Event { value: "a-value", timestamp: 23456 };

        assert_eq!(a, b);
    }
}

然而,在某些测试中,我想确保两个这样的变量是"strictly equals":测试应该失败他们有不同的timestamp(在[=18方面不一样) =]).

根据 assert_eq! 的文档:

Asserts that two expressions are equal to each other (using PartialEq). source

所以,我正在寻找一个 Eq 等价物,一个 assert_Eq_eq! 排序。

(或者我误解了 Eq 的工作原理和使用方法?)

以下是我未能完成的内容:

impl Eq for Event {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_strict_equality() {
        let a = Event { value: "a-value", timestamp: 12345 };
        let b = Event { value: "a-value", timestamp: 12345 };

        // ???
    }
}

是的,你误会了什么。 Eq 没有超过 PartialEq 的任何新方法。这只是一个断言 PartialEq 的实现是自反的(以及 PartialEq 假设的传递性和对称性)。

如果你想要一个不同的 "strict" 相等性,你可以创建自己的特征(如果你希望经常使用它)或者简单地在 [=16] 上附加一个方法 fn strict_eq(&self, other: &Self) -> bool =].

给定这样的方法,您可以编写一个宏来使用该方法。

可以找到 assert_eq! 的完整源代码 here 并且可以很容易地改编,但是(可能过于)简单的版本类似于

macro_rules! assert_strict_eq {
    ($left: expr, $right: expr) => {
        if !$left.strict_eq(&$right) {
            panic!(r#"assertion failed: `(left == right)`
  left: `{:?}`,
 right: `{:?}`"#, $left, $right)
        }
    }
}

(example usage)

你在逆流而上逆流而上。顺其自然。设 PartialEq 为严格相等,并为松散相等定义一个单独的特征或方法。

#[derive(Eq, PartialEq)]
struct Event { .. }

impl Event {
    fn loose_eq(&self, other: &Self) -> bool {
        self.value == other.value
    }
}
#[test]
fn test_loose_equality() {
    let a = Event { value: "a-value", timestamp: 12345 };
    let b = Event { value: "a-value", timestamp: 23456 };

    assert!(a.loose_eq(b));
}

我会根据需要创建 "views" 类型:

struct Event {
    value: &'static str,
    timestamp: usize,
}

#[derive(Debug, PartialEq)]
struct Exact<'a> {
    value: &'a &'static str,
    timestamp: &'a usize,
}

impl Event {
    fn exact(&self) -> Exact<'_> {
        let Self { value, timestamp } = self;
        Exact { value, timestamp }
    }
}

fn demo(a: Event, b: Event) {
    assert_eq!(a.exact(), b.exact());
}

这里我选择引用每个字段来演示一般情况,但您不需要引用这个特定示例(&strusize 实现 Copy 并且很小)。

您也可以选择根本不在原始类型上实现 PartialEq,而 通过视图执行比较:

assert_eq!(a.exact(), b.exact());
assert_eq!(a.loose(), b.loose());

与 JavaScript 不同,Rust 不支持相等与严格相等。

但是,您可以通过实现自己的特征和宏来实现类似的效果:

pub trait StrictEq {
    fn strict_eq(&self, other: &Self) -> bool;
}

macro_rules! assert_strict_eq {
    ($left:expr, $right:expr) => {{
        match (&$left, &$right) {
            (left_val, right_val) => {
                assert!(left_val.strict_eq(right_val));
            }
        }
    }};
}

Event 的实现相当简单:

impl StrictEq for Event {
    fn strict_eq(&self, other: &Self) -> bool {
        self.value == other.value && self.timestamp == other.timestamp
    }
}

如果你只在测试中需要严格相等,而你的结构只有两个字段,我会直接比较这些字段:

let a = Event { value: "a-value", timestamp: 12345 };
let b = Event { value: "a-value", timestamp: 12345 };
assert_eq!(a.value, b.value);
assert_eq!(a.timestamp, b.timestamp);

对我来说,这看起来是最简单、最易读的选项。