【问题标题】:Iterator types in RustRust 中的迭代器类型
【发布时间】:2023-03-31 02:33:02
【问题描述】:

我正在学习 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 太通用了,无法使用...我需要告诉编译器它在包装什么。这是有道理的,否则我将无法取消引用它。我不得不再次查看source code 以了解SplitWhitespace 如何实现迭代器并看到type Item = &amp;'a str; 所以我尝试用fn handle_tokens( mut it: Iterator&lt;Item = &amp;str&gt;) 指定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 的大小在编译时无法知道,但 &amp;str 的大小应该是。

在这一点上,我很困惑。当 Rust 似乎提供了如此出色的内置文档支持时,源代码检查是必要的,我也很惊讶。这让我觉得我用来回答这个问题的方法是错误的。

【问题讨论】:

  • 在我(初学者)的水平上,很难理解这个答案。我确定这没有错,但添加 where IntoIteratorBorrow 还不是我要做的。 @Kitsu 的回答很明确。

标签: rust


【解决方案1】:

实际上,您走在正确的道路上。 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::&lt;SplitAsciiWhitespace&gt;(iter)

【讨论】:

  • 在实践中,如果您希望函数尽可能通用,您通常会使用IntoIterator 而不是Iterator
【解决方案2】:

fn handle_tokens 使用来自 Iterator 特征的 fn next 并在迭代器的项目上要求 Display 特征,因此您可以将此函数设为通用。

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。它叫做trait objects


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

Link to the playground

【讨论】:

  • 我尝试了dyn,因为有警告提示。我只是在做同一本书第 8 章中的练习(你链接到第 17 章)。很高兴知道这一切很快就会变得有意义。
  • 请注意,使用dyn 会产生后果/运行时成本,这意味着您正在通过虚函数表 (vtable) 处理对象,并且对此类对象的方法调用将是间接的。仅当您知道这是您需要时才使用它。
猜你喜欢
  • 2021-04-25
  • 2021-08-02
  • 1970-01-01
  • 1970-01-01
  • 2015-06-03
  • 1970-01-01
  • 2014-03-21
  • 2022-01-08
  • 1970-01-01
相关资源
最近更新 更多