为什么我不能使用 `&Iterator<Item = &String>` 作为迭代器?

12

我有以下函数,它应该能够从一个Iterator中找到并返回最长的String长度:

fn max_width(strings: &Iterator<Item = &String>) -> usize {
    let mut max_width = 0;
    for string in strings {
        if string.len() > max_width {
            max_width = string.len();
        }
    }
    return max_width;
}

然而,编译器给了我以下错误:

error[E0277]: the trait bound `&std::iter::Iterator<Item=&std::string::String>: std::iter::Iterator` is not satisfied
 --> src/main.rs:3:19
  |
3 |     for string in strings {
  |                   ^^^^^^^ `&std::iter::Iterator<Item=&std::string::String>` is not an iterator; maybe try calling `.iter()` or a similar method
  |
  = help: the trait `std::iter::Iterator` is not implemented for `&std::iter::Iterator<Item=&std::string::String>`
  = note: required by `std::iter::IntoIterator::into_iter`

我刚接触Rust,并对此感到非常困惑,因为我认为我已经显式传递了一个迭代器。调用strings.iter()告诉我它没有实现,而调用strings.into_iter()会让我陷入可变性的困境,我肯定不想改变传递的参数。

我该如何遍历我的字符串?


4
在 Rust 语言中,常用的惯用写法是在函数结尾省略 return 关键字。只需写 max_width(不加分号)。 - Tim Diekmann
3个回答

14
您的代码失败是因为 Iterator 不同于 &Iterator。如果将 Iterator 传递给函数,可以解决此问题,但由于 Iterator 是一个 trait,其大小无法确定(您不知道您正在传递哪个 Iterator)。解决方案是传递任何实现了 Iterator 的东西:
fn max_width<'a>(strings: impl Iterator<Item = &'a String>) -> usize

游乐场


对于更有经验的Rust用户:

最通用的方式可能是这样的:

fn max_width<T: AsRef<str>>(strings: impl IntoIterator<Item = T>) -> usize {
    let mut max_width = 0;
    for string in strings {
        let string = string.as_ref();
        if string.len() > max_width {
            max_width = string.len();
        }
    }
    max_width
}

游乐场

然而,你也可以使用

fn max_width<T: AsRef<str>>(strings: impl IntoIterator<Item = T>) -> usize {
    strings
        .into_iter()
        .map(|s| s.as_ref().len())
        .max()
        .unwrap_or(0)
}

沙盒


这个代码可以运行,但我很难理解其语义。我知道Iterator是一个特质(因此可以是实现该特质的任何内容),但在这种情况下,我对impl关键字感到困惑。我们如何在以前没有它的情况下获得已知大小? - pjf
我建议阅读这篇文章。基本上,它与fn max_width<'a, T: Iterator<Item = &'a String>(strings: T) -> usize相同。 - Tim Diekmann
3
哦!所以Iterator是指那个特质本身,而impl Iterator是实现了该特质的某个东西。非常感谢!! - pjf
1
谢谢Tom。我认为这个章节与旧链接相匹配。 - Tim Diekmann

6
其他答案告诉你如何接受迭代器,但没有回答你实际的问题:
为什么我不能使用 `&Iterator` 作为迭代器?
有趣的是,你已经阻止了它发生:
并且我肯定不想改变传递的参数。
迭代器通过改变目标来工作——这就是迭代器如何在每次调用中更改返回值的方法!
pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    //       ^^^
}

通过接收一个不可变的trait对象,你的迭代器无法更新自身,因此实际的迭代也是不可能的。
为了使你的代码能够编译,你可以做的最小化改动就是接受一个可变引用。
fn max_width(strings: &mut dyn Iterator<Item = &String>) -> usize

然而,我可能会这样编写函数:

fn max_width<I>(strings: I) -> usize
where
    I: IntoIterator,
    I::Item: AsRef<str>,
{
    strings
        .into_iter()
        .map(|s| s.as_ref().len())
        .max()
        .unwrap_or(0)
}
  1. 不要使用明确的 return
  2. 使用迭代器组合方式,例如mapmax
  3. 使用Option::unwrap_or提供默认值。
  4. 使用IntoIterator接受任何可转换为迭代器的内容。

1
我更喜欢这个答案,因为它还提供了一种接近 OP 最初意图的动态多态解决方案。 - Thierry

1
如果您不需要迭代任何给定的迭代器的普遍性,编写函数的更简单方法是让您的max_width函数接受一个&[&str](字符串切片的切片)。您可以在for循环中使用切片,因为Rust知道如何将其转换为迭代器(它实现了IntoIterator特质):
fn max_width(strings: &[&str]) -> usize {
    let mut max_width = 0;
    for string in strings {
        if string.len() > max_width {
            max_width = string.len();
        }
    }
    return max_width;
}

fn main() {
    let strings = vec![
        "following",
        "function",
        "supposed",
        "return",
        "longest",
        "string",
        "length"
    ];

    let max_width = max_width(&strings);

    println!("Longest string had size {}", max_width);
}

// OUTPUT: Longest string had size 9

在这里玩耍


谢谢,我会更新。&String也会解引用为&str,你是否可以传递它而不是&Vec<String>?我想也许OP刚接触Rust,只是想迭代一堆字符串,而Vec似乎是一个简单的方法。但通用解决方案也很有趣。 - Matt Harrison
非常抱歉,我应该先说明我的用例!我正在从一个结构体向量中解包字段(max_width(recipes.iter().map(|x| &x.name))),因此我已经有了一个迭代器,所以函数使用它而不是向量。 - pjf
1
@pjf 如果您考虑使用以下代码替换它:recipes.iter().map(|x| x.name.len()).max().unwrap_or(0) - Tim Diekmann
您也可以将您的用例添加到问题中 :) - Tim Diekmann

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