Qual é a diferença entre iter e into_iter?

174

Estou fazendo o tutorial Rust by Example que possui esse trecho de código:

// 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));

Estou completamente confuso - para um Veciterador retornado de iterreferências de rendimentos e o iterador retornado de into_itervalores de rendimentos, mas para uma matriz esses iteradores são idênticos?

Qual é o caso de uso / API para esses dois métodos?

vitiral
fonte

Respostas:

146

TL; DR:

  • A iteração retornado por into_iterpodem produzir qualquer um de T, &Tou &mut T, dependendo do contexto.
  • O iterador retornado por iterproduzirá &T, por convenção.
  • O iterador retornado por iter_mutproduzirá &mut T, por convenção.

A primeira pergunta é: "O que é into_iter?"

into_itervem da IntoIteratorcaracterística :

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

Você implementa essa característica quando deseja especificar como um tipo específico deve ser convertido em um iterador. Mais notavelmente, se um tipo implementa, IntoIteratorele pode ser usado em um forloop.

Por exemplo, Vecimplementa IntoIterator... três vezes!

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

Cada variante é um pouco diferente.

Este consome Vece seu iterador gera valores ( Tdiretamente):

impl<T> IntoIterator for Vec<T> {
    type Item = T;
    type IntoIter = IntoIter<T>;

    fn into_iter(mut self) -> IntoIter<T> { /* ... */ }
}

Os outros dois tomam o vetor por referência (não se deixe enganar pela assinatura de into_iter(self)porque selfé uma referência nos dois casos) e seus iteradores produzirão referências aos elementos internos Vec.

Este produz referências imutáveis :

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> { /* ... */ }
}

Enquanto este produz referências mutáveis :

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> { /* ... */ }
}

Assim:

Qual é a diferença entre itere into_iter?

into_iteré um método genérico para obter um iterador, se esse iterador gera valores, referências imutáveis ​​ou referências mutáveis depende do contexto e às vezes pode ser surpreendente.

itere iter_mutsão métodos ad-hoc. Seu tipo de retorno é, portanto, independente do contexto e será convencionalmente iteradores que produzem referências imutáveis ​​e referências mutáveis, respectivamente.

O autor do post Rust by Example ilustra a surpresa que vem da dependência do contexto (ou seja, o tipo) no qual into_iteré chamado e também está agravando o problema usando o fato de que:

  1. IntoIteratornão é implementado para [T; N], apenas para &[T; N]e&mut [T; N]
  2. Quando um método não é implementado para um valor, ele é automaticamente procurado por referências a esse valor.

o que é muito surpreendente, into_iterpois todos os tipos (exceto [T; N]) o implementam nas três variações (valor e referências). Não é possível para a matriz implementar um iterador que gera valores porque não pode "encolher" para abrir mão de seus itens.

Por que as matrizes são implementadas IntoIterator(de maneira tão surpreendente): é para possibilitar a iteração das referências a elas em forloops.

Matthieu M.
fonte
14
Eu encontrei esta publicação no blog útil: hermanradtke.com/2015/06/22/…
poy
> se esse iterador gera valores, referências imutáveis ​​ou referências mutáveis ​​depende do contexto O que significa e como lidar com isso? Como forçar o iter_mut a produzir valores mutáveis, por exemplo?
Dan M.
@DanM .: (1) Significa que into_iterescolhe uma implementação com base no fato de o receptor ser um valor, referência ou referência mutável. (2) Não há valores mutáveis ​​em Rust, ou melhor, qualquer valor é mutável, pois você é o proprietário.
Matthieu M.
@ MatthieuM.hm, isso não parece ser o caso nos meus testes. Eu tenho implementado IntoIter para &'a MyStructe &mut 'a MyStructe o primeiro foi escolhido sempre se presente mesmo se eu chamada into_iter().for_each()em mutvalor, com &mutargumentos em lambda.
Dan M.
1
@ Ixx: Obrigado, isso é muito útil. Decidi fornecer um TL; DR no topo da pergunta para evitar enterrar a resposta no meio, o que você acha?
Matthieu M.
77

Eu (um novato da Rust) vim aqui do Google buscando uma resposta simples, que não foi fornecida pelas outras respostas. Aqui está essa resposta simples:

  • iter() itera sobre os itens por referência
  • into_iter() itera sobre os itens, movendo-os para o novo escopo
  • iter_mut() itera sobre os itens, fornecendo uma referência mutável para cada item

Então for x in my_vec { ... }é essencialmente equivalente a my_vec.into_iter().for_each(|x| ... )- ambos moveos elementos my_vecpara o ...escopo.

Se você apenas precisar "examinar" os dados, use iter, se precisar editá-los / modificá-los, use iter_mute, se precisar dar a um novo proprietário, use into_iter.

Isso foi útil: http://hermanradtke.com/2015/06/22/effectively-using-iterators-in-rust.html

Tornando este um wiki da comunidade para que, com sorte, um profissional do Rust possa editar essa resposta se eu cometer algum erro.

Joe
fonte
7
Obrigado ... É difícil ver como a resposta aceita articula uma distinção entre itere into_iter.
mmw 6/01
Era exatamente o que eu estava procurando!
Cyrusmith
6

.into_iter()não é implementado para uma matriz em si, mas apenas &[]. Comparar:

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

com

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

Como IntoIteratorestá definido apenas em &[T], a fatia em si não pode ser descartada da mesma maneira que Vecquando você usa os valores. (os valores não podem ser removidos)

Agora, por que esse é o caso é uma questão diferente, e eu gostaria de aprender. Especulação: array são os dados em si, fatia é apenas uma visão dele. Na prática, você não pode mover a matriz como um valor para outra função, basta passar uma visão dela, para que você também não possa consumi-la.

viraptor
fonte
IntoIteratortambém é implementado para &'a mut [T], para que ele possa mover os objetos para fora da matriz. Eu acho que isso está relacionado ao fato de que a estrutura de retorno IntoIter<T>não tem um argumento vitalício enquanto o Iter<'a, T>possui, portanto o primeiro não pode conter uma fatia.
Rodrigo
mutsignifica que você pode alterar os valores, não que você pode movê-los.
viraptor 12/01
@rodrigo let mut a = ["abc".to_string()]; a.into_iter().map(|x| { *x });=> "erro: não é possível sair do conteúdo emprestado"
viraptor
Sim, acho que você está certo e os valores não podem ser movidos da matriz. No entanto, ainda acho que deve ser possível implementar um tipo de ArrayIntoIterestrutura usando o Rust inseguro, como parte da biblioteca ... Talvez não valha a pena, pois você deve usar Vecnesses casos de qualquer maneira.
Rodrigo
então eu não entendo ... é esse o motivo que array.into_iterretorna &T- porque está fazendo mágica para convertê-lo automaticamente em &array.into_iter- e, nesse caso, não entendo o que isso tem a ver com valores em movimento ou não em valores. Ou é como @rodrigo disse, que você obtém a referência simplesmente porque (por algum motivo) você não pode mover valores das matrizes ? Ainda muito confuso.
vitiral
2

Eu acho que há algo para esclarecer um pouco mais. Tipos de coleção, como Vec<T>e VecDeque<T>, têm um into_itermétodo que gera Tporque eles implementam IntoIterator<Item=T>. Não há nada para nos impedir de criar um tipo, Foo<T>se for iterado, ele produzirá não Tapenas outro tipo U. Ou seja, Foo<T>implementa IntoIterator<Item=U>.

De fato, existem alguns exemplos em std: &Path implementos IntoIterator<Item=&OsStr> e &UnixListener implementos IntoIterator<Item=Result<UnixStream>> .


A diferença entre into_itereiter

Voltar à pergunta original sobre a diferença entre into_itere iter. Semelhante ao que outros já apontaram, a diferença é que into_iteré um método exigido IntoIteratorque pode gerar qualquer tipo especificado em IntoIterator::Item. Normalmente, se um tipo é implementado IntoIterator<Item=I>, por convenção, ele também possui dois métodos ad-hoc: itere iter_mutque produzem &Ie &mut I, respectivamente.

O que isso implica é que podemos criar uma função que recebe um tipo que possui into_itermétodo (ou seja, é iterável) usando um atributo vinculado:

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

No entanto, não podemos * usar uma característica vinculada para exigir que um tipo tenha itermétodo ou iter_mutmétodo, porque são apenas convenções. Podemos dizer que into_iteré mais amplamente utilizável que iterou iter_mut.

Alternativas itereiter_mut

Outro interessante a observar é que iternão é a única maneira de obter um iterador que produza &T. Por convenção (novamente), os tipos de coleção SomeCollection<T>nos stdquais o itermétodo também tem seus tipos de referência imutáveis &SomeCollection<T>implementados IntoIterator<Item=&T>. Por exemplo, &Vec<T> implementa IntoIterator<Item=&T> , portanto, permite iterar sobre &Vec<T>:

let v = vec![1, 2];

// Below is equivalent to: `for item in v.iter() {`
for item in &v {
    println!("{}", item);
}

Se v.iter()é equivalente a &vque ambos implementam IntoIterator<Item=&T>, por que então Rust fornece ambos? É para ergonomia. Em forloops, é um pouco mais conciso do &vque v.iter(); mas em outros casos, v.iter()é muito mais claro que (&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();

Da mesma forma, em forloops, v.iter_mut()pode ser substituído por &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;
}

Quando fornecer (implementar) into_itere itermétodos para um tipo

Se o tipo tiver apenas uma "maneira" de ser repetida, devemos implementar as duas. No entanto, se há duas maneiras ou mais para iterar, devemos fornecer um método ad-hoc para cada maneira.

Por exemplo, Stringfornece nem into_iternem iterporque existem duas maneiras de iterá-lo: iterar sua representação em bytes ou iterar sua representação em caracteres. Em vez disso, fornece dois métodos: bytespara iterar os bytes e charspara iterar os caracteres, como alternativas ao itermétodo.


* Bem, tecnicamente, podemos fazer isso criando uma característica. Mas então precisamos impldessa característica para cada tipo que queremos usar. Enquanto isso, muitos tipos stdjá implementam IntoIterator.

Daniel
fonte