【问题标题】:What is the difference between iter and into_iter?iter 和 into_iter 有什么区别?
【发布时间】:2021-12-19 12:40:05
【问题描述】:

我正在做 Rust by Example 教程,它有这个代码 sn-p:

// Vec example
let vec1 = vec![1, 2, 3];
let vec2 = vec![4, 5, 6];

// `iter()` for vecs yields `&i32`. Destructure to `i32`.
println!("2 in vec1: {}", vec1.iter()     .any(|&x| x == 2));
// `into_iter()` for vecs yields `i32`. No destructuring required.
println!("2 in vec2: {}", vec2.into_iter().any(| x| x == 2));

// Array example
let array1 = [1, 2, 3];
let array2 = [4, 5, 6];

// `iter()` for arrays yields `&i32`.
println!("2 in array1: {}", array1.iter()     .any(|&x| x == 2));
// `into_iter()` for arrays unusually yields `&i32`.
println!("2 in array2: {}", array2.into_iter().any(|&x| x == 2));

我完全糊涂了——对于Vec,从iter 返回的迭代器产生引用,而从into_iter 返回的迭代器产生值,但是对于数组,这些迭代器是相同的吗?

这两种方法的用例/API 是什么?

【问题讨论】:

    标签: rust


    【解决方案1】:

    TL;DR:

    • into_iter 返回的迭代器可能会产生 T&T&mut T 中的任何一个,具体取决于上下文。
    • 按照惯例,iter 返回的迭代器将产生 &T
    • 按照惯例,iter_mut 返回的迭代器将产生 &mut T

    第一个问题是:“into_iter是什么?”

    into_iter来自IntoIterator trait

    pub trait IntoIterator 
    where
        <Self::IntoIter as Iterator>::Item == Self::Item, 
    {
        type Item;
        type IntoIter: Iterator;
        fn into_iter(self) -> Self::IntoIter;
    }
    

    当您想指定如何将特定类型转换为迭代器时,您可以实现此 trait。最值得注意的是,如果一个类型实现了IntoIterator,它可以在for 循环中使用。

    例如,Vec 实现了IntoIterator...三次!

    impl<T> IntoIterator for Vec<T>
    impl<'a, T> IntoIterator for &'a Vec<T>
    impl<'a, T> IntoIterator for &'a mut Vec<T>
    

    每个变体都略有不同。

    这个消耗Vec和它的迭代器yields values(直接T):

    impl<T> IntoIterator for Vec<T> {
        type Item = T;
        type IntoIter = IntoIter<T>;
    
        fn into_iter(mut self) -> IntoIter<T> { /* ... */ }
    }
    

    另外两个通过引用获取向量(不要被into_iter(self) 的签名所迷惑,因为self 在这两种情况下都是引用)并且它们的迭代器将产生对Vec 中元素的引用。

    这个yields immutable references

    impl<'a, T> IntoIterator for &'a Vec<T> {
        type Item = &'a T;
        type IntoIter = slice::Iter<'a, T>;
    
        fn into_iter(self) -> slice::Iter<'a, T> { /* ... */ }
    }
    

    而这个yields mutable references:

    impl<'a, T> IntoIterator for &'a mut Vec<T> {
        type Item = &'a mut T;
        type IntoIter = slice::IterMut<'a, T>;
    
        fn into_iter(self) -> slice::IterMut<'a, T> { /* ... */ }
    }
    

    所以:

    iterinto_iter 有什么区别?

    into_iter 是获取迭代器的通用方法,无论此迭代器产生值、不可变引用还是可变引用取决于上下文,有时可能会令人惊讶。

    iteriter_mut 是临时方法。因此,它们的返回类型独立于上下文,并且通常是迭代器,分别产生不可变引用和可变引用。

    Rust by Example 帖子的作者说明了对调用 into_iter 的上下文(即类型)的依赖性带来的惊喜,并且还通过使用以下事实使问题复杂化:

    1. IntoIterator 不适用于 [T; N],仅适用于 &amp;[T; N]&amp;mut [T; N] -- 它将适用于 Rust 2021。
    2. 当没有为某个值实现方法时,它会自动搜索该值的引用

    这对于into_iter 来说非常令人惊讶,因为所有类型([T; N] 除外)都为所有 3 个变体(值和引用)实现了它。

    数组实现IntoIterator(以如此令人惊讶的方式)使得可以在for循环中迭代对它们的引用。

    从 Rust 1.51 开始,数组可以实现产生值的迭代器(通过 array::IntoIter),但 IntoIterator 的现有实现会自动引用 makes it hard to implement by-value iteration via IntoIterator

    【讨论】:

    • 我发现这篇博文很有帮助:hermanradtke.com/2015/06/22/…
    • >这个迭代器是否产生值、不可变引用或可变引用取决于上下文它是什么意思以及如何处理?例如,如何强制 iter_mut 产生可变值?
    • @DanM.: (1) 这意味着into_iter 根据接收者是值、引用还是可变引用来选择实现。 (2) Rust 中没有可变值,或者更确切地说,任何值都是可变的,因为您拥有所有权。
    • @Ixx:谢谢,这非常有用。我决定在问题的顶部提供一个 TL;DR 以避免将答案埋在中间,你怎么看?
    • 对于实现IntoIter的数组,另见github.com/rust-lang/rust/issues/65798
    【解决方案2】:

    我(Rust 新手)从 Google 来到这里,寻求其他答案未提供的简单答案。这是一个简单的答案:

    • iter() 通过引用迭代项目
    • iter_mut() 迭代项目,为每个项目提供可变引用
    • into_iter() 迭代项目,将它们移动到新范围内

    所以for x in my_vec { ... } 本质上等同于my_vec.into_iter().for_each(|x| ... ) - movemy_vec 的元素都进入了... 范围。

    如果您只需要“查看”数据,请使用iter,如果您需要编辑/修改它,请使用iter_mut,如果您需要为其赋予新所有者,请使用into_iter

    这很有帮助:http://hermanradtke.com/2015/06/22/effectively-using-iterators-in-rust.html

    将此作为社区 wiki,希望 Rust 专业人士可以在我犯任何错误时编辑此答案。

    【讨论】:

    • 谢谢...很难看出接受的答案如何明确区分 iterinto_iter
    • 这正是我想要的!
    • 对于my_vec 或您所称的.into_iter() 实际上是Vec&lt;T&gt; 的(常见?)情况,这是一个很好的简单答案。其他答案更复杂,还提到,如果您在其他事物上调用 .into_iter(),例如 &amp;Vec&lt;T&gt;&amp;[](或等效地,尝试在 for ... in ... 循环中迭代它们),您可能不会实际上是将项目移动到新范围内。 (至少,这是我目前的理解......)
    • 是的,“into_iter() 迭代项目,将它们移动到新范围”并不总是准确的 - 有关更多详细信息,请参阅接受的答案。
    • 我希望我能多次为这个答案投票。
    【解决方案3】:

    我认为还有一些需要澄清的地方。集合类型,例如Vec&lt;T&gt;VecDeque&lt;T&gt;,具有产生Tinto_iter 方法,因为它们实现了IntoIterator&lt;Item=T&gt;。没有什么可以阻止我们创建一个类型Foo&lt;T&gt;,如果它被迭代,它将产生不是T,而是另一个类型U。即Foo&lt;T&gt;实现IntoIterator&lt;Item=U&gt;

    其实std中也有一些例子:&amp;PathimplementsIntoIterator&lt;Item=&amp;OsStr&gt;&amp;UnixListenerimplementsIntoIterator&lt;Item=Result&lt;UnixStream&gt;&gt;


    into_iteriter的区别

    回到关于into_iteriter 之间区别的原始问题。与其他人指出的类似,不同之处在于into_iterIntoIterator 的必需方法,它可以产生IntoIterator::Item 中指定的任何类型。通常,如果一个类型实现了IntoIterator&lt;Item=I&gt;,按照惯例,它还有两个临时方法:iteriter_mut,它们分别产生&amp;I&amp;mut I

    这意味着我们可以创建一个函数来接收具有into_iter 方法的类型(即它是可迭代的),方法是使用 trait bound:

    fn process_iterable<I: IntoIterator>(iterable: I) {
        for item in iterable {
            // ...
        }
    }
    

    但是,我们不能*使用一个特征绑定来要求一个类型具有iter 方法或iter_mut 方法,因为它们只是约定。我们可以说into_iteriteriter_mut 的使用范围更广。

    iteriter_mut 的替代方案

    要观察的另一件有趣的事情是iter 并不是获得产生&amp;T 的迭代器的唯一方法。按照惯例(再次),std 中的集合类型 SomeCollection&lt;T&gt; 具有 iter 方法也有它们的不可变引用类型 &amp;SomeCollection&lt;T&gt; 实现 IntoIterator&lt;Item=&amp;T&gt;。比如&amp;Vec&lt;T&gt;implementsIntoIterator&lt;Item=&amp;T&gt;,所以我们可以遍历&amp;Vec&lt;T&gt;

    let v = vec![1, 2];
    
    // Below is equivalent to: `for item in v.iter() {`
    for item in &v {
        println!("{}", item);
    }
    

    如果v.iter() 等价于&amp;v 两者都实现了IntoIterator&lt;Item=&amp;T&gt;,那么为什么Rust 两者都提供呢?是为了人体工学。在for 循环中,使用&amp;v 比使用v.iter() 更简洁一些;但在其他情况下,v.iter()(&amp;v).into_iter() 清晰得多:

    let v = vec![1, 2];
    
    let a: Vec<i32> = v.iter().map(|x| x * x).collect();
    // Although above and below are equivalent, above is a lot clearer than below.
    let b: Vec<i32> = (&v).into_iter().map(|x| x * x).collect();
    

    同样,在for 循环中,v.iter_mut() 可以替换为&amp;mut v

    let mut v = vec![1, 2];
    
    // Below is equivalent to: `for item in v.iter_mut() {`
    for item in &mut v {
        *item *= 2;
    }
    

    何时为类型提供(实现)into_iteriter 方法

    如果该类型只有一种“方式”可以迭代,我们应该同时实现这两种方式。但是,如果有两种或多种方式可以迭代,我们应该为每种方式提供一个 ad-hoc 方法。

    例如,String 既不提供 into_iter 也不提供 iter,因为有两种方法可以对其进行迭代:以字节为单位迭代其表示或以字符为单位对其表示进行迭代。相反,它提供了两种方法:bytes 用于迭代字节和chars 用于迭代字符,作为iter 方法的替代方法。


    * 好吧,从技术上讲,我们可以通过创建特征来做到这一点。但是我们需要为我们想要使用的每种类型 impl 那个特征。同时std中的很多类型已经实现了IntoIterator

    【讨论】:

      【解决方案4】:

      .into_iter() 不是针对数组本身实现的,而只是针对&amp;[]。比较:

      impl<'a, T> IntoIterator for &'a [T]
          type Item = &'a T
      

      impl<T> IntoIterator for Vec<T>
          type Item = T
      

      由于IntoIterator 仅在&amp;[T] 上定义,因此当您使用这些值时,不能像Vec 那样删除切片本身。 (值不能移出)

      现在,为什么会这样是一个不同的问题,我想了解自己。推测:数组是数据本身,切片只是其中的一个视图。实际上,您不能将数组作为值移动到另一个函数中,只需传递它的视图,因此您也不能在那里使用它。

      【讨论】:

      • IntoIterator 也为&amp;'a mut [T] 实现,因此它可以将对象移出数组。我认为这与返回结构IntoIter&lt;T&gt; 没有生命周期参数而Iter&lt;'a, T&gt; 没有生命周期参数有关,所以前者不能保存切片。
      • mut 表示您可以更改这些值,而不是您可以将它们移出。
      • @rodrigo let mut a = ["abc".to_string()]; a.into_iter().map(|x| { *x }); => “错误:无法移出借来的内容”
      • 是的,我认为你是对的,值不能从数组中移动。但是,我仍然认为应该可以使用不安全的 Rust 实现一种 ArrayIntoIter 结构,作为库的一部分......也许不值得,因为无论如何你应该使用 Vec 来处理这些情况。跨度>
      • 所以一个接一个:是的,into_iter 返回&amp;T,因为你真的在调用&amp;array.into_iter()。这并不是真正的神奇 - 它会自动发生在所有方法中(需要 & 将自动添加)。无法移动数组只是一个稍微相关的旁注;它可能会也可能不会解释答案。移动该值意味着它在以前的位置不再有效,而是出现在新位置。例如,vec2 会发生这种情况。在这段代码的最后,你可以访问vec1[0],但不能访问vec2[0]
      猜你喜欢
      • 2015-11-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多