【问题标题】:How to write a Rust function that takes an iterator?如何编写一个带有迭代器的 Rust 函数?
【发布时间】:2016-04-30 10:08:28
【问题描述】:

我想写一个函数,它接受一个迭代器并返回一些操作的结果。具体来说,我正在尝试遍历 HashMap 的值:

use std::collections::HashMap;

fn find_min<'a>(vals: Iterator<Item=&'a u32>) -> Option<&'a u32> {
    vals.min()
}

fn main() {
    let mut map = HashMap::new();
    map.insert("zero", 0u32);
    map.insert("one", 1u32);
    println!("Min value {:?}", find_min(map.values()));
}

可惜:

error: the `min` method cannot be invoked on a trait object
 --> src/main.rs:4:10
  |
4 |     vals.min()
  |          ^^^

error[E0277]: the trait bound `std::iter::Iterator<Item=&'a u32> + 'static: std::marker::Sized` is not satisfied
 --> src/main.rs:3:17
  |
3 | fn find_min<'a>(vals: Iterator<Item = &'a u32>) -> Option<&'a u32> {
  |                 ^^^^ `std::iter::Iterator<Item=&'a u32> + 'static` does not have a constant size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `std::iter::Iterator<Item=&'a u32> + 'static`
  = note: all local variables must have a statically known size

error[E0308]: mismatched types
  --> src/main.rs:11:41
   |
11 |     println!("Min value {:?}", find_min(map.values()));
   |                                         ^^^^^^^^^^^^ expected trait std::iter::Iterator, found struct `std::collections::hash_map::Values`
   |
   = note: expected type `std::iter::Iterator<Item=&u32> + 'static`
              found type `std::collections::hash_map::Values<'_, &str, u32>`

如果我尝试通过引用传递,我会得到同样的错误;如果我使用Box,我会得到终身错误。

【问题讨论】:

  • 许多用例会受益于提出一个更广泛的问题:“如何编写一个采用 iterable 的 Rust 函数?”可迭代,我的意思是可以迭代的东西。 (这比迭代器更广泛。)正如in this answer 所述,要做到这一点,请使用IntoIterator,因为任何实现IntoIterator 的类型都是可迭代的。

标签: function iterator rust


【解决方案1】:

你想在这里使用泛型:

fn find_min<'a, I>(vals: I) -> Option<&'a u32>
where
    I: Iterator<Item = &'a u32>,
{
    vals.min()
}

Traits 可以以两种方式使用:作为类型参数的边界和作为 trait 对象。 Rust 编程语言这本书有一个关于 traits 的章节和一个关于 trait objects 的章节来解释这两个用例。

此外,您通常希望采用实现 IntoIterator 的东西,因为这可以使调用您的函数的代码更好:

fn find_min<'a, I>(vals: I) -> Option<&'a u32>
where
    I: IntoIterator<Item = &'a u32>,
{
    vals.into_iter().min()
}

【讨论】:

  • 我是如此接近。解释一下,与泛型的区别在于静态调度,即,Rust 正在为我调用它的每个具体类型创建这个函数的一个版本?
  • 正确。这样,编译器就知道迭代器的类型,因此它知道它的大小,因此它知道它需要保留多少内存。另外,像Iterator&lt;Item=&amp;'a u32&gt; 这样的类型不能单独使用;它只能在指针后面使用。
  • 请注意,如果您想将任意迭代器传递给函数,则要求I 实现Iterator 是绝对正确的,但更通用的方法是要求I 实现IntoIterator .它也允许您传递迭代器,但您也可以将任何可以转换的东西传递到迭代器中,而无需显式调用转换方法。我想说这是使用迭代器和可迭代对象的惯用方法。
  • @VladimirMatveev,你能详细说明一下吗?我正在尝试根据您的建议获得类似以下内容...gist.github.com/anonymous/0c1db25a9b38a2fc95f44bec34ebd46a
  • 有了foo: impl Type,现在变得更容易了
【解决方案2】:

由于 Rust 1.26 impl Trait 可用。一个不太冗长的版本。

use std::collections::HashMap;

fn find_min<'a>(vals: impl Iterator<Item = &'a u32>) -> Option<&'a u32> {
    vals.min()
}

fn main() {
    let mut map = HashMap::new();
    map.insert("zero", 0u32);
    map.insert("one", 1u32);
    println!("Min value {:?}", find_min(map.values()));
}

playground

【讨论】:

  • 有什么理由说明这不会比其他替代品更好,还是只是最近?
  • 在 Rust 1.26 impl Trait 被添加到语言中。这是实现相同代码的一种不太冗长的方式
【解决方案3】:

对于那些有 Python 背景而不是 C++ 背景的人来说,这种行为有点不直观,所以让我稍微澄清一下。

在 Rust 中,值在概念上存储在绑定它们的名称中。因此,如果你写

let mut x = Foo { t: 10 };
let mut y = x;
x.t = 999;

y.t 仍将是 10

所以当你写的时候

let x: Iterator<Item=&'a u32>;

(或在函数参数列表中相同),Rust 需要为 Iterator&lt;Item=&amp;'a u32&gt; 类型的 any 值分配足够的空间。即使这可能,也不会有效。

因此,Rust 所做的就是为您提供选择

  • 将值放在堆上,例如。 Box,它提供了 Python 风格的语义。然后你可以用&amp;mut Iterator&lt;Item=&amp;'a u32&gt; 通用。

  • 为每个可能的类型专门化每个函数调用以满足界限。这更灵活,因为特征引用是一种可能的特化,并为编译器提供了更多特化的机会,但这意味着您不能有 dynamic 调度(其中类型可以根据运行时参数而变化) .

【讨论】:

  • 这里的例子不能编译, let mut y = x 赋值移动了 x,所以第 3 行不能再使用 x。 (除非您指定 Foo 是 Copy,但这已经很好地暗示了它不会像您在脚本语言中所期望的那样表现)
  • 不管“它是否改变了代码,或者它改变了你被允许做的事情”的语义,上面的示例代码仍然无效,并且无法编译。
  • 我认为它是非复制的,因为这是结构的默认设置,并且在您的答案中没有提到它是复制。
  • 问题是你用来推理的代码是无效的,没有一些初学者可能没有的任意假设。
  • 同意 - 此处的代码已损坏,会教人们关于 Rust 的错误知识。请修复!
猜你喜欢
  • 1970-01-01
  • 2016-06-26
  • 2019-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-12
  • 2011-04-11
相关资源
最近更新 更多