반복자 (또는 다른 특성)를 반환하는 올바른 방법은 무엇입니까?


114

다음 Rust 코드는 문제없이 컴파일되고 실행됩니다.

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비 스타터입니다.

Impl 특성

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

이것은 동적 디스패치 를 허용하는 기본 옵션입니다 . 즉, 코드의 정확한 구현은 컴파일 타임이 아닌 런타임에 결정됩니다. 즉, 조건에 따라 하나 이상의 구체적인 반복기 유형을 반환해야하는 경우에 적합합니다.

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

유형 별칭

으로 림에 의해 지적 밖으로

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
래퍼 유형은 복잡성을 숨기는 데 유용 할 수 있지만, type대신 별칭을 사용하는 것이 더 낫다는 것을 알았습니다 . newtype을 사용 RandomAccessIterator하면 기본 Iterator가 수행하더라도 Iterator가 특성을 구현 하지 않기 때문입니다.
reem dec

4
예! 유형 별칭은 일반 매개 변수를 지원합니다. 예를 들어, 많은 라이브러리는 유형 별칭이기도 type LibraryResult<T> = Result<T, LibraryError>한와 유사한 편의 기능을 수행 IoResult<T>합니다.

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.