Apa perbedaan antara iter dan into_iter?

174

Saya melakukan tutorial Rust by Example yang memiliki cuplikan kode ini:

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

Saya benar-benar bingung - untuk Veciterator yang dikembalikan dari iterreferensi hasil dan iterator kembali dari into_iternilai hasil, tetapi untuk array iterator ini identik?

Apa use case / API untuk kedua metode ini?

vital
sumber

Jawaban:

146

TL; DR:

  • Iterator yang dikembalikan oleh into_iterdapat menghasilkan T, &Tatau &mut Ttergantung pada konteksnya.
  • Iterator yang dikembalikan oleh iterakan menghasilkan &T, dengan konvensi.
  • Iterator yang dikembalikan oleh iter_mutakan menghasilkan &mut T, dengan konvensi.

Pertanyaan pertama adalah: "Apa itu into_iter?"

into_iterberasal dari IntoIteratorsifat :

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

Anda menerapkan sifat ini ketika Anda ingin menentukan bagaimana tipe tertentu akan dikonversi menjadi iterator. Terutama, jika suatu tipe mengimplementasikannya IntoIteratordapat digunakan dalam satu forlingkaran.

Misalnya, Vecmenerapkan IntoIterator... tiga kali!

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

Setiap varian sedikit berbeda.

Ini mengkonsumsi Vecdan iteratornya menghasilkan nilai ( Tlangsung):

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

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

Dua lainnya mengambil vektor dengan referensi (jangan tertipu oleh tanda tangan into_iter(self)karena selfmerupakan referensi dalam kedua kasus) dan iterator mereka akan menghasilkan referensi ke elemen di dalamnya Vec.

Yang ini menghasilkan referensi abadi :

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

Sementara ini menghasilkan referensi yang bisa berubah :

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

Begitu:

Apa perbedaan antara iterdan into_iter?

into_iteradalah metode generik untuk mendapatkan iterator, apakah iterator ini menghasilkan nilai, referensi yang tidak dapat diubah atau referensi yang dapat diubah tergantung pada konteks dan terkadang mengejutkan.

iterdan iter_mutmerupakan metode ad-hoc. Jenis pengembalian mereka karena itu independen dari konteks, dan secara konvensional akan menjadi iterator yang menghasilkan referensi yang tidak berubah dan referensi yang dapat berubah, masing-masing.

Penulis tulisan Rust by Example mengilustrasikan kejutan yang datang dari ketergantungan pada konteks (yaitu, tipe) yang into_iterdipanggil, dan juga menambah masalah dengan menggunakan fakta bahwa:

  1. IntoIteratortidak diterapkan untuk [T; N], hanya untuk &[T; N]dan&mut [T; N]
  2. Ketika suatu metode tidak diterapkan untuk suatu nilai, itu secara otomatis mencari referensi ke nilai itu sebagai gantinya

yang sangat mengejutkan into_iterkarena semua tipe (kecuali [T; N]) mengimplementasikannya untuk ketiga variasi (nilai dan referensi). Array tidak mungkin untuk mengimplementasikan iterator yang menghasilkan nilai karena ia tidak dapat "menyusut" untuk menyerahkan item-itemnya.

Seperti mengapa array mengimplementasikan IntoIterator(dengan cara yang mengejutkan): itu memungkinkan untuk beralih pada referensi ke mereka dalam forloop.

Matthieu M.
sumber
14
Saya menemukan posting blog ini bermanfaat: hermanradtke.com/2015/06/22/...
poy
> apakah iterator ini menghasilkan nilai, referensi yang tidak dapat diubah atau referensi yang bisa berubah tergantung pada konteks Apa artinya dan bagaimana cara mengatasinya? Bagaimana seseorang memaksa iter_mut untuk menghasilkan nilai yang bisa berubah, misalnya?
Dan M.
@ DanM .: (1) Ini berarti bahwa into_itermemilih implementasi berdasarkan pada apakah penerima adalah nilai, referensi atau referensi yang bisa berubah. (2) Tidak ada nilai yang bisa berubah di Rust, atau lebih tepatnya, nilai apa pun bisa berubah karena Anda memiliki kepemilikan.
Matthieu M.
@ MatthieuM.hm, sepertinya tidak demikian dalam tes saya. Saya telah menerapkan IntoIter untuk &'a MyStructdan &mut 'a MyStructdan yang pertama selalu dipilih jika ada bahkan jika aku menelepon into_iter().for_each()pada mutnilai dengan &mutargumen di lambda.
Dan M.
1
@Ixx: Terima kasih itu sangat berguna. Saya memutuskan untuk memberikan TL; DR di bagian atas pertanyaan untuk menghindari mengubur jawaban di tengah, bagaimana menurut Anda?
Matthieu M.
77

Saya (seorang pemula Rust) datang ke sini dari Google mencari jawaban sederhana yang tidak disediakan oleh jawaban lain. Inilah jawaban sederhana itu:

  • iter() beralih pada item dengan referensi
  • into_iter() beralih ke item, memindahkannya ke ruang lingkup baru
  • iter_mut() beralih pada item, memberikan referensi yang bisa berubah untuk setiap item

Jadi for x in my_vec { ... }pada dasarnya setara dengan my_vec.into_iter().for_each(|x| ... )- kedua moveelemen yang my_vecmasuk ke dalam ...ruang lingkup.

Jika Anda hanya perlu "melihat" data, gunakan iter, jika Anda perlu mengedit / mengubahnya, gunakan iter_mut, dan jika Anda perlu memberikannya kepada pemilik baru, gunakan into_iter.

Ini sangat membantu: http://hermanradtke.com/2015/06/22/effectively-using-iterators-in-rust.html

Jadikan ini sebagai wiki komunitas sehingga semoga pro karat dapat mengedit jawaban ini jika saya membuat kesalahan.

joe
sumber
7
Terima kasih ... Sulit untuk melihat bagaimana jawaban yang diterima mengartikulasikan perbedaan antara iterdan into_iter.
mmw
Itulah tepatnya yang saya cari!
Cyrusmith
6

.into_iter()tidak diimplementasikan untuk array itu sendiri, tetapi hanya &[]. Membandingkan:

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

dengan

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

Karena IntoIteratordidefinisikan hanya pada &[T], irisan itu sendiri tidak dapat dijatuhkan dengan cara yang sama seperti Vecketika Anda menggunakan nilai-nilai. (nilai tidak dapat dipindahkan)

Sekarang, mengapa itu menjadi masalah yang berbeda, dan saya ingin belajar sendiri. Berspekulasi: array adalah data itu sendiri, slice hanya pandangan ke dalamnya. Dalam praktiknya Anda tidak bisa memindahkan array sebagai nilai ke fungsi lain, cukup lewati saja, jadi Anda juga tidak bisa mengkonsumsinya.

viraptor
sumber
IntoIteratorjuga diimplementasikan untuk &'a mut [T], sehingga bisa memindahkan objek dari array. Saya pikir itu terkait dengan fakta bahwa struct kembali IntoIter<T>tidak memiliki argumen seumur hidup Iter<'a, T>, jadi yang pertama tidak dapat memegang sepotong.
rodrigo
mutberarti Anda dapat mengubah nilai, bukan berarti Anda bisa memindahkannya.
viraptor
@rodrigo let mut a = ["abc".to_string()]; a.into_iter().map(|x| { *x });=> "error: tidak bisa keluar dari konten yang dipinjam"
viraptor
Ya, saya pikir Anda benar dan nilai tidak dapat dipindahkan dari array. Namun, saya masih berpikir bahwa itu mungkin untuk mengimplementasikan semacam ArrayIntoIterstruct menggunakan Rust yang tidak aman, sebagai bagian dari perpustakaan ... Mungkin tidak sepadan, seperti yang seharusnya Anda gunakan Vecuntuk kasus-kasus itu.
rodrigo
jadi saya tidak mengerti ... adalah alasan yang array.into_itermengembalikan &T- karena melakukan sulap untuk mengubahnya secara otomatis &array.into_iter- dan jika demikian, saya tidak mengerti apa yang harus dilakukan dengan nilai bergerak atau tidak nilai bergerak. Atau apakah seperti yang dikatakan @rodrigo, bahwa Anda mendapatkan referensi hanya karena (karena alasan tertentu) Anda tidak dapat memindahkan nilai dari array ? Masih sangat bingung.
vitiral
2

Saya pikir ada sesuatu yang perlu diklarifikasi sedikit lagi. Jenis koleksi, seperti Vec<T>dan VecDeque<T>, memiliki into_itermetode yang menghasilkan Tkarena mereka menerapkan IntoIterator<Item=T>. Tidak ada yang menghentikan kita untuk membuat tipe Foo<T>jika yang diulangi, itu akan menghasilkan bukan Ttipe lain U. Yaitu, Foo<T>implementasinya IntoIterator<Item=U>.

Bahkan, ada beberapa contoh di std: &Path alat IntoIterator<Item=&OsStr> dan &UnixListener alat IntoIterator<Item=Result<UnixStream>> .


Perbedaan antara into_iterdaniter

Kembali ke pertanyaan awal tentang perbedaan antara into_iterdan iter. Serupa dengan yang ditunjukkan orang lain, perbedaannya adalah into_itermetode yang diperlukan IntoIteratoryang dapat menghasilkan jenis apa pun yang ditentukan IntoIterator::Item. Biasanya, jika suatu tipe mengimplementasikan IntoIterator<Item=I>, dengan konvensi ia juga memiliki dua metode ad-hoc: iterdan iter_mutyang menghasilkan &Idan &mut I, masing-masing.

Apa yang tersirat adalah bahwa kita dapat membuat fungsi yang menerima tipe yang memiliki into_itermetode (yaitu iterable) dengan menggunakan sifat terikat:

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

Namun, kita tidak dapat * menggunakan suatu sifat yang terikat untuk meminta suatu tipe untuk memiliki itermetode atau iter_mutmetode, karena itu hanya konvensi. Kita dapat mengatakan bahwa into_iteritu lebih banyak digunakan daripada iteratau iter_mut.

Alternatif untuk iterdaniter_mut

Yang menarik untuk diamati adalah bahwa iteritu bukan satu-satunya cara untuk mendapatkan iterator yang menghasilkan &T. Dengan konvensi (lagi), jenis koleksi SomeCollection<T>di stdmana memiliki itermetode juga memiliki referensi jenis berubah mereka &SomeCollection<T>melaksanakan IntoIterator<Item=&T>. Misalnya, &Vec<T> mengimplementasikan IntoIterator<Item=&T> , sehingga memungkinkan kita untuk beralih &Vec<T>:

let v = vec![1, 2];

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

Jika v.iter()setara dengan &vkedua implementasi tersebut IntoIterator<Item=&T>, mengapa Rust menyediakan keduanya? Ini untuk ergonomi. Dalam forloop, ini sedikit lebih ringkas untuk digunakan &vdaripada v.iter(); tetapi dalam kasus lain, v.iter()jauh lebih jelas daripada (&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();

Demikian pula, dalam forloop, v.iter_mut()dapat diganti dengan &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;
}

Kapan harus menyediakan (mengimplementasikan) into_iterdan itermetode untuk suatu jenis

Jika tipe hanya memiliki satu "cara" untuk diulangi, kita harus mengimplementasikan keduanya. Namun, jika ada dua cara atau lebih yang dapat diulangi, kita harus menyediakan metode ad-hoc untuk setiap cara.

Misalnya, Stringtidak menyediakan into_iteratau iterkarena ada dua cara untuk mengulanginya: untuk mengulangi perwakilannya dalam byte atau untuk mengulangi perwakilannya dalam karakter. Alih-alih, ia menyediakan dua metode: bytesuntuk iterasi byte dan charsuntuk iterasi karakter, sebagai alternatif itermetode.


* Yah, secara teknis kita bisa melakukannya dengan menciptakan suatu sifat. Tetapi kemudian kita perlu implsifat itu untuk setiap jenis yang ingin kita gunakan. Sementara itu, banyak tipe yang stdsudah diimplementasikan IntoIterator.

Daniel
sumber