byteorder icon indicating copy to clipboard operation
byteorder copied to clipboard

Feature request: Add write_uXX_from to WriteBytesExt trait

Open kacejot opened this issue 5 years ago • 4 comments

I write some kind of protocol parser and I use next code to read protocol data:

reader.read_u32_into::<LittleEndian>(&mut buffer);

I would like to use the same logic for writer:

writer.write_u32_from::<LittleEndian>(&buffer);

Is that already possible? Or it needs implementation?

kacejot avatar Nov 08 '19 16:11 kacejot

I think there would be fine to add, yes. I believe the necessary APIs for implementing this are already available on the ByteOrder trait.

BurntSushi avatar Nov 08 '19 17:11 BurntSushi

@BurntSushi, For now I see no other way except writing each chunk separately:

#[inline]
fn write_u16_from<T: ByteOrder>(&mut self, src: &[u16]) -> Result<()> {
    for &n in src {
        self.write_u16::<T>(n)?
    }

    Ok(())
}

The problem here is that we can get an error after some data is already written. In this case we can do nothing to roll back the writer to last stable state. It is done mostly like write_slice macro. The only difference is that write_slice is able to check if src fits dst and panic if it doesn't.

kacejot avatar Jan 20 '21 21:01 kacejot

If that's the only implementation choice, then we probably shouldn't provide it.

BurntSushi avatar Jan 20 '21 21:01 BurntSushi

You can do a bit better if you check if T is the same as NativeEndian or not; if it is, you can just cast the slice into bytes and use write_all.

It would look something a bit like

pub trait ByteOrderIo: ByteOrder {
  fn write_u16_from<W: Write + ?Sized>(writer: &mut W, src: &[u16]) -> Result<()>;
  // ...
}

impl ByteOrderIo for LittleEndian {
    #[cfg(target_endian = "little")]
    fn write_u16_from<W: Write + ?Sized>(writer: &mut W, src: &[u16]) -> Result<()> {
        let base = src.as_ptr() as *const u8;
        // SAFETY: I don't think len can overflow here, since creating a slice at all
        // requires len * size_of <= isize::MAX, but maybe do some splitting into parts
        // if that's a worry.
        let casted_slice = unsafe { std::slice::from_raw_parts(base, src.len() * std::mem::size_of::<u16>()) };
        writer.write_all(casted_slice)
    }

    #[cfg(not(target_endian = "little"))]
    pub fn write_u16_from<W: Write + ?Sized>(writer: &mut W, src: &[u16]) -> Result<()> {
        // But we could also batch calls to write_all here via using a larger buffer.
        let mut buf = [0u8; 2];
        for &n in src {
            Self::write_u16(&mut buf, n);
            writer.write_all(&buf)?;
        }
        Ok(())
    }
}

khoover avatar Feb 25 '23 20:02 khoover