2015-05-27 3 views
5

Update: Der Titel des Beitrags wurde aktualisiert, und die Antwort wurde nicht mehr in Frage gestellt. Die kurze Antwort ist, dass Sie nicht können. Bitte sehen Sie meine Antwort auf diese Frage.Wie gebe ich einen Iterator zurück, der von einer Funktion generiert wird, die & mut self (wenn self lokal erstellt wird)?

Ich folge eine Fehlerbehandlung Blog-Post here (Github für sie here ist), und ich versuchte, einige Änderungen an dem Code zu machen, so dass die search Funktion einen Iterator anstelle eines Vec zurückgibt. Das war unglaublich schwierig und ich stecke fest.

Ich habe zu diesem Punkt aufgestanden:

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

mit dem folgenden Fehler vom Compiler:

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 

ich diese Funktion auch Definition versucht habe:

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

mit diesen Fehlern vom Compiler:

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) 

Ich kann nicht Box die Schließung, denn dann wird es nicht von filter_map akzeptiert werden.

Ich habe dann versucht, dies aus:

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

Welche mir diesen Fehler gibt:

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 

Habe ich etwas falsch ausgelegt, oder bin ich der falsche Ansatz? Fehle ich etwas wirklich Einfaches und Dummes? Ich bin mir nicht sicher, wohin ich von hier aus gehen soll.

+0

Können Sie erklären, warum dies kein Duplikat von [Richtiger Weg, um einen Iterator zurückzugeben] (http://stackoverflow.com/q/27535289/155423)? – Shepmaster

+0

Ich denke, Nashenas hat tatsächlich einen Iterator zurückbekommen, aber es gibt ein anderes Problem - ein lokales Borgen. – bluss

+0

@ bluss ist korrekt. Ich habe die Frage basierend auf dem, was ich erreichen wollte, benannt, aber es sieht so aus, als ob es basierend auf dem, was ich gelernt habe, umbenannt werden sollte. Was würdest du allen empfehlen? – Nashenas

Antwort

2

Diese Antwort basiert auf @bluss ‚s answer + Hilfe von #rust auf irc.mozilla.org

Ein Problem, das von dem Code nicht offensichtlich ist, und das der letzte Fehler gerade oben angezeigt verursacht wurde, hat mit der Definition von csv::Reader::decode zu tun (siehe source). Es dauert &'a mut self, die Erklärung dieses Problems ist in dieser answer abgedeckt. Dies führt im Wesentlichen dazu, dass die Lebensdauer des Lesers auf den Block begrenzt wird, in dem er aufgerufen wird. Die Lösung besteht darin, die Funktion aufzuteilen (da ich die Funktionsdefinition nicht steuern kann, wie in der vorherigen Antwort empfohlen). Ich brauchte eine Lebensdauer des Lesers, die innerhalb der main Funktion gültig war, so dass der Leser dann in die search Funktion weitergegeben werden konnte. Sehen Sie den Code unten (Es könnte auf jeden Fall gereinigt mehr werden):

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

Ich habe viel gelernt, versuchen, dies zu erhalten zu arbeiten, und haben viel mehr Wertschätzung für die Compiler-Fehler. Es ist jetzt klar, dass, wenn dies C gewesen wäre, der letzte Fehler darüber hinaus Segfaults verursacht haben könnte, die viel schwieriger zu debuggen wären. Ich habe auch erkannt, dass die Konvertierung von einem vorberechneten Vektor in einen Iterator mehr involviertes Denken darüber erfordert, wann der Speicher ein- und ausgeht; Ich kann nicht einfach ein paar Funktionsaufrufe und Rückgabetypen ändern und es einen Tag nennen.

8

Das Zurückgeben von Iteratoren ist möglich, aber es gibt einige Einschränkungen.

Um es ist möglich, zu zeigen zwei Beispiele, (A) mit expliziten Iteratortyp und (B) Boxen mit (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!(""); 
} 

Sie erleben ein Problem mit (A) - explizitem Typ! Unboxed-Closures, die wir aus regulären Lambda-Ausdrücken mit der Syntax |a, b, c| .. erhalten, haben eindeutige anonyme Typen. Funktionen benötigen explizite Rückgabetypen, so dass dies hier nicht funktioniert.

Einige Lösungen zum Rück Verschluss:

  • einen Funktionszeiger fn() wie in Beispiel (A) verwenden. Oft brauchen Sie sowieso keine Schließungsumgebung.
  • Box den Verschluss. Dies ist auch dann sinnvoll, wenn die Iteratoren das Aufrufen im Moment nicht unterstützen. Nicht deine Schuld.
  • Box den Iterator
  • Geben Sie eine benutzerdefinierte Iteratorstruktur zurück. Benötigt einige Textbausteine.

Sie können sehen, dass wir in Beispiel (B) ziemlich vorsichtig mit den Lebenszeiten sein müssen.Es sagt, dass der Rückgabewert Box<Iterator<Item=i32> + 'a> ist, was ist das 'a? Dies ist die geringste Lebensdauer, die von allem in der Box benötigt wird! Wir setzen auch die 'a gebunden an I::IntoIter - dies stellt sicher, dass wir das in die Box legen können.

Wenn Sie nur Box<Iterator<Item=i32>> sagen, wird davon ausgegangen, 'static.

Wir müssen die Lebensdauern des Inhalts unserer Box explizit erklären. Nur um sicher zu sein.

Dies ist eigentlich das grundlegende Problem mit Ihrer Funktion. Sie haben dies: DecodedRecords<'a, Box<Read>, Row>, F>

Sehen Sie das, ein 'a! Dieser Typ borgt etwas. Das Problem ist, es leiht es nicht aus den Eingaben. Es gibt keine 'a an den Eingängen.

Sie werden feststellen, dass es von einem Wert borgt, den Sie während der Funktion erstellen, und dass die Lebensdauer dieses Werts endet, wenn die Funktion zurückkehrt. We cannot return DecodedRecords<'a> from the function, because it wants to borrow a local variable.

Wohin gehen von hier? Meine einfachste Antwort wäre, den gleichen Split durchzuführen, den csv macht. Ein Teil (Struct oder value), dem der Leser gehört, und ein Teil (struct oder value), der der Iterator ist und vom Reader ausborgt.

Vielleicht hat die CSV-Kiste einen eigenen Decoder, der Besitz des Lesers übernimmt, die es verarbeitet. In diesem Fall können Sie das verwenden, um die Kreditprobleme zu zerstreuen.

Verwandte Themen