Generalize output streams and input arguments with traits
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, wherestd::io
andstd::fmt
have a single error type each. This lets for exampleVec<u8>
have an empty enum for errors, reflecting thatVec::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 usestd::io::Write
directly and havestd::io::Error
everywhere. -
display_bytes
is generic over the output type, whereasstd::fmt
has a singleFormatter
type that internally holds a trait object. This allows propagating the associated error type, but makesDisplayBytes
non-object-safe.