Thanks for all the comments. I ended up reconfiguring a bunch of my function parameters from taking a Write
to taking a Cursor<Vec<u8>>
instead. That allowed me to take advantage of the byteorder crate without worrying too much about the extra vector allocation.
fn write_u24_be(cursor: &mut Cursor<Vec<u8>>, value: u32) -> Result<()> {
debug_assert!(value <= 16777215, "Value is greater than what can fit in 3 bytes");
try!(cursor.write_u32::<BigEndian>(value));
{
let mut inner = cursor.get_mut();
let index_to_remove = inner.len() - 1 - 3;
inner.remove(index_to_remove);
}
try!(cursor.seek(SeekFrom::End(0)));
Ok(())
}
#[cfg(test)]
mod test {
use super::write_u24_be;
use std::io::Cursor;
use byteorder::WriteBytesExt;
#[test]
fn can_write_u24() {
let mut cursor = Cursor::new(Vec::new());
write_u24_be(&mut cursor, 16777215).unwrap();
// Make sure next writes are at the 4th byte
cursor.write_u8(8).unwrap();
assert_eq!(cursor.into_inner(), vec![255, 255, 255, 8]);
}
}
If profiling shows this being a critical hot path then I'll swap that function out for bitwise stuff.