What is the right way to choose output Writer


#1

I have a program where I am trying to allow the user to choose between writing to a file and writing to stdout. I’m used to this being very simple in other languages but I’m having a log of trouble with it in Rust. What is the right way to factor write_output() to choose between a file and stdout? I tried to create a factory function to return a Write trait but of course it isn’t Sized.

Thanks!

extern crate getopts;
use std::env;
use getopts::Options;
use std::io::{Write, BufWriter};
use std::fs::{File, OpenOptions};

#[derive(Debug, Default)]
struct Args {
    output: Option<String>,
}

fn parse_options() -> Args {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();
    let mut a = Args::default();

    let mut opts = Options::new();
    opts.optopt("o", "output", "output file name (stdout if not specified)", "NAME");
    opts.optflag("h", "help", "print this help menu");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    if matches.opt_present("h") {
        print_usage(&program, opts);
        std::process::exit(0);
    }
    a.output = matches.opt_str("o");

    a
}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} <options>\n\
                        You know, a classic.",
                        program);
    print!("{}", opts.usage(&brief));
}

fn write_hello_world<T: Write>(w: &mut T) {
    w.write(format!("hello world!\n").as_bytes()).unwrap();
}

/* this doesn't work
fn write_factory(args: &Args) -> Write {
    match args.output {
        Some(ref s) => {
            let f = OpenOptions::new().write(true)
                                      .create(true)
                                      .truncate(true)
                                      .open(s).unwrap();
            let mut w = BufWriter::new(f);
            w
        }
        None => {
            let mut w = BufWriter::new(std::io::stdout());
            w
        }
    }
} */

// TODO: refactor
fn write_output(args: &Args) {
    match args.output {
        Some(ref s) => {
            let f = OpenOptions::new().write(true)
                                      .create(true)
                                      .truncate(true)
                                      .open(s).unwrap();
            let mut w = BufWriter::new(f);
            write_hello_world(&mut w);
        }
        None => {
            let mut w = BufWriter::new(std::io::stdout());
            write_hello_world(&mut w);
        }
    }
}

fn main() {
    let args: Args = parse_options();

    write_output(&args);
}

#2

If you ever need to return something unsized, you just need a pointer. The pointer has a set size, so it can go on the stack. If you’re returning it from a function, Box<Write> is generally the right choice. When taking it to another function, you can take a borrowed pointer.

You can read more about this here: https://doc.rust-lang.org/book/trait-objects.html

As for refactoring help, I’ll leave that answer to those more talented than myself. I don’t want to give potentially poor advice.


#3

Thanks! That was what I was missing:

fn write_factory(args: &Args) -> Box<Write> {
    match args.output {
        Some(ref s) => {
            let f = OpenOptions::new().write(true)
                                      .create(true)
                                      .truncate(true)
                                      .open(s).unwrap();
            let mut w = BufWriter::new(f);
            Box::new(w)
        }
        None => {
            let mut w = BufWriter::new(std::io::stdout());
            Box::new(w)
        }
    }
}

fn write_output(args: &Args) {
    let mut w = write_factory(args);
    write_hello_world(&mut w);
}