Rust中的迭代器类型

5

我正在学习Rust,并遇到了问题。我有这个MCVE:

fn main() {
    let mut line = String::new();
    std::io::stdin()
        .read_line(&mut line)
        .expect("Failed to read line");

    handle_tokens( line.split_ascii_whitespace() );
}

fn handle_tokens( mut it: std::str::SplitAsciiWhitespace ) {
    loop {
        match it.next() {
            None => return,
            Some(s) => println!("{}",s),
        }
    }
}

String::split_ascii_whitespace 返回一个SplitAsciiWhitespace对象,因此我在 handle_tokens 的签名中使用了它,但是 std::str::SplitAsciiWhitespace 是一个非常具体的类型。一个通用的迭代器适用于字符串列表,这样我就可以选择split_whitespace 或者一个通用的字符串列表。

如何使用文档或编译错误来推广 handle_tokens 的签名?


以下是我自己尝试回答这个问题失败的尝试:

我看到SplitAsciiWhitespace "Trait Implementations" 包括:

impl<'a> Iterator for SplitWhitespace<'a>

这里是next()的来源(我不得不检查源代码来验证)。因此,我尝试着使用一个迭代器和fn handle_tokens( mut it: Iterator ) {,但是:
error[E0191]: the value of the associated type `Item` (from trait `std::iter::Iterator`) must be specified
  --> src/main.rs:10:27
   |
10 | fn handle_tokens( mut it: Iterator ) {
   |                           ^^^^^^^^ help: specify the associated type: `Iterator<Item = Type>`

好的,所以Iterator太泛泛了,我需要告诉编译器它封装的是什么。这样才有意义,否则我就无法解引用它。我不得不再次查看源代码来查看SplitWhitespace如何实现迭代器,并看到 type Item = &'a str; 所以我尝试使用fn handle_tokens( mut it: Iterator<Item = &str>)来指定Item,但是遇到了问题:

error[E0277]: the size for values of type `(dyn std::iter::Iterator<Item = &str> + 'static)` cannot be known at compilation time
  --> src/main.rs:10:19
   |
10 | fn handle_tokens( mut it: Iterator<Item = &str> ) {
   |                   ^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `(dyn std::iter::Iterator<Item = &str> + 'static)`
   = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
   = note: all local variables must have a statically known size
   = help: unsized locals are gated as an unstable feature

好的,所以我需要指定一个大小。这很奇怪,因为虽然我知道 str 的大小不能在编译时确定,但 &str 的大小应该可以。

此时我非常困惑。当 Rust 提供了如此出色的内置文档支持时,我也感到惊讶需要进行源代码检查。这使我认为我回答这个问题的方法是错误的。


这个回答解决了你的问题吗? 如何在Rust中正确地将迭代器传递给函数 - E net4
在我的(初学者)水平上,很难理解那个答案。我确定它不是错的,但添加 whereIntoIteratorBorrow 还不是我掌握的内容。@Kitsu 的回答非常清晰。 - Stewart
2个回答

4
你实际上已经走在正确的道路上了。`next` 确实是在 `Iterator` 中定义的,这是你需要使用的内容。你错过的事情是 `Iterator` 实际上是一个 trait,而不是一个类型。类型可以被 trait 限定,因此泛型变量非常方便:
fn handle_tokens<'a, I: Iterator<Item = &'a str>>(mut it: I) { .. }

还有一种特殊的impl-trait语法可以使用:

fn handle_tokens<'a>(mut it: impl Iterator<Item = &'a str>) { .. }

然而,最后一个示例不能使用明确指定的类型来调用,即 handle_tokens::<SplitAsciiWhitespace>(iter)


1
在实践中,如果您希望函数尽可能通用,则通常会使用IntoIterator而不是Iterator - Sven Marnach

3

handle_tokens函数使用Iterator trait的next函数,并要求迭代器中的元素实现Display trait,因此你可以将该函数泛型化。

use std::fmt::Display;
fn handle_tokens<T>(mut tokens: T)
where
    T: Iterator,
    <T as Iterator>::Item: Display,
{
    loop {
        match tokens.next() {
            None => return,
            Some(s) => println!("{}", s),
        }
    }
}

或者您可以使用 .collect() 迭代器

let tokens = line.split_ascii_whitespace().collect::<Vec<_>>()

我看到你尝试使用了dyn。它被称为特质对象


fn handle_tokens3(it: &mut dyn Iterator<Item = &str>) {
    loop {
        match it.next() {
            None => return,
            Some(s) => println!("{}", s),
        }
    }
}

点击链接可以进入代码演示环境


我尝试使用dyn,因为警告建议这样做。我只是在同一本书的第8章中进行练习(你链接到的是第17章)。很高兴知道这一切很快就会变得清晰明了。 - Stewart
2
请注意,使用 dyn 会产生后果/运行时成本,这意味着您通过虚函数表(vtable)处理对象,并且对此类对象的方法调用将是间接的。仅在您知道这正是您所需的情况下才使用它。 - Jesper

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