2016-03-18 13 views
6

Sto tentando di implementare un enum "polimorfo" Input che nasconde se stiamo leggendo da un file o da uno stdin. Più concretamente, sto provando a costruire un enum che avrà un metodo lines che a sua volta "delegherà" quella chiamata a uno File impacchettato in uno BufReader oa uno StdInLock (entrambi hanno il metodo lines()).Come fare l'IO polimorfo (file o stdin) in Rust?

Ecco l'enum:

enum Input<'a> { 
    Console(std::io::StdinLock<'a>), 
    File(std::io::BufReader<std::fs::File>) 
} 

Ho tre metodi:

  • from_arg per decidere se stiamo leggendo da un file o da uno stdin controllando se un argomento (nome del file) è stato a condizione,
  • file per avvolgere un file con un BufReader,
  • console per Locki ng lo stdin.

L'implementazione:

impl <'a> Input<'a> { 

    fn console() -> Input<'a> { 
     Input::Console(io::stdin().lock()) 
    } 

    fn file(path: String) -> io::Result<Input<'a>> { 
     match File::open(path) { 
      Ok(file) => Ok(Input::File(std::io::BufReader::new(file))), 
      Err(_) => { panic!("kita") } 
     } 
    } 

    fn from_arg(arg: Option<String>) -> io::Result<Input<'a>> { 
     Ok(match arg { 
      None  => Input::console(), 
      Some(path) => try!(Input::file(path)) 
     }) 
    } 
} 

Per quanto ho capito, devo implementare bot BufRead e Read tratti per far funzionare tutto questo. Questo è il mio tentativo:

impl <'a> io::Read for Input<'a> { 
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { 
     match *self { 
      Input::Console(ref mut c) => c.read(buf), 
      Input::File(ref mut f)  => f.read(buf), 
     } 
    } 
} 

impl <'a> io::BufRead for Input<'a> { 
    fn lines(self) -> Lines<Self> { 
     match self { 
      Input::Console(ref c) => c.lines(), 
      Input::File(ref f)  => f.lines() 
     } 
    } 

    fn consume(&mut self, amt: usize) { 
     match *self { 
      Input::Console(ref mut c) => c.consume(amt), 
      Input::File(ref mut f) => f.consume(amt) 
     } 
    } 

    fn fill_buf(&mut self) -> io::Result<&[u8]> { 
     match *self { 
      Input::Console(ref mut c) => c.fill_buf(), 
      Input::File(ref mut f) => f.fill_buf() 
     } 
    } 
} 

Infine, l'invocazione:

fn load_input<'a>() -> io::Result<Input<'a>> { 
    Ok(try!(Input::from_arg(env::args().skip(1).next()))) 
} 

fn main() { 
    let mut input = match load_input() { 
     Ok(input) => input, 
     Err(error) => panic!("Failed: {}", error), 
    }; 


    for line in input.lines() { 
     /* do stuff */ 
    } 
} 

Il problema è che il compilatore mi dice che io sono il pattern matching torto e che ho mismatched types. Ho provato a soddisfarlo con:

match self { 
    Input::Console(std::io::StdinLock(ref c)) => c.lines(), 
    Input::File(std::io::BufReader(ref f)) => f.lines() 
} 

... ma anche questo non funziona.

Nel primo caso sto ottenendo l'errore mismatched types:

poly_input.rs:43:40: 43:49 error: mismatched types: 
expected `std::io::Lines<Input<'a>>`, 
    found `std::io::Lines<std::io::stdio::StdinLock<'_>>` 
(expected enum `Input`, 
    found struct `std::io::stdio::StdinLock`) [E0308] 
poly_input.rs:43    Input::Console(ref c) => c.lines(), 
                 ^~~~~~~~~ 

, e nel secondo caso un errore unresolved variant:

poly_input.rs:45:29: 45:47 error: unresolved enum variant, struct or const `StdinLock` [E0419] 
poly_input.rs:45    Input::Console(std::io::StdinLock(ref c))   => c.lines(), 

Sono davvero fuori dalla mia profondità qui, sembra.

+0

L'approccio corrente non funziona poiché 'StdinLock' contiene un riferimento a un oggetto' Stdin'. –

+0

Potresti approfondire un po 'se hai tempo? Grazie. – neektza

risposta

9

Questa è la soluzione più semplice ma prenderà in prestito e bloccherà Stdin.

use std::io::{self, Read, BufRead}; 
use std::fs::File; 

struct Input<'a> { 
    source: Box<BufRead + 'a> 
} 

impl<'a> Input<'a> { 
    fn console(stdin: &'a io::Stdin) -> Input<'a> { 
     Input { source: Box::new(stdin.lock()) } 
    } 

    fn file(path: &str) -> io::Result<Input<'a>> { 
     File::open(path) 
      .map(|file| Input { source: Box::new(io::BufReader::new(file)) }) 
    } 
} 

impl<'a> Read for Input<'a> { 
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { 
     self.source.read(buf) 
    } 
} 

impl<'a> BufRead for Input<'a> { 
    fn fill_buf(&mut self) -> io::Result<&[u8]> { 
     self.source.fill_buf() 
    } 
    fn consume(&mut self, amt: usize) { 
     self.source.consume(amt); 
    } 
} 

causa di metodi di tratto di default, Read e BufRead saranno a regime per Input. Quindi puoi chiamare lo lines al numero Input.

let input = Input::file("foo.txt").unwrap(); 
for line in input.lines() { 
    println!("input line: {:?}", line); 
} 
+0

Grazie mille. – neektza