Struggling to wrap a fmt::Formatter in io::Write

Viewed 531

I have a function that renders my DocObj to a fmt::Write:

impl DocObj {
    fn render(&self, write: &mut dyn Write) -> io::Result<()>;
}

To implement fmt::Display, I want to write the same bytes to the fmt::Formatter, without copying and pasting code.

I'm a newbie to rust, and I've tried a variety of solutions, but nothing works so far.

Here's what I've tried:

Strategy 1: Implement Write trait for std::Formatter

Fails because rust won't let me implement a trait for a type in another crate. Ok.

Strategy 2: Create a little wrapper for std::Formatter that implements Write

Fails because I don't understand lifetimes or rust well enough yet.

struct DisplayWriter<'a> {
    formatter: &'a fmt::Formatter<'a>
}

impl<'a> DisplayWriter<'a> {
    fn from_formatter(aformatter: &'a mut fmt::Formatter<'a>) -> DisplayWriter<'a> {
        DisplayWriter {formatter: aformatter}
    }
}

impl<'a> io::Write for DisplayWriter<'a> {
    fn write(&mut self, bytes: &[u8]) -> std::result::Result<usize, std::io::Error> {
        use std::fmt::Write;
        for c in bytes.iter() {
            match self.formatter.write_char(*c as char) {
                Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err))
            }
        }
        Ok(bytes.len())
    }
    fn flush(&mut self) -> std::result::Result<(), std::io::Error> { todo!() }
}

impl fmt::Display for DocObj {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let write = DisplayWriter::from_formatter(f);
        match self.render(&mut write) {
            Ok(_) => Ok(()),
            Err(err) => Err(fmt::Error)
        }
    }
}

With this code, the compiler complains with the error message:

error[E0623]: lifetime mismatch
   --> src\cos.rs:286:51
    |
285 |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    |                      -------------------
    |                      |
    |                      these two types are declared with different lifetimes...
286 |         let write = DisplayWriter::from_formatter(f);
    |                                                   ^ ...but data from `f` flows into `f` here

Some notes about my context:

  1. The bytes I want to display are not UTF-8 encoded. They are binary. I want to display one character for each byte. Therefore, any solution that does UTF encoding or decoding won't work in my context.
  2. The code is in a high performance loop, therefore I'd like to avoid allocating memory.

Thanks for your help!

3 Answers

std::Formatter already implements Write, so you can implement display by calling your render method directly:

impl Display for DocObj {
    fn fmt (&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.render (f).or (Err (fmt::Err))
    }
}

If you want to wrap a reference to a formatter in a struct, you will need to use different lifetimes for the reference and for the generic parameter:

struct DisplayWriter<'a, 'b> {
    formatter: &'a mut fmt::Formatter<'b>
}

Full code, including fixes for other errors you would get once you have the two lifetimes:

use std::{ fmt, io };

struct DocObj {}

impl DocObj {
    fn render(&self, write: &mut dyn io::Write) -> io::Result<()> { Ok (()) }
}

struct DisplayWriter<'a, 'b> {
    formatter: &'a mut fmt::Formatter<'b>
}

impl<'a, 'b> DisplayWriter<'a, 'b> {
    fn from_formatter(aformatter: &'a mut fmt::Formatter<'b>) -> Self {
        DisplayWriter {formatter: aformatter}
    }
}

impl<'a, 'b> io::Write for DisplayWriter<'a, 'b> {
    fn write(&mut self, bytes: &[u8]) -> std::result::Result<usize, std::io::Error> {
        use std::fmt::Write;
        for c in bytes.iter() {
            if let Err (err) = self.formatter.write_char(*c as char) {
                return Err(io::Error::new(io::ErrorKind::Other, err));
            }
        }
        Ok(bytes.len())
    }
    fn flush(&mut self) -> std::result::Result<(), std::io::Error> { todo!() }
}

impl fmt::Display for DocObj {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut write = DisplayWriter::from_formatter(f);
        match self.render(&mut write) {
            Ok(_) => Ok(()),
            Err(_) => Err(fmt::Error)
        }
    }
}

Playground

One way to implement a foreign trait on a foreign type is to use the NewType pattern. This pattern involves defining a 'tuple struct' containing only one element (in this case, the foreign type).

It is basically the same as the wrapper you tried to implement (that @Jmb finished for you), with less boilerplate:

struct DisplayWriter<'a, 'b>(&'a mut fmt::Formatter<'b>);

impl<'a, 'b> io::Write for DisplayWriter<'a, 'b> {
    fn write(&mut self, bytes: &[u8]) -> std::result::Result<usize, std::io::Error> {
        
        self.0.write_str(&String::from_utf8_lossy(bytes))
            .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;

        Ok(bytes.len())
    }
    fn flush(&mut self) -> std::result::Result<(), std::io::Error> { todo!() }
}

I also simplified the write implementation to use a functional style.

Then you can use it like this:

impl fmt::Display for DocObj {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.render(&mut DisplayWriter(f)) {
            Ok(_) => Ok(()),
            Err(_) => Err(fmt::Error)
        }
    }
}
Related