Write to stdout, stderr or file

Let's say I have a function which outputs some text in a formatted manner (a pile of string for all intents and purposes) and I want to allow the caller to be able to choose where to output it; at least stdout, stderr and a file descriptor. (But the more merrier).

In C terms I'm looking for something equivalent to a file descriptor or FILE*, and in C++ something akin to std::ostream&

I.e. I'm wondering if there's some nifty way to do something akin to this:

impl MyFoo {
  fn dump_info(out: &std::Writer) {
    // .. 'out' backend could be mapped to any file descriptor ..
    out.write("blah, blah, blah");
  }
}
  • C's FILE * would be Rust's File, although it is less versatile.

  • So you need something like "an object with virtual methods". In Rust, this can be achieved by erasing a concrete type (e.g., Stdout, Stderr, the aforementioned File) into a type describing the common behavior (all three implement the trait io::Write), that is, the trait object type: dyn io::Write.

In order to perform dynamic dispatch, the trait object needs to be behind some form of indirection. To store it without borrowing it, it requires Boxing the element into the heap: your struct could thus then hold a Box<dyn io::Write>.

If you just need it as a parameter to a function, such as with your dump_info example, then you can make the function generic over all the types that are Write-able:

#![deny(bare_trait_objects)]

use ::std::io::{self, Write};

impl MyFoo {
    /// function can be fed a `&mut Stdout`, a `&mut File`, and also a `&mut dyn Write`
    fn dump_info (out: &mut impl Write + ?Sized)
    {
        out.write("blah, blah, blah");
    }
}

or, for slightly faster compile times and slighlty smaller binaries, you can just use one monomorphization of the above generic function: the case using dyamic dispatch (since writing is an expensive operation anyways, this is one case where the virtual method cost is not a problem. But for other traits, prefer the generic approach whenever possible):

#![deny(bare_trait_objects)]

use ::std::io::{self, Write};

impl MyFoo {
    /// function can only be fed a `&mut dyn Write`,
    /// but luckily `&mut Stdout`, `&mut File`, etc. coerce to `&mut dyn Write`
    fn dump_info (out: &mut dyn Write)
    {
        out.write("blah, blah, blah");
    }
}
  • which is @leudz's example (except they have used the deprecated trait object notation without dyn ).
2 Likes

This is the pattern I often use when writing command-line programs:

use std::env;
use std::fs::File;
use std::io::{self, Error, Write};
use std::path::PathBuf;

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

    let mut output = args.get_output().unwrap();

    writeln!(output, "Hello, World!").unwrap();
}

#[derive(Debug)]
struct Args {
    /// write to the provided file, or `stdout` when not provided
    output: Option<PathBuf>,
}

impl Args {
    // I'd usually use clap or structopt for command-line argument parsing...
    fn from_env() -> Args {
        Args {
            output: env::args().nth(1).map(PathBuf::from),
        }
    }

    fn get_output(&self) -> Result<Box<dyn Write>, Error> {
        match self.output {
            Some(ref path) => File::open(path).map(|f| Box::new(f) as Box<dyn Write>),
            None => Ok(Box::new(io::stdout())),
        }
    }
}
4 Likes

I don't know if it matches your requirements but notice that you can always map stdout and stderr to what ever you want at starting your program, e.g.:

$> my_binary > output.txt >> error.txt

So you can always output on stdout (e.g. print!()) and as required write to console or to a file or you can even use a pipe to map stdout to stdin of a second program:

$> my_binary | consumer_of_binary_output

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.