Rust中的字符串相等性比较:引用和解引用是如何工作的?

7
作为一名Rust新手,我正在解决Project Euler问题,以帮助我熟悉这种语言。第4个问题涉及回文,我找到了两种创建回文向量的方法,但我不确定它们中的任何一种是如何工作的。
我正在使用一个字符串向量products,计算方法如下:
let mut products = Vec::new();
for i in 100..500 {
    for j in 500..1000 {
        products.push((i * j).to_string());
    }
}

对于筛选回文产品,我有以下两个解决方案: 解决方案1:
let palindromes: Vec<_> = products
    .iter()
    .filter(|&x| x == &x.chars().rev().collect::<String>())
    .collect();

解决方案2:

let palindromes: Vec<_> = products
    .iter()
    .filter(|&x| *x == *x.chars().rev().collect::<String>())
    .collect();

他们都得出了正确的结果,但我不知道为什么!
在解决方案1中,我们比较了一个字符串的引用和一个我们刚刚创建的字符串的引用?
在解决方案2中,我们取消引用了一个字符串的引用,并将其与取消引用的新字符串进行比较?
我希望能够做到的是:
let palindromes: Vec<_> = products
    .iter()
    .filter(|x| x == x.chars().rev().collect::<String>())
    .collect();

我希望有人能够解释:

  • 我的两个解决方案有什么不同,为什么它们都有效?
  • 为什么在过滤函数中不能只使用x而不引用或取消引用它?

谢谢!


1
"它们都产生了正确的结果,但我不知道为什么!" => "理论是当你知道一切但什么都不起作用实践是当一切都起作用但没有人知道为什么在我们的实验室中,理论和实践结合在一起,什么都不起作用,也没有人知道为什么" - Stargateur
1
我认为这与如何迭代数组?非常相似(尽管标题不佳)。那个问题的答案是否有助于澄清问题? - trent
4
请注意,您不需要分配一个String,您可以比较迭代器:.filter(|x| x.chars().eq(x.chars().rev()))。 (提示:这是一段代码,建议在翻译时尽可能保留原有的代码格式和结构) - Boiethios
3
@piercebot 很好,项目欧拉也是我开始学习 Rust 的方式。请允许我向您发起挑战:能否想出一种不需要分配内存(collect 会在堆上创建一个新的 String)的回文检查方法? :) - MB-F
好吧..挑战就到这里吧 :p - MB-F
@Boiethios,我不知道.eq,这超级好!谢谢!然而采用暴力方法会遇到很多问题;感谢@kazemakase给我们的教训 :) - piercebot
1个回答

11
  1. Vec<String>.iter() 返回一个对引用(&String)的迭代器。
  2. .filter() 的闭包参数接受一个迭代器项的引用。所以传递给闭包的类型是双重引用 &&String
  3. |&x| 告诉闭包期望一个引用,所以现在的 x 是类型为 &String

第一种解决方案: collect返回一个字符串String,其中 & 取得引用。 x 也是字符串的引用,因此比较是两个 &String之间的比较。

第二种解决方案: 解引用运算符 * 应用于 x,结果是一个 String。右侧很有趣: collect 的结果是一个字符串切片,因为 String 实现了 Deref<Target=str>。现在比较是 Stringstr 之间的比较,这是可行的,因为它在标准库中 被实现了(注意,a == b 等同于 a.eq(&b))。

第三种解决方案: 编译器解释了为什么不起作用。

trait std::cmp::PartialEq<std::string::String> 没有为 &&std::string::String 实现。

左侧是字符串的双重引用(&&String),右侧只是一个 String您需要将两边都转换为相同的“引用级别”。以下所有方法都有效:

x.iter().filter(|x| x == &&x.chars().rev().collect::<String>());
x.iter().filter(|x| *x == &x.chars().rev().collect::<String>());
x.iter().filter(|x| **x == x.chars().rev().collect::<String>());

太棒了!感谢您详细的解释!所有闭包参数默认都是引用吗,还是要根据情况而定? - piercebot
4
逐个处理。如果您查看filter的定义,可以看到它需要一个接受&Iter::Item的闭包。相比之下,例如通过filter_map接受的闭包会采用Iter::Item,这不是一个引用。 - MB-F

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接