Generic writer (File & String)

I have a function which may generate a small buffer. The caller can:

  • not care about this buffer
  • want the buffer (String)
  • want to store the buffer to a file

How would you go about accomplishing this?

My first thought was something like:

fn do_something<T: SomeWriterTrait>(out: Option<T>) {
   if let Some(out) = out {
    out.write(the_buffer);
  }
}

But I'm unsure what trait I should be using which can write to something that can be extracted as a String as well as be written to a file.

Any tips?

Best solution is to use ::std::io::Write, but then you won't be able to use String directly since it requires the emitted / written bytes be valid UTF-8, which is not something the io::Write trait requires or enforces.

So you need to drop the String requirement, and use a Vec<u8> instead. Later on, if you want back a String , you can upgrade the Vec<u8> back to a String using one of the functions dedicated to that purpose (which either fail or perform a lossy conversion when encountering invalid UTF-8 sequences):

use ::std::string::FromUtf8Error; // Contains the `Vec<u8>` inside.

trait StringWithMutBytes {
    fn with_mut_bytes<R> (
        self: &'_ mut Self,
        f: impl FnOnce(&'_ mut Vec<u8>) -> R,
    ) -> Result<(), FromUtf8Error>
    ;
}
impl StringWithMutBytes for String {
    fn with_mut_bytes<R> (
        self: &'_ mut Self,
        f: impl FnOnce(&'_ mut Vec<u8>) -> R,
    ) -> Result<R, Vec<u8>>
    {
        let mut vec: Vec<u8> = ::core::mem::take(self).into();
        let ret = f(&mut vec);
        *self = String::from_utf8(vec)?;
        Ok(ret)
    }
}

this way, you can write:

use ::std::io;

fn do_something (out: &'_ mut dyn io::Write)
  -> io::Result<()>
{
    let the_buffer = b"World!";
    out.write_all(the_buffer)
}

Possible callers:

// String
let mut s = String::new("Hello, ");
s.with_mut_bytes(|v| Ok({
    do_something(v)?;
}))??;
assert_eq!(s, "Hello, World!");
// File
let mut file = File::create("…");
do_something(&mut file)?;
drop(file);
assert_eq!(::std::fs::read_to_string("…")?, "World!");
// Dummy
do_something(&mut io::sink()); // dummy out
1 Like