I have a struct that can be converted to text through a complex series of method calls which contain numerous write! calls. This text can be written to a file or to a debug log. I am trying to decide whether to use fmt::Write or io::Write. I can't really use both because then all of the writing methods would need to be duplicated.
Simplified example:
impl MyType {
fn write_it(&self, writer: &mut impl ???) {
// ^ fmt::Write or io::Write?
self.write_header(writer);
self.write_contents(writer);
self.write_footer(writer);
}
fn write_header(&self, writer: &mut impl ???) {
write!(writer, "...")
}
// and more...
}
The docs for fmt::Write say,
...This is similar to the standard library's io::Write trait, but it is only intended for use in libcore.
So this leads me to believe I should use io::Write. This (obviously) will work well for I/O types like BufWriter. And it seems notable that serde_json does it this way.
// if I use io::Write, I can do this
my_type.write_it(&mut BufWriter::new(File::create("my-file.txt")?)?;
I also want to use my type with format! and similar macros. So I need to implement Display. In fact, isn't Display the de-facto trait for a type that can be represented as a String?
// I want to do this
println!("Contents:\n{}", my_type);
// or this
let mut s = String::new();
write!(s, "{}", my_type);
So I think I'll just tie in my implementation with Display. But here's the problem:
impl Display for MyType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.write_it(f)?;
// ^ the trait `std::io::Write` is not implemented for `std::fmt::Formatter<'_>`
}
}
I can fix this by using impl fmt::Write, but then I lose the I/O capability mentioned before. There is no adapter between the two types (I assume there would be one if that were an intended usage). So I am caught between the two. Which trait should I use? Is there an idiomatic way to get the functionality of both?
Edit: Adapter?
@Ă–merErden suggested creating an adapter like the following. I'm just not sure what conditions will cause the UTF-8 conversion to fail. Is that conversion guaranteed to not fail as long as I write valid UTF-8 with write!? It seems too risky to me.
struct Adapter<'a> {
f: &'a mut dyn fmt::Write,
}
impl<'a> io::Write for Adapter<'a> {
fn write(&mut self, b: &[u8]) -> Result<usize, io::Error> {
let s = str::from_utf8(b)
.map_err(|_| io::Error::from(io::ErrorKind::Other))?;
self.f.write_str(s)
.map_err(|_| io::Error::from(io::ErrorKind::Other))?;
Ok(b.len())
}
fn flush(&mut self) -> Result<(), io::Error> {
Ok(())
}
}