0

I'm creating some ports based on arguments, but the ports don't live long enough to be passed to the next function, any lifetime-fu to be done? Better still, a way to adapt this to statically dispatch?

fn init<'a>(matches: getopts::Matches) {
    let in_port: &mut Read = match matches.opt_str("i") {
        Some(filename) =>  &mut File::open(filename).expect("Couldn't open input file.") as &mut Read,
        _ => &mut io::stdin() as &mut Read,
    };
    let out_port: &mut Write = match matches.opt_str("o") {
        Some(filename) => &mut File::create(filename).expect("Couln't open output file") as &mut Write,
        _ => &mut io::stdout() as &mut Write,
    };
    run(in_port, out_port);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
414owen
  • 791
  • 3
  • 13

3 Answers3

1

You can use static dispatch by calling the run function directly in every match arm:

use std::fs::File;
use std::io::{Read, Write};

fn init(i: Option<&str>, o: Option<&str>) {
    match i {
        Some(filename) => init2(File::open(filename).expect("Couldn't open input file."), o),
        None => init2(std::io::stdin(), o),
    }
}

fn init2<R: Read>(i: R, o: Option<&str>) {
    match o {
        Some(filename) => run(i, File::create(filename).expect("Couldn't open output file")),
        None => run(i, std::io::stdout()),
    }
}

fn run<R: Read, W: Write>(i: R, o: W) {
    unimplemented!()
}
oli_obk
  • 28,729
  • 6
  • 82
  • 98
1

The simplest solution would be to Box your objects, putting them in the heap.

I would personally prefer to separate init and run, so it means returning them:

fn init(matches: Matches) -> (Box<Read>, Box<Write>) {
    let in_port: Box<Read> = match matches.opt_str("i") {
        Some(filename) =>  Box::new(File::open(filename).expect("Couldn't open input file.")),
        _ => Box::new(stdin()),
    };
    let out_port: Box<Write> = match matches.opt_str("o") {
        Some(filename) => Box::new(File::create(filename).expect("Couln't open output file")),
        _ => Box::new(stdout()),
    };
    (in_port, out_port)
}
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
1

The problem is that you're trying to return a reference to something you're about to destroy:

let in_port: &mut Read = match matches.opt_str("i") {
    Some(filename) =>  &mut File::open(filename).expect("Couldn't open input file.") as &mut Read,
    _ => &mut io::stdin() as &mut Read,
};

Inside the block, the created File is a temporary which only lasts as long as the expression it's in. I assume you're using references rather than values so that you can hide the concrete type behind a trait object. One way would be to us Box<Trait>, which would own the object.

fn init<'a>(i: Option<&str>, o: Option<&str>) {
    let mut in_port: Box<Read> = match i {
        Some(filename) =>  Box::new(File::open(filename).expect("Couldn't open input file.")),
        _ => Box::new(io::stdin()),
    };
    let mut out_port: Box<Write> = match o {
        Some(filename) => Box::new(File::create(filename).expect("Couln't open output file")),
        _ => Box::new(io::stdout()),
    };
    run(&mut in_port, &mut out_port);
}

(playground)

Chris Emerson
  • 13,041
  • 3
  • 44
  • 66