Cách chính xác để trả về một Iterator (hoặc bất kỳ đặc điểm nào khác) là gì?


114

Mã Rust sau đây biên dịch và chạy mà không gặp bất kỳ sự cố nào.

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

Sau đó, tôi đã thử một cái gì đó như thế này .... nhưng nó không biên dịch

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

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

Vấn đề chính là tôi không chắc hàm to_words()nên có kiểu trả về nào. Trình biên dịch nói:

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`

Mã chính xác để chạy nó là gì? .... và lỗ hổng kiến ​​thức của tôi ở đâu?

Câu trả lời:


143

Tôi thấy hữu ích khi để trình biên dịch hướng dẫn tôi:

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

Biên dịch mang lại:

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

Thực hiện theo đề xuất của trình biên dịch và sao chép dán làm kiểu trả về của tôi (với một chút dọn dẹp):

use std::str;

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

Vấn đề là bạn không thể trả về một đặc điểm như Iteratorvì một đặc điểm không có kích thước. Điều đó có nghĩa là Rust không biết có bao nhiêu không gian để phân bổ cho kiểu. Bạn cũng không thể trả về một tham chiếu đến một biến cục bộ , do đó, trả về &dyn Iteratorkhông phải là biến cục bộ.

Đặc điểm cấy ghép

Kể từ Rust 1.26, bạn có thể sử dụng 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());
}

Có những hạn chế về cách thức này có thể được sử dụng. Bạn chỉ có thể trả về một kiểu duy nhất (không có điều kiện!) Và nó phải được sử dụng trên một hàm miễn phí hoặc một triển khai cố hữu.

Đóng hộp

Nếu bạn không ngại mất một chút hiệu quả, bạn có thể trả về 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());
}

Đây là tùy chọn chính cho phép điều phối động . Đó là, việc triển khai chính xác mã được quyết định tại thời điểm chạy, thay vì thời gian biên dịch. Điều đó có nghĩa là điều này phù hợp với các trường hợp bạn cần trả về nhiều hơn một loại trình lặp cụ thể dựa trên một điều kiện.

Loại mới

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

Nhập bí danh

Như đã chỉ ra bởi 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());
}

Đối phó với việc đóng cửa

Khi impl Traitkhông có sẵn để sử dụng, việc đóng cửa khiến mọi thứ trở nên phức tạp hơn. Đóng cửa tạo ra các loại ẩn danh và chúng không thể được đặt tên trong loại trả lại:

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

Trong một số trường hợp nhất định, các bao đóng này có thể được thay thế bằng các hàm, có thể được đặt tên là:

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

Và theo lời khuyên trên:

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

Đối phó với các điều kiện

Nếu bạn cần chọn một trình lặp có điều kiện, hãy tham khảo Lặp có điều kiện qua một trong một số trình lặp có thể .


Cảm ơn bạn, điều này đã giúp tôi rất nhiều. "Thủ thuật" để trình biên dịch hướng dẫn bạn khá hữu ích, tôi chắc chắn sẽ sử dụng nó trong tương lai. ... và vâng, điều này thật là xấu xí! Tôi hy vọng rằng RFC sẽ trở thành ứng cử viên phát hành.
forgemo

8
Mặc dù các loại trình bao bọc có thể tốt để che giấu sự phức tạp, nhưng tôi thấy tốt hơn là chỉ sử dụng typebí danh để thay thế, vì sử dụng một kiểu mới có nghĩa là Iterator của bạn sẽ không triển khai các đặc điểm như RandomAccessIteratorngay cả khi lặp lại bên dưới.
reem

4
Đúng! Loại bí danh hỗ trợ các tham số chung. Ví dụ, nhiều thư viện làm type LibraryResult<T> = Result<T, LibraryError>như một tiện ích tương tự như IoResult<T>, cũng chỉ là một bí danh kiểu.
reem

1
Bạn có thể vui lòng làm rõ tại sao người ta phải thêm 'acả cuộc đời vào Boxkhông? Điều đó nghĩa là gì? Tôi luôn nghĩ điều này chỉ dành cho giới hạn, để nói rằng "T chỉ có thể phụ thuộc vào một cái gì đó sống ít nhất là lâu dài 'a".
torkleyy

1
@torkleyy có lẽ stackoverflow.com/q/27790168/155423 hoặc stackoverflow.com/q/27675554/155423 sẽ trả lời câu hỏi của bạn? Nếu không, tôi khuyến khích bạn tìm kiếm câu hỏi của mình và nếu bạn không thể tìm thấy, hãy hỏi một câu hỏi mới.
Shepmaster
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.