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 ).
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: