วิธีที่ถูกต้องในการส่งคืน Iterator (หรือลักษณะอื่น ๆ ) คืออะไร?


114

รหัสสนิมต่อไปนี้รวบรวมและทำงานโดยไม่มีปัญหาใด ๆ

fn main() {
    let text = "abc";
    println!("{}", text.split(' ').take(2).count());
}

หลังจากนั้นฉันก็ลองทำอะไรแบบนี้ .... แต่มันไม่ได้คอมไพล์

fn main() {
    let text = "word1 word2 word3";
    println!("{}", to_words(text).take(2).count());
}

fn to_words(text: &str) -> &Iterator<Item = &str> {
    &(text.split(' '))
}

ปัญหาหลักคือฉันไม่แน่ใจว่าto_words()ควรมีฟังก์ชันการส่งคืนประเภทใด คอมไพเลอร์กล่าวว่า:

error[E0599]: no method named `count` found for type `std::iter::Take<std::iter::Iterator<Item=&str>>` in the current scope
 --> src/main.rs:3:43
  |
3 |     println!("{}", to_words(text).take(2).count());
  |                                           ^^^^^
  |
  = note: the method `count` exists but the following trait bounds were not satisfied:
          `std::iter::Iterator<Item=&str> : std::marker::Sized`
          `std::iter::Take<std::iter::Iterator<Item=&str>> : std::iter::Iterator`

รหัสที่ถูกต้องในการเรียกใช้คืออะไร .... แล้วช่องว่างความรู้ของฉันอยู่ที่ไหน?

คำตอบ:


143

ฉันพบว่ามีประโยชน์ที่จะให้คอมไพเลอร์แนะนำฉัน:

fn to_words(text: &str) { // Note no return type
    text.split(' ')
}

การรวบรวมให้:

error[E0308]: mismatched types
 --> src/lib.rs:5:5
  |
5 |     text.split(' ')
  |     ^^^^^^^^^^^^^^^ expected (), found struct `std::str::Split`
  |
  = note: expected type `()`
             found type `std::str::Split<'_, char>`
help: try adding a semicolon
  |
5 |     text.split(' ');
  |                    ^
help: try adding a return type
  |
3 | fn to_words(text: &str) -> std::str::Split<'_, char> {
  |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

ทำตามคำแนะนำของคอมไพเลอร์และการคัดลอกวางที่เป็นประเภทการส่งคืนของฉัน (ด้วยการล้างข้อมูลเล็กน้อย):

use std::str;

fn to_words(text: &str) -> str::Split<'_, char> {
    text.split(' ')
}

ปัญหาคือคุณไม่สามารถส่งคืนลักษณะที่เหมือนได้Iteratorเนื่องจากลักษณะไม่มีขนาด นั่นหมายความว่า Rust ไม่รู้ว่าจะจัดสรรพื้นที่สำหรับประเภทเท่าไร คุณไม่สามารถส่งคืนการอ้างอิงไปยังตัวแปรโลคัลได้ดังนั้นการส่งคืน&dyn Iteratorจึงไม่ใช่การเริ่มต้น

ลักษณะเด่น

ตั้งแต่ Rust 1.26 คุณสามารถใช้impl trait:

fn to_words<'a>(text: &'a str) -> impl Iterator<Item = &'a str> {
    text.split(' ')
}

fn main() {
    let text = "word1 word2 word3";
    println!("{}", to_words(text).take(2).count());
}

มีข้อ จำกัด ในการใช้งาน คุณสามารถส่งคืนได้เพียงประเภทเดียวเท่านั้น (ไม่มีเงื่อนไข!) และต้องใช้กับฟังก์ชันฟรีหรือการใช้งานโดยธรรมชาติ

บรรจุกล่อง

หากคุณไม่รังเกียจที่จะสูญเสียประสิทธิภาพเล็กน้อยคุณสามารถคืนค่าBox<dyn Iterator>:

fn to_words<'a>(text: &'a str) -> Box<dyn Iterator<Item = &'a str> + 'a> {
    Box::new(text.split(' '))
}

fn main() {
    let text = "word1 word2 word3";
    println!("{}", to_words(text).take(2).count());
}

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

ประเภทใหม่

use std::str;

struct Wrapper<'a>(str::Split<'a, char>);

impl<'a> Iterator for Wrapper<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<&'a str> {
        self.0.next()
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        self.0.size_hint()
    }
}

fn to_words(text: &str) -> Wrapper<'_> {
    Wrapper(text.split(' '))
}

fn main() {
    let text = "word1 word2 word3";
    println!("{}", to_words(text).take(2).count());
}

พิมพ์นามแฝง

ในฐานะที่เป็นออกแหลมโดย reem

use std::str;

type MyIter<'a> = str::Split<'a, char>;

fn to_words(text: &str) -> MyIter<'_> {
    text.split(' ')
}

fn main() {
    let text = "word1 word2 word3";
    println!("{}", to_words(text).take(2).count());
}

การจัดการกับการปิด

เมื่อimpl Traitไม่พร้อมใช้งานการปิดทำให้สิ่งต่างๆซับซ้อนมากขึ้น การปิดจะสร้างประเภทที่ไม่ระบุตัวตนและไม่สามารถตั้งชื่อในประเภทการส่งคืน:

fn odd_numbers() -> () {
    (0..100).filter(|&v| v % 2 != 0)
}
found type `std::iter::Filter<std::ops::Range<{integer}>, [closure@src/lib.rs:4:21: 4:36]>`

ในบางกรณีการปิดเหล่านี้สามารถแทนที่ด้วยฟังก์ชันซึ่งสามารถตั้งชื่อได้:

fn odd_numbers() -> () {
    fn f(&v: &i32) -> bool {
        v % 2 != 0
    }
    (0..100).filter(f as fn(v: &i32) -> bool)
}
found type `std::iter::Filter<std::ops::Range<i32>, for<'r> fn(&'r i32) -> bool>`

และปฏิบัติตามคำแนะนำข้างต้น:

use std::{iter::Filter, ops::Range};

type Odds = Filter<Range<i32>, fn(&i32) -> bool>;

fn odd_numbers() -> Odds {
    fn f(&v: &i32) -> bool {
        v % 2 != 0
    }
    (0..100).filter(f as fn(v: &i32) -> bool)
}

การจัดการกับเงื่อนไข

หากคุณต้องการเลือกตัววนซ้ำตามเงื่อนไขโปรดดูที่การทำซ้ำตามเงื่อนไขมากกว่าหนึ่งในตัวทำซ้ำที่เป็นไปได้หลายตัว


ขอบคุณสิ่งนี้ช่วยฉันได้มาก "เคล็ดลับ" ที่จะให้คอมไพเลอร์แนะนำคุณมีประโยชน์มากฉันจะใช้มันในอนาคตแน่นอน ... และใช่มันน่าเกลียดมาก! ฉันหวังว่า RFC จะมอบให้กับผู้สมัครที่ปล่อยออกมา
forgemo

8
ในขณะที่ประเภท wrapper สามารถซ่อนความซับซ้อนได้ดี แต่ฉันคิดว่าเป็นการดีกว่าที่จะใช้typeนามแฝงแทนเนื่องจากการใช้ newtype หมายความว่า Iterator ของคุณจะไม่ใช้ลักษณะเช่นRandomAccessIteratorแม้ว่า Iterator ที่อยู่เบื้องหลังจะทำก็ตาม
reem

4
ได้! ประเภทนามแฝงสนับสนุนพารามิเตอร์ทั่วไป ตัวอย่างเช่นไลบรารีหลายแห่งทำtype LibraryResult<T> = Result<T, LibraryError>เพื่ออำนวยความสะดวกคล้ายกับIoResult<T>ซึ่งเป็นเพียงนามแฝงประเภทหนึ่ง
reem

1
คุณช่วยชี้แจงได้ไหมว่าทำไมต้องเพิ่ม'aอายุการใช้งานBox? นั่นหมายความว่าอย่างไร? ฉันคิดเสมอว่านี่เป็นเพียงขอบเขตเท่านั้นที่จะพูดว่า "T อาจขึ้นอยู่กับบางสิ่งที่มีชีวิตอยู่อย่างน้อยตราบเท่าที่'a"
torkleyy

1
@torkleyy บางทีstackoverflow.com/q/27790168/155423หรือstackoverflow.com/q/27675554/155423จะตอบคำถามของคุณ? หากไม่เป็นเช่นนั้นเราขอแนะนำให้คุณค้นหาคำถามของคุณและหากไม่พบให้ถามใหม่
Shepmaster
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.