Apa cara yang benar untuk mengembalikan Iterator (atau sifat lainnya)?

114

Kode Rust berikut mengkompilasi dan berjalan tanpa masalah apa pun.

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

Setelah itu, saya mencoba sesuatu seperti ini .... tetapi tidak dapat dikompilasi

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

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

Masalah utamanya adalah saya tidak yakin tipe kembalian apa yang to_words()harus dimiliki fungsi tersebut. Kompiler mengatakan:

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`

Apa kode yang benar untuk menjalankan ini? .... dan di manakah celah pengetahuan saya?

Forgemo
sumber

Jawaban:

143

Saya merasa berguna untuk membiarkan kompiler memandu saya:

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

Kompilasi memberi:

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> {
  |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Mengikuti saran kompiler dan copy-paste itu sebagai tipe kembalian saya (dengan sedikit pembersihan):

use std::str;

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

Masalahnya adalah Anda tidak dapat mengembalikan sifat seperti Iteratorkarena sifat tidak memiliki ukuran. Itu berarti Rust tidak tahu berapa banyak ruang yang dialokasikan untuk tipe tersebut. Anda juga tidak bisa mengembalikan referensi ke variabel lokal , jadi mengembalikan &dyn Iteratoradalah non-starter.

Menerapkan sifat

Mulai Rust 1.26, Anda dapat menggunakan 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());
}

Ada batasan tentang bagaimana ini bisa digunakan. Anda hanya dapat mengembalikan satu jenis (tanpa kondisional!) Dan itu harus digunakan pada fungsi bebas atau implementasi inheren.

Kemas

Jika Anda tidak keberatan kehilangan sedikit efisiensi, Anda dapat mengembalikan 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());
}

Ini adalah opsi utama yang memungkinkan pengiriman dinamis . Artinya, implementasi kode yang tepat diputuskan pada saat run-time, daripada waktu kompilasi. Artinya, ini cocok untuk kasus di mana Anda perlu mengembalikan lebih dari satu jenis iterator konkret berdasarkan suatu kondisi.

Newtype

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());
}

Ketik alias

Seperti yang ditunjukkan oleh 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());
}

Berurusan dengan penutupan

Jika impl Traittidak tersedia untuk digunakan, penutupan membuat segalanya menjadi lebih rumit. Penutupan membuat tipe anonim dan ini tidak dapat dinamai dalam tipe kembalian:

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]>`

Dalam kasus tertentu, closure ini bisa diganti dengan fungsi, yang bisa dinamai:

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>`

Dan mengikuti saran di atas:

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)
}

Berurusan dengan persyaratan

Jika Anda perlu memilih iterator secara kondisional, lihat iterasi bersyarat pada salah satu dari beberapa kemungkinan iterator .

Shepmaster
sumber
Terima kasih, ini sangat membantu saya. "Trik" untuk membiarkan kompiler memandu Anda cukup berguna, saya pasti akan menggunakannya di masa mendatang. ... dan ya, ini sangat jelek! Saya berharap RFC berhasil mencapai kandidat rilis.
Forgemo
8
Meskipun jenis pembungkus bisa bagus untuk menyembunyikan kompleksitas, saya merasa lebih baik menggunakan typealias saja, karena menggunakan jenis baru berarti Iterator Anda tidak akan menerapkan sifat seperti RandomAccessIteratorbahkan jika Iterator yang mendasarinya melakukannya.
reem
4
Ya! Jenis alias mendukung parameter umum. Sebagai contoh, banyak perpustakaan melakukan type LibraryResult<T> = Result<T, LibraryError>kemudahan yang mirip dengan IoResult<T>, yang juga hanya sebuah alias tipe.
reem
1
Bisakah Anda menjelaskan mengapa seseorang harus menambahkan 'aseumur hidup Box? Apa artinya? Saya selalu berpikir ini hanya untuk batas, untuk mengatakan "Saya hanya dapat bergantung pada sesuatu yang hidup setidaknya selama 'a".
torkleyy
1
@torkleyy mungkin stackoverflow.com/q/27790168/155423 atau stackoverflow.com/q/27675554/155423 akan menjawab pertanyaan Anda? Jika tidak, saya mendorong Anda untuk mencari pertanyaan Anda, dan jika Anda tidak dapat menemukannya, ajukan pertanyaan baru.
Shepmaster