Skip to content

Generalize output streams and input arguments with traits

Simon Sapin requested to merge topic/default/display into branch/default

This introduces a trait for arbitrary output byte streams, similar to std::fmt::Write and std::io::Write:

pub trait WriteBytes {
    type Error;
    fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), Self::Error>;
}

And a trait for values that can be formatted as bytes, similar to std::fmt::Display:

pub trait DisplayBytes {
    fn display_bytes<O>(&self, output: &mut O) -> Result<(), O::Error>
    where
        O: WriteBytes;
}

The format_bytes! macro is extended to accept arguments of any type that implements DisplayBytes. A write_bytes! macro is added that writes to an output stream of any type that implements WriteBytes (and format_bytes! is now built on top).


There are some tradeoffs in the design of these traits:

  • WriteBytes has an associated type for errors, where std::io and std::fmt have a single error type each. This lets for example Vec<u8> have an empty enum for errors, reflecting that Vec::extends does not have an error case. I feel it is interesting that this is possible, but perhaps not all that useful in practice? We could instead use std::io::Write directly and have std::io::Error everywhere.

  • display_bytes is generic over the output type, whereas std::fmt has a single Formatter type that internally holds a trait object. This allows propagating the associated error type, but makes DisplayBytes non-object-safe.

Merge request reports