为什么用 assert_eq 比较 string 和 &str 会成功?
昨天写代码的时候发现用 assert_eq 比较 string 和 &str 会成功,如下:
let s1 = String::from("hello");
let s2 = "hello";
assert_eq!(s1, s2);
凭直觉来讲,这不应该能通过,因为 string 和 &str 是两种类型,然后我觉得是 assert_eq 这个宏做了特殊处理,于是我又试了一下
let s1 = String::from("hello");
let s2 = "hello";
assert!(s1 == s2);
发现也没有问题,这说明和 assert_eq 没啥关系,于是我在 reddit 以及 Rust 中文社区提问,一个老哥的回答是:
String and &str cannot be compared at all, so Rust will look for ways to make the comparison possible. It will find that s1.deref() returns &str which obviously can be compared to &str (because Strings implement Deref<Target=str>) so it will automatically use that. This is called “deref coercion”.
简言之,他的理解是因为强制解引用多态(deref coercion)使得 s1 自动调用 deref() ,而其实现了 Deref <Target=str>
,所以返回 &str , 然后就能与 s2 相比较了。逻辑非常清晰,我都已经道谢了,但是后面证实这是不正确的,另一位老哥也对这种说法进行了严谨地反驳:
这个说法是错的,跟 Deref 没有直接的关系,你可以这样写:
// assert macro will print using debug format
#[derive(Debug)]
struct S {
inner: String,
}
impl S {
pub fn new(inner: &str) -> Self {
Self {
inner: inner.to_owned(),
}
}
}
impl std::cmp::PartialEq<&str> for S {
fn eq(&self, other: &&str) -> bool {
return self.inner.eq(other);
}
}
// comment for compile error
impl std::cmp::PartialEq<S> for &str {
fn eq(&self, _other: &S) -> bool {
return false;
}
}
fn main() {
let s = "hello";
let string = String::from("hello");
let fake_string = S::new("hello");
assert_eq!(string, s);
assert_eq!(fake_string, s);
assert_eq!(s, fake_string);
}
这里 S 没有实现任何 deref,但是还是可以通过编译。把
assert_eq
宏展开之后是这样的:
match (&s, &fake_string) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
{
::std::rt::begin_panic_fmt...
也就是说这里只调用了 lhs == rhs,只要实现了 PartialEq for Lhs 就可以了。 String 的文档里面两种顺序都实现了。
后面通过翻文档发现 String 和 str 确实互相实现了 PartialEq Trait,所以 String 和 &str 是可以直接比较的。
感谢 rustaceans 的热心解答。