ความแตกต่างระหว่าง iter และ Into_iter คืออะไร


175

ฉันกำลังกวดวิชาRust by Exampleซึ่งมีข้อมูลโค้ดนี้:

// 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 สำหรับสองวิธีนี้คืออะไร

คำตอบ:


147

TL; DR:

  • iterator กลับโดยinto_iterอาจให้การใด ๆT, &Tหรือ&mut Tขึ้นอยู่กับบริบท
  • ตัววนซ้ำที่ส่งคืนโดยiterจะให้ผลตอบแทน&Tตามแบบแผน
  • ตัววนซ้ำที่ส่งคืนโดยiter_mutจะให้ผลตอบแทน&mut Tตามแบบแผน

คำถามแรกคือ: "อะไรนะinto_iter"

into_iterมาจากIntoIteratorลักษณะ :

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

คุณใช้คุณลักษณะนี้เมื่อคุณต้องการระบุว่าจะแปลงประเภทใดประเภทหนึ่งเป็นตัววนซ้ำ สิ่งที่สะดุดตาที่สุดคือถ้าเป็นประเภทที่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และ iterator ของมันให้คุณค่า ( 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มีการอ้างอิงในทั้งสองกรณี) และ iterators Vecของพวกเขาจะผลิตอ้างอิงถึงองค์ประกอบภายใน

สิ่งนี้ให้ผลอ้างอิงที่ไม่เปลี่ยนรูป :

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

ในขณะที่สิ่งนี้ให้ผลอ้างอิงที่ไม่แน่นอน :

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

ดังนั้น:

ความแตกต่างระหว่างiterและinto_iterคืออะไร?

into_iterเป็นวิธีการทั่วไปในการรับตัววนซ้ำไม่ว่าตัววนซ้ำนี้จะให้คุณค่าการอ้างอิงที่ไม่เปลี่ยนรูปหรือการอ้างอิงที่ไม่แน่นอนนั้นขึ้นอยู่กับบริบทและบางครั้งก็น่าแปลกใจ

iterและiter_mutเป็นวิธีการเฉพาะกิจ ชนิดที่ส่งคืนของพวกเขาจึงเป็นอิสระจากบริบทและจะเป็นตัววนซ้ำตามปกติซึ่งให้การอ้างอิงที่ไม่เปลี่ยนรูปแบบและการอ้างอิงที่ไม่แน่นอน

ผู้เขียนโพสต์ตัวอย่างสนิมโดยแสดงให้เห็นถึงความประหลาดใจที่มาจากการพึ่งพาบริบท (เช่นประเภท) ที่into_iterเรียกว่าและยังประกอบปัญหาโดยใช้ความจริงที่:

  1. IntoIteratorไม่ได้ดำเนินการสำหรับ[T; N]เฉพาะสำหรับ&[T; N]และ&mut [T; N]
  2. เมื่อไม่มีการใช้วิธีสำหรับค่าจะทำการค้นหาการอ้างอิงไปยังค่านั้นโดยอัตโนมัติ

ซึ่งน่าแปลกใจมากสำหรับinto_iterตั้งแต่ทุกประเภท (ยกเว้น[T; N]) ใช้มันสำหรับทั้ง 3 รูปแบบ (ค่าและการอ้างอิง) เป็นไปไม่ได้ที่อาร์เรย์จะใช้ตัววนซ้ำที่ให้ค่าเพราะมันไม่สามารถ "ลดขนาด" เพื่อให้ไอเท็มของมันหมด

สำหรับสาเหตุที่อาร์เรย์นำไปใช้IntoIterator(ในรูปแบบที่น่าประหลาดใจ): เพื่อให้สามารถทำซ้ำการอ้างอิงไปยังforลูปได้


14
ฉันพบว่าการโพสต์บล็อกนี้มีประโยชน์: hermanradtke.com/2015/06/22/…
poy

> ไม่ว่าตัววนซ้ำนี้จะให้ค่าการอ้างอิงที่ไม่เปลี่ยนรูปหรือการอ้างอิงที่ไม่แน่นอนนั้นขึ้นอยู่กับบริบทมันหมายถึงอะไรและจะจัดการกับมันอย่างไร? วิธีการอย่างใดอย่างหนึ่งจะบังคับให้ iter_mut ให้ผลค่าที่ไม่แน่นอนเช่น?
Dan M.

@DanM .: (1) หมายถึงการinto_iterเลือกการนำไปปฏิบัติโดยพิจารณาว่าผู้รับเป็นค่าการอ้างอิงหรือการอ้างอิงที่ไม่แน่นอน (2) ไม่มีค่าที่ไม่แน่นอนใน Rust หรือมากกว่าค่าใด ๆ ที่ไม่แน่นอนเนื่องจากคุณเป็นเจ้าของ
Matthieu M.

@ MatthieuM.hm ดูเหมือนจะไม่เป็นอย่างนั้นในการทดสอบของฉัน ฉันได้ดำเนินการ IntoIter สำหรับ&'a MyStructและ&mut 'a MyStructและเป็นครั้งแรกที่ได้รับเลือกอย่างใดอย่างหนึ่งเสมอถ้าปัจจุบันแม้ว่าผมจะเรียกว่าinto_iter().for_each()ในmutความคุ้มค่ากับ&mutข้อโต้แย้งในแลมบ์ดา
Dan M.

1
@Ixx: ขอบคุณที่มีประโยชน์มาก ฉันตัดสินใจจัดหา TL; DR ที่ด้านบนของคำถามเพื่อหลีกเลี่ยงการฝังคำตอบไว้ตรงกลางคุณคิดอย่างไร?
Matthieu M.

78

ฉัน (มือใหม่ที่เป็นสนิม) มาที่นี่จาก Google เพื่อหาคำตอบง่ายๆซึ่งคำตอบอื่น ๆ ไม่ได้ให้มา นี่คือคำตอบง่ายๆ:

  • iter() วนซ้ำของรายการโดยอ้างอิง
  • into_iter() วนซ้ำทุกไอเท็มย้ายไปไว้ในขอบเขตใหม่
  • iter_mut() วนซ้ำของรายการโดยให้การอ้างอิงที่ไม่แน่นอนกับแต่ละรายการ

ดังนั้นfor x in my_vec { ... }เท่ากับเป็นหลักmy_vec.into_iter().for_each(|x| ... )- ทั้งmoveองค์ประกอบของmy_vecเข้าไปใน...ขอบเขต

หากคุณเพียงแค่ต้อง "ดูที่" ข้อมูลการใช้งานiterถ้าคุณต้องการที่จะแก้ไข / กลายพันธุ์มันใช้และถ้าคุณต้องการที่จะให้มันเจ้าของใหม่ใช้iter_mutinto_iter

สิ่งนี้มีประโยชน์: http://hermanradtke.com/2015/06/22/effectively-using-iterators-in-rust.html

ทำให้นี่เป็นวิกิชุมชนเพื่อหวังว่า Rust pro จะสามารถแก้ไขคำตอบนี้ได้หากฉันทำผิดพลาด


7
ขอบคุณ ... มันยากที่จะเห็นว่าคำตอบที่ยอมรับนั้นเป็นข้อแตกต่างระหว่างiterและinto_iterอย่างไร
mmw

นั่นคือสิ่งที่ฉันกำลังมองหา!
Cyrusmith

6

.into_iter()ไม่ได้ดำเนินการสำหรับอาร์เรย์ตัวเอง &[]แต่เพียง เปรียบเทียบ:

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

กับ

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

เนื่องจากIntoIteratorมีการกำหนดไว้เท่านั้น&[T]จึงไม่สามารถตัดชิ้นงานด้วยวิธีเดียวกันกับVecเมื่อคุณใช้ค่าได้ (ค่าไม่สามารถย้ายออกได้)

ตอนนี้สาเหตุที่เป็นเช่นนั้นเป็นปัญหาที่แตกต่างกันและฉันต้องการเรียนรู้ด้วยตัวเอง เก็งกำไร: อาร์เรย์เป็นข้อมูลตัวเองชิ้นเป็นเพียงมุมมองเข้าไป ในทางปฏิบัติคุณไม่สามารถย้ายอาร์เรย์เป็นค่าไปยังฟังก์ชันอื่นเพียงแค่ส่งผ่านมุมมองของมันดังนั้นคุณจะไม่สามารถใช้งานอาร์เรย์นั้นได้


IntoIteratorถูกนำมาใช้&'a mut [T]เพื่อทำให้สามารถย้ายวัตถุออกจากอาร์เรย์ ฉันคิดว่ามันเกี่ยวข้องกับความจริงที่ว่าโครงสร้างการส่งคืนIntoIter<T>ไม่มีอาร์กิวเมนต์ตลอดชีวิตในขณะที่Iter<'a, T>ทำดังนั้นอดีตไม่สามารถเก็บชิ้นส่วนได้
rodrigo

mutหมายความว่าคุณสามารถเปลี่ยนค่าได้ไม่ใช่เพื่อที่จะย้ายออก
viraptor

@rodrigo let mut a = ["abc".to_string()]; a.into_iter().map(|x| { *x });=> "ข้อผิดพลาด: ไม่สามารถย้ายออกจากเนื้อหาที่ยืมมา"
viraptor

ใช่ฉันคิดว่าคุณพูดถูกและไม่สามารถย้ายค่าจากแถวลำดับได้ อย่างไรก็ตามฉันยังคงคิดว่ามันควรจะเป็นไปได้ที่จะใช้งานโครงสร้างที่ArrayIntoIterไม่ปลอดภัยโดยใช้ Rust ที่ไม่ปลอดภัยซึ่งเป็นส่วนหนึ่งของห้องสมุด ... อาจไม่คุ้มกับที่คุณควรใช้Vecกับกรณีเหล่านี้
rodrigo

ดังนั้นฉันจึงไม่เข้าใจ ... นั่นคือเหตุผลที่array.into_iterส่งคืน&T- เพราะมันกำลังใช้เวทย์มนตร์ในการแปลงเป็น&array.into_iter- โดยอัตโนมัติและถ้าเป็นเช่นนั้นฉันไม่เข้าใจสิ่งที่เกี่ยวข้องกับค่าที่เคลื่อนที่หรือค่าที่ไม่เคลื่อนไหว หรือว่า @rodrigo บอกว่าคุณได้รับการอ้างอิงเพียงเพราะ (ด้วยเหตุผลบางอย่าง) คุณไม่สามารถย้ายค่าออกจากอาร์เรย์ ? ยังสับสนอยู่มาก
vitiral

2

ฉันคิดว่ามีบางอย่างที่จะอธิบายเพิ่มเติมอีกเล็กน้อย ประเภทการเก็บเช่นVec<T>และVecDeque<T>มีinto_iterวิธีการที่อัตราผลตอบแทนเพราะพวกเขาใช้T IntoIterator<Item=T>ไม่มีอะไรที่จะหยุดเราจะสร้างประเภทคือFoo<T>ถ้าซึ่งเป็นซ้ำมากกว่าก็จะให้ผลผลิตไม่ได้แต่อีกประเภทหนึ่งT Uนั่นคือการดำเนินการFoo<T>IntoIterator<Item=U>

ในความเป็นจริงมีตัวอย่างบางส่วนstd: &Path การดำเนินการ IntoIterator<Item=&OsStr>และการดำเนินการ&UnixListener IntoIterator<Item=Result<UnixStream>>


ความแตกต่างระหว่างinto_iterและiter

กลับไปที่คำถามเดิมกับความแตกต่างระหว่างและinto_iter iterคล้ายกับคนอื่น ๆ ที่มีการชี้ความแตกต่างก็คือว่าinto_iterเป็นวิธีการที่จำเป็นของการIntoIteratorที่สามารถให้ผลผลิตชนิดใด ๆ IntoIterator::Itemที่ระบุไว้ใน โดยทั่วไปหากประเภทดำเนินIntoIterator<Item=I>ตามแบบแผนก็มีสองวิธี ad-hoc: iterและiter_mutที่ผลผลิต&Iและ&mut Iตามลำดับ

สิ่งที่มันหมายถึงคือเราสามารถสร้างฟังก์ชั่นที่ได้รับประเภทที่มีinto_iterวิธีการ (เช่นมันเป็น iterable) โดยใช้ลักษณะผูกพัน:

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

อย่างไรก็ตามเราไม่สามารถ*ใช้ลักษณะผูกพันที่จะต้องใช้ชนิดที่จะมีiterวิธีการหรือiter_mutวิธีการเพราะพวกเขากำลังเพียงการประชุม เราสามารถพูดได้ว่าinto_iterมีมากขึ้นใช้ได้อย่างกว้างขวางกว่าหรือiteriter_mut

ทางเลือกเพื่อiterและiter_mut

ที่น่าสนใจที่จะสังเกตเห็นก็คือว่าiterไม่ได้เป็นวิธีเดียวที่จะได้รับการ iterator &Tที่อัตราผลตอบแทน โดยการประชุม (อีกครั้ง) ประเภทคอลเลกชันSomeCollection<T>ในstdที่มีiterวิธีการยังมีประเภทของการอ้างอิงที่ไม่เปลี่ยนรูปของพวกเขาดำเนินการ&SomeCollection<T> IntoIterator<Item=&T>ตัวอย่างเช่น&Vec<T> การดำเนินการ IntoIterator<Item=&T>เพื่อให้เราสามารถทำซ้ำได้มากกว่า&Vec<T>:

let v = vec![1, 2];

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

หากv.iter()เทียบเท่ากับ&vที่ใช้กันทั้งสองIntoIterator<Item=&T>เหตุใด Rust จึงให้ทั้งสองอย่าง สำหรับการยศาสตร์ ในforลูปก็รัดกุมมากขึ้นอีกนิดกับการใช้งาน&vกว่าv.iter(); แต่ในกรณีอื่น ๆv.iter()มีความชัดเจนมากกว่า(&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()สามารถถูกแทนที่ด้วย&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_iterและiterวิธีการสำหรับประเภท

หากประเภทมี "ทาง" เดียวที่จะวนซ้ำเราควรใช้ทั้งสองอย่าง อย่างไรก็ตามหากมีสองวิธีขึ้นไปสามารถทำซ้ำได้เราควรจัดให้มีวิธีการเฉพาะกิจสำหรับแต่ละวิธี

ตัวอย่างเช่นStringให้ไม่ใช่into_iterหรือiterเพราะมีสองวิธีในการย้ำ: เพื่อย้ำการเป็นตัวแทนในไบต์หรือเพื่อย้ำการเป็นตัวแทนในตัวละคร แต่มีสองวิธี: bytesสำหรับการทำซ้ำไบต์และcharsสำหรับการทำซ้ำอักขระเป็นทางเลือกแทนiterวิธีการ


*ทางเทคนิคเราสามารถทำได้โดยการสร้างลักษณะ แต่เราจำเป็นต้องมีคุณสมบัติimplนั้นสำหรับแต่ละประเภทที่เราต้องการใช้ ในขณะเดียวกันในหลายประเภทอยู่แล้วดำเนินการstdIntoIterator

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.