2015-05-27 7 views
5

Aktualizacja: tytuł postu został zaktualizowany, a odpowiedź została usunięta z pytania. Krótka odpowiedź brzmi: nie możesz. Proszę zobaczyć moją odpowiedź na to pytanie.Jak mogę zwrócić Iterator wygenerowany przez funkcję, która pobiera & mut self (kiedy self jest tworzony lokalnie)?

Obserwuję błąd Handling blogu here (GitHub bo here), a ja starałem się dokonać pewnych modyfikacji do kodu tak, że funkcja search zwraca Iterator zamiast Vec. To było szalenie trudne i utknąłem.

stałam się do tego punktu:

fn search<'a, P: AsRef<Path>>(file_path: &Option<P>, city: &str) 
    -> Result<FilterMap<csv::reader::DecodedRecords<'a, Box<Read>, Row>, 
         FnMut(Result<Row, csv::Error>) 
          -> Option<Result<PopulationCount, csv::Error>>>, 
       CliError> { 
    let mut found = vec![]; 
    let input: Box<io::Read> = match *file_path { 
     None => Box::new(io::stdin()), 
     Some(ref file_path) => Box::new(try!(fs::File::open(file_path))), 
    }; 

    let mut rdr = csv::Reader::from_reader(input); 
    let closure = |row: Result<Row, csv::Error>| -> Option<Result<PopulationCount, csv::Error>> { 
     let row = match row { 
      Ok(row) => row, 
      Err(err) => return Some(Err(From::from(err))), 
     }; 
     match row.population { 
      None => None, 
      Some(count) => if row.city == city { 
       Some(Ok(PopulationCount { 
        city: row.city, 
        country: row.country, 
        count: count, 
       })) 
      } else { 
       None 
      } 
     } 
    }; 
    let found = rdr.decode::<Row>().filter_map(closure); 

    if !found.all(|row| match row { 
     Ok(_) => true, 
     _ => false, 
    }) { 
     Err(CliError::NotFound) 
    } else { 
     Ok(found) 
    } 
} 

z następującym błędem z kompilatora:

src/main.rs:97:1: 133:2 error: the trait `core::marker::Sized` is not implemented for the type `core::ops::FnMut(core::result::Result<Row, csv::Error>) -> core::option::Option<core::result::Result<PopulationCount, csv::Error>>` [E0277] 
src/main.rs:97 fn search<'a, P: AsRef<Path>>(file_path: &Option<P>, city: &str) -> Result<FilterMap<csv::reader::DecodedRecords<'a, Box<Read>, Row>, FnMut(Result<Row, csv::Error>) -> Option<Result<PopulationCount, csv::Error>>>, CliError> { 
src/main.rs:98  let mut found = vec![]; 
src/main.rs:99  let input: Box<io::Read> = match *file_path { 
src/main.rs:100   None => Box::new(io::stdin()), 
src/main.rs:101   Some(ref file_path) => Box::new(try!(fs::File::open(file_path))), 
src/main.rs:102  }; 
       ... 
src/main.rs:97:1: 133:2 note: `core::ops::FnMut(core::result::Result<Row, csv::Error>) -> core::option::Option<core::result::Result<PopulationCount, csv::Error>>` does not have a constant size known at compile-time 
src/main.rs:97 fn search<'a, P: AsRef<Path>>(file_path: &Option<P>, city: &str) -> Result<FilterMap<csv::reader::DecodedRecords<'a, Box<Read>, Row>, FnMut(Result<Row, csv::Error>) -> Option<Result<PopulationCount, csv::Error>>>, CliError> { 
src/main.rs:98  let mut found = vec![]; 
src/main.rs:99  let input: Box<io::Read> = match *file_path { 
src/main.rs:100   None => Box::new(io::stdin()), 
src/main.rs:101   Some(ref file_path) => Box::new(try!(fs::File::open(file_path))), 
src/main.rs:102  }; 
       ... 
error: aborting due to previous error 

Ja również próbował tę definicję funkcji:

fn search<'a, P: AsRef<Path>, F>(file_path: &Option<P>, city: &str) 
    -> Result<FilterMap<csv::reader::DecodedRecords<'a, Box<Read>, Row>, F>, 
       CliError> 
    where F: FnMut(Result<Row, csv::Error>) 
        -> Option<Result<PopulationCount, csv::Error>> { 

z tymi błędami z kompilatora:

src/main.rs:131:12: 131:17 error: mismatched types: 
expected `core::iter::FilterMap<csv::reader::DecodedRecords<'_, Box<std::io::Read>, Row>, F>`, 
found `core::iter::FilterMap<csv::reader::DecodedRecords<'_, Box<std::io::Read>, Row>, [closure src/main.rs:105:19: 122:6]>` 
(expected type parameter, 
found closure) [E0308] 
src/main.rs:131   Ok(found) 

Nie mogę Box zamknięcia, ponieważ wtedy nie zostanie zaakceptowane przez filter_map.

Potem próbowałem się:

fn search<'a, P: AsRef<Path>>(file_path: &Option<P>, city: &'a str) 
    -> Result<(Box<Iterator<Item=Result<PopulationCount, csv::Error>> + 'a>, csv::Reader<Box<io::Read>>), CliError> { 
    let input: Box<io::Read> = match *file_path { 
     None => box io::stdin(), 
     Some(ref file_path) => box try!(fs::File::open(file_path)), 
    }; 

    let mut rdr = csv::Reader::from_reader(input); 
    let mut found = rdr.decode::<Row>().filter_map(move |row| { 
     let row = match row { 
      Ok(row) => row, 
      Err(err) => return Some(Err(err)), 
     }; 
     match row.population { 
      None => None, 
      Some(count) if row.city == city => { 
       Some(Ok(PopulationCount { 
        city: row.city, 
        country: row.country, 
        count: count, 
       })) 
      }, 
      _ => None, 
     } 
    }); 

    if found.size_hint().0 == 0 { 
     Err(CliError::NotFound) 
    } else { 
     Ok((box found, rdr)) 
    } 
} 

fn main() { 
    let args: Args = Docopt::new(USAGE) 
          .and_then(|d| d.decode()) 
          .unwrap_or_else(|err| err.exit()); 


    match search(&args.arg_data_path, &args.arg_city) { 
     Err(CliError::NotFound) if args.flag_quiet => process::exit(1), 
     Err(err) => fatal!("{}", err), 
     Ok((pops, rdr)) => for pop in pops { 
      match pop { 
       Err(err) => panic!(err), 
       Ok(pop) => println!("{}, {}: {} - {:?}", pop.city, pop.country, pop.count, rdr.byte_offset()), 
      } 
     } 
    } 
} 

co daje mi ten błąd:

src/main.rs:107:21: 107:24 error: `rdr` does not live long enough 
src/main.rs:107  let mut found = rdr.decode::<Row>().filter_map(move |row| { 
            ^~~ 
src/main.rs:100:117: 130:2 note: reference must be valid for the lifetime 'a as defined on the block at 100:116... 
src/main.rs:100  -> Result<(Box<Iterator<Item=Result<PopulationCount, csv::Error>> + 'a>, csv::Reader<Box<io::Read>>), CliError> { 
src/main.rs:101  let input: Box<io::Read> = match *file_path { 
src/main.rs:102   None => box io::stdin(), 
src/main.rs:103   Some(ref file_path) => box try!(fs::File::open(file_path)), 
src/main.rs:104  }; 
src/main.rs:105  
       ... 
src/main.rs:106:51: 130:2 note: ...but borrowed value is only valid for the block suffix following statement 1 at 106:50 
src/main.rs:106  let mut rdr = csv::Reader::from_reader(input); 
src/main.rs:107  let mut found = rdr.decode::<Row>().filter_map(move |row| { 
src/main.rs:108   let row = match row { 
src/main.rs:109    Ok(row) => row, 
src/main.rs:110    Err(err) => return Some(Err(err)), 
src/main.rs:111   }; 
       ... 
error: aborting due to previous error 

ja coś źle zaprojektowane, albo ja biorąc błędne podejście? Czy brakuje mi czegoś naprawdę prostego i głupiego? Nie jestem pewien, dokąd się udać.

+0

Czy możesz wyjaśnić, dlaczego nie jest to duplikat [Prawidłowy sposób na zwrócenie Iterator] (http://stackoverflow.com/q/27535289/155423)? – Shepmaster

+0

Myślę, że Nashenas rzeczywiście zwrócił iterator, ale jest inny problem - pożyczenie lokalnego. – bluss

+0

@bluss jest poprawne. Nazwałem to pytanie na podstawie tego, co próbowałem osiągnąć, ale wygląda na to, że należy zmienić nazwę na podstawie tego, czego się nauczyłem. Co byście wszyscy polecili? – Nashenas

Odpowiedz

2

Ta odpowiedź jest oparta na @bluss „s answer + pomoC#rust na serwerze irc.mozilla.org

Jeden problem to nie oczywiste z kodu, który był przyczyną i ostatecznym błąd wyświetlany tylko powyżej, ma związek z definicją csv::Reader::decode (patrz jej source). Zajmuje &'a mut self, wyjaśnienie tego problemu jest omówione w tym answer. Zasadniczo powoduje to, że czas życia czytnika jest ograniczony do bloku, do którego jest wywoływany. Sposobem naprawienia tego jest podzielenie funkcji na połowę (ponieważ nie mogę kontrolować definicji funkcji, jak zaleca się w poprzednim łączu odpowiedzi). Potrzebowałem dożywotnio czytnika, który był ważny w funkcji main, aby czytelnik mógł przejść do funkcji search. Zobacz poniższy kod (To może być zdecydowanie bardziej oczyszczone):

fn population_count<'a, I>(iter: I, city: &'a str) 
    -> Box<Iterator<Item=Result<PopulationCount,csv::Error>> + 'a> 
    where I: IntoIterator<Item=Result<Row,csv::Error>>, 
      I::IntoIter: 'a, 
{ 
    Box::new(iter.into_iter().filter_map(move |row| { 
     let row = match row { 
      Ok(row) => row, 
      Err(err) => return Some(Err(err)), 
     }; 

     match row.population { 
      None => None, 
      Some(count) if row.city == city => { 
       Some(Ok(PopulationCount { 
        city: row.city, 
        country: row.country, 
        count: count, 
       })) 
      }, 
      _ => None, 
     } 
    })) 
} 

fn get_reader<P: AsRef<Path>>(file_path: &Option<P>) 
    -> Result<csv::Reader<Box<io::Read>>, CliError> 
{ 
    let input: Box<io::Read> = match *file_path { 
     None => Box::new(io::stdin()), 
     Some(ref file_path) => Box::new(try!(fs::File::open(file_path))), 
    }; 

    Ok(csv::Reader::from_reader(input)) 
} 

fn search<'a>(reader: &'a mut csv::Reader<Box<io::Read>>, city: &'a str) 
    -> Box<Iterator<Item=Result<PopulationCount, csv::Error>> + 'a> 
{ 
    population_count(reader.decode::<Row>(), city) 
} 

fn main() { 
    let args: Args = Docopt::new(USAGE) 
     .and_then(|d| d.decode()) 
     .unwrap_or_else(|err| err.exit()); 

    let reader = get_reader(&args.arg_data_path); 
    let mut reader = match reader { 
     Err(err) => fatal!("{}", err), 
     Ok(reader) => reader, 
    }; 

    let populations = search(&mut reader, &args.arg_city); 
    let mut found = false; 
    for pop in populations { 
     found = true; 
     match pop { 
      Err(err) => fatal!("fatal !! {}", err), 
      Ok(pop) => println!("{}, {}: {}", pop.city, pop.country, pop.count), 
     } 
    } 

    if !(found || args.flag_quiet) { 
     fatal!("{}", CliError::NotFound); 
    } 
} 

Nauczyłem się dużo próbuje uzyskać to do pracy i mają znacznie większą wdzięczność za błędy kompilatora. Teraz jest jasne, że gdyby to był C, ostatni błąd powyżej mógł rzeczywiście spowodować uszkodzenia, co byłoby o wiele trudniejsze do debugowania. Zauważyłem również, że konwersja z wcześniej obliczonego vec na iterator wymaga bardziej zaangażowanego myślenia o tym, kiedy pamięć wchodzi i jest poza zakresem; Nie mogę po prostu zmienić kilku wywołań funkcji i zwracać typów i nazwać to dziennie.

8

Powracanie iteratorów jest możliwe, ale wiąże się z pewnymi ograniczeniami.

Aby wykazać, że to możliwe, dwa przykłady (A) z wyraźną typ iteratora i (B) za pomocą boks (playpen link).

use std::iter::FilterMap; 

fn is_even(elt: i32) -> Option<i32> { 
    if elt % 2 == 0 { 
     Some(elt) 
    } else { None } 
} 

/// (A) 
pub fn evens<I: IntoIterator<Item=i32>>(iter: I) 
    -> FilterMap<I::IntoIter, fn(I::Item) -> Option<I::Item>> 
{ 
    iter.into_iter().filter_map(is_even) 
} 

/// (B) 
pub fn cumulative_sums<'a, I>(iter: I) -> Box<Iterator<Item=i32> + 'a> 
    where I: IntoIterator<Item=i32>, 
      I::IntoIter: 'a, 
{ 
    Box::new(iter.into_iter().scan(0, |acc, x| { 
     *acc += x; 
     Some(*acc) 
    })) 
} 

fn main() { 
    // The output is: 
    // 0 is even, 10 is even, 
    // 1, 3, 6, 10, 
    for even in evens(vec![0, 3, 7, 10]) { 
     print!("{} is even, ", even); 
    } 
    println!(""); 

    for cs in cumulative_sums(1..5) { 
     print!("{}, ", cs); 
    } 
    println!(""); 
} 

Ty wystąpił problem z (A) - wyraźnej typu! Bezobsługowe zamknięcia, które uzyskujemy ze zwykłych wyrażeń lambda ze składnią |a, b, c| .., mają unikalne anonimowe typy. Funkcje wymagają jawnych typów zwracanych, więc to nie działa tutaj.

Niektóre rozwiązania dla zamknięć powracający:

  • pomocą wskaźnika funkcji fn() jak w przykładzie I (A). Często i tak nie potrzebujesz środowiska zamknięcia.
  • Box the closure. Jest to uzasadnione, nawet jeśli iteratory nie obsługują wywoływania go w tej chwili. Nie twoja wina.
  • Okno iteratora
  • Zwraca niestandardową strukturę iteratora. Wymaga kilku podstaw.

Widać, że w przykładzie (B) musimy być bardzo ostrożni w życiu.Mówi, że wartość zwracana to Box<Iterator<Item=i32> + 'a>, co to jest 'a? Jest to najmniejsza żywotność wymagana od wszystkiego w pudełku! Umieściliśmy również 'a związaną z I::IntoIter - dzięki temu możemy umieścić to w pudełku.

Jeśli powiesz po prostu Box<Iterator<Item=i32>>, przyjmie on postać 'static.

Musimy wyraźnie określić czas życia zawartości naszego pudełka. Aby być bezpiecznym.

Jest to właściwie podstawowy problem związany z funkcją. Masz to: DecodedRecords<'a, Box<Read>, Row>, F>

Zobacz, 'a! Ten typ pożycza coś. Problem polega na tym, że nie pożycza go od nakładów. Na wejściach nie ma wartości 'a.

Uświadomisz sobie, że pożycza od wartości, którą tworzysz podczas funkcji, a jej żywotność kończy się wraz z powrotem funkcji. We cannot return DecodedRecords<'a> from the function, because it wants to borrow a local variable.

Gdzie dalej? Moją najłatwiejszą odpowiedzią byłoby wykonanie tego samego podziału, co csv. Jedna część (Struct lub value), która jest właścicielem czytnika, i jedna część (struct lub value), która jest iteratorem i pożycza od czytelnika.

Być może skrzynka csv ma ​​dekoder posiadający własność czytnika, który przetwarza. W takim przypadku możesz użyć tego do rozwiania problemów związanych z pożyczkami.