2017-11-09 7 views
2

현재 중복 된 일련의 반복 된 반복기 호출 (.lines().map(...).filter(...))을 캡슐화하는 함수를 작성하려고합니다. 컴파일 할 때 형식 서명을 알아낼 수 없습니다. Rust에 대해 이것이 불가능하거나 고도로 독창적이지 않다면, 나는 관용적 접근을위한 제안에 대해 개방적이다.반복자 체인의 추출 체인이 도우미 함수를 호출합니다.

use std::fs; 
use std::io; 
use std::io::prelude::*; 
use std::iter; 

const WORDS_PATH: &str = "/usr/share/dict/words"; 

fn is_short(word: &String) -> bool { 
    word.len() < 7 
} 

fn unwrap(result: Result<String, io::Error>) -> String { 
    result.unwrap() 
} 

fn main_works_but_code_dupe() { 
    let file = fs::File::open(WORDS_PATH).unwrap(); 
    let reader = io::BufReader::new(&file); 
    let count = reader.lines().map(unwrap).filter(is_short).count(); 
    println!("{:?}", count); 

    let mut reader = io::BufReader::new(&file); 
    reader.seek(io::SeekFrom::Start(0)); 
    let sample_size = (0.05 * count as f32) as usize; // 5% sample 

    // This chain of iterator logic is duplicated 
    for line in reader.lines().map(unwrap).filter(is_short).take(sample_size) { 
     println!("{}", line); 
    } 
} 

fn short_lines<'a, T> 
    (reader: &'a T) 
    -> iter::Filter<std::iter::Map<std::io::Lines<T>, &FnMut(&str, bool)>, &FnMut(&str, bool)> 
    where T: io::BufRead 
{ 
    reader.lines().map(unwrap).filter(is_short) 
} 

fn main_dry() { 
    let file = fs::File::open(WORDS_PATH).unwrap(); 
    let reader = io::BufReader::new(&file); 
    let count = short_lines(reader).count(); 
    println!("{:?}", count); 

    // Would like to do this instead: 
    let mut reader = io::BufReader::new(&file); 
    reader.seek(io::SeekFrom::Start(0)); 
    let sample_size = (0.05 * count as f32) as usize; // 5% sample 
    for line in short_lines(reader).take(sample_size) { 
     println!("{}", line); 
    } 
} 


fn main() { 
    main_works_but_code_dupe(); 
} 
+1

해결 방법은 체인 반복자의 형식 별칭을 사용하는 것입니다, 나는 생각한다. 나는 추상적 인 타입을 반환하는 것을 생각하지 않는다. (이 경우에는'short_lines'의 반복자를 반환 할 것이지만 여전히 녹슬지는 않습니다.)'impl Trait'에 대한 논의를 확인하십시오 (여기에는 추적 문제가 있습니다 : https : // github.com/rust-lang/rust/issues/34511) 야간 툴 체인을 사용하여 이미이 작업을 수행 할 수 있습니다. – bow

답변

5

나는이 컴파일 얻을 수있는 유형의 서명을 알아낼 수 없습니다.

컴파일러 을 말했습니다.

error[E0308]: mismatched types 
    --> src/main.rs:35:5 
    | 
35 |  reader.lines().map(unwrap).filter(is_short) 
    |  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected reference, found fn item 
    | 
    = note: expected type `std::iter::Filter<std::iter::Map<_, &'a for<'r> std::ops::FnMut(&'r str, bool) + 'a>, &'a for<'r> std::ops::FnMut(&'r str, bool) + 'a>` 
       found type `std::iter::Filter<std::iter::Map<_, fn(std::result::Result<std::string::String, std::io::Error>) -> std::string::String {unwrap}>, for<'r> fn(&'r std::string::String) -> bool {is_short}>` 

이제는 직접 붙여 넣기 만하면됩니다. _ 유형을 이미 가지고있는 실제 유형으로 바꿔야합니다 (이미 맞았 기 때문에 버렸습니다). 둘째, {unwrap}{is_short} 비트를 삭제해야합니다. 그것들은 함수 항목이 고유 한 유형을 가지고 있기 때문에 컴파일러가 주석을 달기 때문입니다. 슬프게도, 당신은 실제로 이러한 형식을 쓸 수 없습니다.

다시 컴파일하고 ...

error[E0308]: mismatched types 
    --> src/main.rs:35:5 
    | 
32 |  -> std::iter::Filter<std::iter::Map<std::io::Lines<T>, fn(std::result::Result<std::string::String, std::io::Error>) -> std::string::String>, for<'r> fn(&'r std::string::String) -> bool> 
    |   -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- expected `std::iter::Filter<std::iter::Map<std::io::Lines<T>, fn(std::result::Result<std::string::String, std::io::Error>) -> std::string::String>, for<'r> fn(&'r std::string::String) -> bool>` because of return type 
... 
35 |  reader.lines().map(unwrap).filter(is_short) 
    |  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found fn item 
    | 
    = note: expected type `std::iter::Filter<std::iter::Map<_, fn(std::result::Result<std::string::String, std::io::Error>) -> std::string::String>, for<'r> fn(&'r std::string::String) -> bool>` 
       found type `std::iter::Filter<std::iter::Map<_, fn(std::result::Result<std::string::String, std::io::Error>) -> std::string::String {unwrap}>, for<'r> fn(&'r std::string::String) -> bool {is_short}>` 

나는 기능 항목에 대해 고유 한 유형이 말을 기억 하는가? 그래, 그거야. 을 수정하려면을 함수 항목에서 함수 포인터로 캐스팅합니다. 우리는 캐스팅 할 내용을 지정할 필요조차 없습니다. 컴파일러가 캐스팅을 수행하기를 원한다는 것을 알리면됩니다.

fn short_lines<'a, T> 
    (reader: &'a T) 
    -> std::iter::Filter<std::iter::Map<std::io::Lines<T>, fn(std::result::Result<std::string::String, std::io::Error>) -> std::string::String>, for<'r> fn(&'r std::string::String) -> bool> 
    where T: io::BufRead 
{ 
    reader.lines().map(unwrap as _).filter(is_short as _) 
} 
error[E0308]: mismatched types 
    --> src/main.rs:41:29 
    | 
41 |  let count = short_lines(reader).count(); 
    |        ^^^^^^ expected reference, found struct `std::io::BufReader` 
    | 
    = note: expected type `&_` 
       found type `std::io::BufReader<&std::fs::File>` 
    = help: try with `&reader` 

다시 말하지만, 컴파일러는 무엇을 정확히 알려줍니다.

error[E0507]: cannot move out of borrowed content 
    --> src/main.rs:35:5 
    | 
35 |  reader.lines().map(unwrap as _).filter(is_short as _) 
    |  ^^^^^^ cannot move out of borrowed content 

오른쪽 당신이 잘못 입력 short_lines을 가지고 있기 때문에, 그건 ... 변화를 확인합니다. 한 번 더 변경 :

fn short_lines<T> 
    (reader: T) 
    -> std::iter::Filter<std::iter::Map<std::io::Lines<T>, fn(std::result::Result<std::string::String, std::io::Error>) -> std::string::String>, for<'r> fn(&'r std::string::String) -> bool> 
    where T: io::BufRead 
{ 
    reader.lines().map(unwrap as _).filter(is_short as _) 
} 

이제 모든 사항을 경고해야합니다.

요약하면 컴파일러 메시지를 읽으십시오. 유용합니다.

+0

'r'수명이 필요 없어 제거 될 수 있다고 생각합니다. – Stefan

+1

감사합니다! 내가 녹슨 것을 배우고 있다고 생각하고 오래 동안 복잡한 타입 시그니처가 새로운 것이고, 기본적으로 "타입이 틀리다"라는 것을 이해하기 때문에 나는 100 문자 타입의 시그니처를 이해할 수없고, 컴파일러 메시지를 의미있게 해석하는 방법을 이해할 수있다. –

+0

@Stefan 예 'r'이 필요 없음을 확인합니다. –

3

나는 그것에게 간단한 방법으로하고 좋을 것 - 벡터에 반복자를 수집을 :

use std::fs; 
use std::io; 
use std::io::prelude::*; 

const WORDS_PATH: &str = "/usr/share/dict/words"; 

fn main() { 
    let file = fs::File::open(WORDS_PATH).unwrap(); 
    let reader = io::BufReader::new(&file); 
    let short_lines = reader.lines() 
          .map(|l| l.unwrap()) 
          .filter(|l| l.len() < 7) 
          .collect::<Vec<_>>(); // the element type can just be inferred 
    let count = short_lines.len(); 
    println!("{:?}", count); 

    let sample_size = (0.05 * count as f32) as usize; // 5% sample 

    for line in &short_lines[0..sample_size] { 
     println!("{}", line); 
    } 
} 
+1

@MihailsStrasuns 저는 질문자의 제약 조건이나 질문의 ​​요지에 대해 아무런 가정을하지 않습니다. 오늘날의 컴퓨터에서는 메모리가 거의 문제가되지 않으며 이와 같은 간단한 작업으로 완전히 유효한 솔루션 일 수 있습니다. 대안적인 관점 : Rust 사용자가 부담없이 동일한 파일을 두 번 읽는 것이 얼마나 쉬운 지에 대한 사실 (메모리에서 일하는 것이 엄청나게 빠름)은 언어 생태계에서 가장 큰 문제 중 하나입니다 : P. – ljedrz

+1

@MihailsStrasuns 하하하! 나는 실제로 다른 방법이라고 말하고 싶습니다 - 많은 녹스니아들은 효율적인 코드를 작성하는 것을 지나치게 걱정하지만 프로그래머의 효율성을 무시합니다! 나는 사람들이 메모리 할당을 과도하게 사용하고있는 참고 문헌을 지적하고자한다. Rust의 참조 및 수명은 실제로 "단지 경우에 따라"할당을 피하면서 참조를 전달하는 것이 훨씬 쉬워졌습니다. – Shepmaster

+0

@Shepmaster 멀리 보지 않아도됩니다 - 매우 https://doc.rust-lang.org/std/io/trait.BufRead.html#method.lines는 각 줄을 새로운 문자열로 할당합니다. 표준 라이브러리 프리미티브에서 허용되는 것으로 간주된다는 사실은 지배적 태도에 대해 많은 것을 말해줍니다. 마이크로 최적화는 좋지 않지만 본질적으로 O (1) 메모리 풋 프린트 (및 단일 파일 패스)가 필요한 작업에 대한 O (n) 메모리 할당을 말하며 비슷한 양의 코드로 효율적으로 표현할 수 있습니다. 거대한 성능 저하를 초래할 수있는 소규모 전문 앱의 경우 –