Sự khác biệt giữa iter và in_iter là gì?


174

Tôi đang thực hiện hướng dẫn Rust by example có đoạn mã này:

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

Tôi hoàn toàn bối rối - đối với một Vectrình vòng lặp được trả về từ các itertham chiếu năng suất và trình vòng lặp được trả về từ into_itercác giá trị năng suất, nhưng đối với một mảng thì các trình vòng lặp này có giống nhau không?

Ca sử dụng / API cho hai phương thức này là gì?

Câu trả lời:


146

TL; DR:

  • Trình lặp được trả về bởi into_itercó thể mang lại bất kỳ T, &Thoặc &mut T, tùy thuộc vào ngữ cảnh.
  • Lặp đi lặp lại bởi itersẽ mang lại &T, theo quy ước.
  • Lặp đi lặp lại bởi iter_mutsẽ mang lại &mut T, theo quy ước.

Câu hỏi đầu tiên là: "Cái gì into_iter?"

into_iterxuất phát từ IntoIteratorđặc điểm :

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

Bạn thực hiện đặc điểm này khi bạn muốn chỉ định cách thức một loại cụ thể được chuyển đổi thành một trình vòng lặp. Đáng chú ý nhất, nếu một loại thực hiện IntoIteratornó có thể được sử dụng trong một forvòng lặp.

Ví dụ, Vecthực hiện IntoIterator... ba lần!

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

Mỗi biến thể là một chút khác nhau.

Cái này tiêu thụ Vecvà iterator của nó mang lại các giá trị ( Ttrực tiếp):

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

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

Hai cái còn lại lấy vectơ theo tham chiếu (không bị đánh lừa bởi chữ ký into_iter(self)bởi vì selflà tham chiếu trong cả hai trường hợp) và các trình lặp của chúng sẽ tạo ra các tham chiếu đến các phần tử bên trong Vec.

Điều này mang lại tài liệu tham khảo bất biến :

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

Trong khi điều này mang lại các tài liệu tham khảo có thể thay đổi :

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

Vì thế:

Sự khác biệt giữa itervà là into_itergì?

into_iterlà một phương pháp chung để có được một trình vòng lặp, cho dù trình lặp này mang lại các giá trị, các tham chiếu không thay đổi hoặc các tham chiếu có thể thay đổi là phụ thuộc vào ngữ cảnh và đôi khi có thể gây ngạc nhiên.

iteriter_mutlà phương pháp đặc biệt. Do đó, kiểu trả về của chúng là độc lập với bối cảnh và theo quy ước sẽ là các trình vòng lặp mang lại các tham chiếu bất biến và các tham chiếu có thể thay đổi tương ứng.

Tác giả của bài viết Rust by Ví dụ minh họa sự bất ngờ đến từ sự phụ thuộc vào bối cảnh (tức là loại) into_iterđược gọi và cũng gây ra vấn đề bằng cách sử dụng thực tế rằng:

  1. IntoIteratorkhông được thực hiện cho [T; N], chỉ cho &[T; N]&mut [T; N]
  2. Khi một phương thức không được triển khai cho một giá trị, nó sẽ tự động được tìm kiếm để tham chiếu đến giá trị đó

điều này rất đáng ngạc nhiên into_itervì tất cả các loại (ngoại trừ [T; N]) triển khai nó cho cả 3 biến thể (giá trị và tham chiếu). Mảng không thể thực hiện một trình vòng lặp mang lại các giá trị vì nó không thể "thu nhỏ" để từ bỏ các mục của nó.

Về lý do tại sao các mảng thực hiện IntoIterator(theo cách đáng ngạc nhiên như vậy): đó là để làm cho nó có thể lặp lại các tham chiếu đến chúng trong forcác vòng lặp.


14
Tôi thấy bài đăng trên blog này hữu ích: hermanradtke.com/2015/06/22/ Khăn
poy

> liệu trình lặp này mang lại các giá trị, các tham chiếu bất biến hoặc các tham chiếu có thể thay đổi là phụ thuộc vào ngữ cảnh Nó có nghĩa gì và làm thế nào để đối phó với điều đó? Làm thế nào một người sẽ buộc iter_mut mang lại các giá trị có thể thay đổi, ví dụ?
Dan M.

@DanM.: (1) Điều đó có nghĩa là into_iterchọn một triển khai dựa trên việc người nhận là một tham chiếu giá trị, tham chiếu hoặc có thể thay đổi. (2) Không có giá trị có thể thay đổi trong Rust, hay đúng hơn, bất kỳ giá trị nào cũng có thể thay đổi do bạn có quyền sở hữu.
Matthieu M.

@ MatthieuM.hm, dường như đó không phải là trường hợp trong các thử nghiệm của tôi. Tôi đã thực hiện IntoIter cho &'a MyStruct&mut 'a MyStructvà là người đầu tiên đã luôn luôn được lựa chọn nếu có ngay cả khi tôi gọi into_iter().for_each()về mutgiá trị với &mutđối số trong lambda.
Dan M.

1
@Ixx: Cảm ơn điều đó rất hữu ích. Tôi quyết định cung cấp một TL; DR ở đầu câu hỏi để tránh chôn câu trả lời ở giữa, bạn nghĩ sao?
Matthieu M.

77

Tôi (một người mới chơi Rust) đến đây từ Google để tìm kiếm một câu trả lời đơn giản không được cung cấp bởi các câu trả lời khác. Đây là câu trả lời đơn giản:

  • iter() lặp đi lặp lại qua các mục bằng cách tham khảo
  • into_iter() Lặp lại các mục, di chuyển chúng vào phạm vi mới
  • iter_mut() lặp đi lặp lại qua các mục, đưa ra một tham chiếu có thể thay đổi cho từng mục

Vì vậy, for x in my_vec { ... }về cơ bản là tương đương với my_vec.into_iter().for_each(|x| ... )- cả hai moveyếu tố my_vecvào ...phạm vi.

Nếu bạn chỉ cần "xem" dữ liệu, hãy sử dụng iter, nếu bạn cần chỉnh sửa / thay đổi dữ liệu, hãy sử dụng iter_mutvà nếu bạn cần cung cấp cho chủ sở hữu mới, hãy sử dụng into_iter.

Điều này rất hữu ích: http://hermanradtke.com/2015/06/22/effectively-USE-iterators-in-rust.html

Biến đây thành wiki cộng đồng để hy vọng Rust pro có thể chỉnh sửa câu trả lời này nếu tôi mắc lỗi.


7
Cảm ơn bạn ... Thật khó để thấy câu trả lời được chấp nhận cho thấy sự khác biệt giữa iterinto_iter.
mmw

Đó chính xác là những gì tôi đang tìm kiếm!
Cyrusmith

6

.into_iter()không được thực hiện cho một mảng, nhưng chỉ &[]. Đối chiếu:

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

với

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

IntoIteratorchỉ được xác định trên &[T], nên chính lát cắt không thể được loại bỏ giống như Veckhi bạn sử dụng các giá trị. (giá trị không thể được chuyển ra)

Bây giờ, tại sao trường hợp đó lại là một vấn đề khác và tôi muốn tự tìm hiểu. Suy đoán: mảng là dữ liệu của chính nó, lát chỉ là một khung nhìn vào nó. Trong thực tế, bạn không thể di chuyển mảng như một giá trị sang một hàm khác, chỉ cần chuyển một khung nhìn của nó, vì vậy bạn cũng không thể sử dụng nó ở đó.


IntoIteratorcũng được thực hiện cho &'a mut [T], vì vậy nó có thể di chuyển các đối tượng ra khỏi mảng. Tôi nghĩ rằng nó có liên quan đến thực tế là cấu trúc trả về IntoIter<T>không có đối số trọn đời trong khi Iter<'a, T>đó, vì vậy cái trước không thể giữ một lát.
Rodrigo

mutcó nghĩa là bạn có thể thay đổi các giá trị, không phải là bạn có thể di chuyển chúng ra ngoài.
viraptor

@rodrigo let mut a = ["abc".to_string()]; a.into_iter().map(|x| { *x });=> "lỗi: không thể chuyển ra khỏi nội dung mượn"
viraptor

Vâng, tôi nghĩ rằng bạn đúng và các giá trị không thể được di chuyển từ mảng. Tuy nhiên, tôi vẫn nghĩ rằng có thể thực hiện một loại ArrayIntoItercấu trúc bằng cách sử dụng Rust không an toàn, như một phần của thư viện ... Có lẽ không đáng, vì Vecdù sao bạn cũng nên sử dụng cho những trường hợp đó.
Rodrigo

vì vậy tôi không hiểu ... đó là lý do array.into_itertrả lại &T- bởi vì nó đang thực hiện phép thuật để tự động chuyển đổi nó thành &array.into_iter- và nếu vậy, tôi không hiểu điều đó phải làm gì với việc di chuyển các giá trị hoặc không di chuyển các giá trị. Hoặc là như @rodrigo đã nói, rằng bạn có được tài liệu tham khảo đơn giản vì (vì một số lý do) bạn không thể di chuyển các giá trị ra khỏi mảng ? Vẫn rất bối rối.
vitirus

2

Tôi nghĩ rằng có một cái gì đó để làm rõ hơn một chút. Các loại bộ sưu tập, chẳng hạn như Vec<T>VecDeque<T>, có into_iterphương pháp mang lại Tvì chúng thực hiện IntoIterator<Item=T>. Không có gì ngăn cản chúng tôi tạo ra một loại Foo<T>nếu được lặp đi lặp lại, nó sẽ mang lại không Tchỉ một loại khác U. Đó là, Foo<T>thực hiện IntoIterator<Item=U>.

Trong thực tế, có một số ví dụ trong std: &Path thực hiện IntoIterator<Item=&OsStr>&UnixListener thực hiện IntoIterator<Item=Result<UnixStream>> .


Sự khác biệt giữa into_iteriter

Quay lại câu hỏi ban đầu về sự khác biệt giữa into_iteriter. Tương tự như những người khác đã chỉ ra, sự khác biệt into_iterlà một phương pháp bắt buộc IntoIteratorcó thể mang lại bất kỳ loại nào được chỉ định trong IntoIterator::Item. Thông thường, nếu một loại thực hiện IntoIterator<Item=I>, theo quy ước, nó cũng có hai phương thức đặc biệt: iteriter_mutmang lại &I&mut Itương ứng.

Điều nó ngụ ý là chúng ta có thể tạo một hàm nhận một kiểu có into_iterphương thức (tức là nó có thể lặp lại) bằng cách sử dụng một đặc điểm bị ràng buộc:

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

Tuy nhiên, chúng tôi không thể * sử dụng một đặc điểm bị ràng buộc để yêu cầu một loại phải có iterphương pháp hoặc iter_mutphương pháp, bởi vì chúng chỉ là quy ước. Chúng ta có thể nói rằng nó into_iterđược sử dụng rộng rãi hơn iterhoặc iter_mut.

Các lựa chọn thay thế cho iteriter_mut

Một điều thú vị khác để quan sát là đó iterkhông phải là cách duy nhất để có được một trình vòng lặp mang lại &T. Theo quy ước (một lần nữa), các kiểu tập hợp SomeCollection<T>trong stdđó có iterphương thức cũng có các kiểu tham chiếu bất biến của chúng &SomeCollection<T>thực hiện IntoIterator<Item=&T>. Ví dụ: &Vec<T> triển khai IntoIterator<Item=&T> , vì vậy nó cho phép chúng tôi lặp lại &Vec<T>:

let v = vec![1, 2];

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

Nếu v.iter()tương đương với &vcả hai thực hiện IntoIterator<Item=&T>, tại sao Rust lại cung cấp cả hai? Đó là cho công thái học. Trong forcác vòng lặp, nó ngắn gọn hơn một chút để sử dụng &vhơn v.iter(); nhưng trong các trường hợp khác, v.iter()rõ ràng hơn nhiều so với (&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();

Tương tự, trong forcác vòng lặp, v.iter_mut()có thể được thay thế bằng &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;
}

Khi nào cần cung cấp (hiện thực) into_iteriterphương thức cho một loại

Nếu kiểu này chỉ có một cách thức mà thôi, thì chúng ta nên thực hiện cả hai. Tuy nhiên, nếu có hai cách trở lên có thể lặp đi lặp lại, thay vào đó chúng ta nên cung cấp một phương pháp đặc biệt cho mỗi cách.

Ví dụ, Stringcung cấp không into_iterphải iterbởi vì có hai cách để lặp lại nó: lặp lại biểu diễn của nó theo byte hoặc lặp lại biểu diễn của nó trong các ký tự. Thay vào đó, nó cung cấp hai phương thức: bytesđể lặp các byte và charslặp lại các ký tự, như là các iterphương thức thay thế cho phương thức.


* Vâng, về mặt kỹ thuật chúng ta có thể làm điều đó bằng cách tạo ra một đặc điểm. Nhưng sau đó chúng ta cần implđặc điểm đó cho từng loại chúng ta muốn sử dụng. Trong khi đó, nhiều loại trong stdđã thực hiện IntoIterator.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.