`byteorder`, array slices and mutability


I have a question about byteorder crate and array slices. In the following code parse_stream function is working properly:

fn parse_stream(input_stream : &[u8]) ->  Result<StreamData, std::io::Error> {
   // Slice incoming stream into an interesting part
   let mut stream = &input_stream [start..start+length]; // (1)
   // Do some reads
   let input1 = stream.read_u32::<LittleEndian>()?;
   let input2 = stream.read_u32::<LittleEndian>()?;
   let input3 = stream.read_u32::<LittleEndian>()?;
   // Borrow mutable u8 slice for further parsing
   parse_table(&mut stream);

fn parse_table(stream : &mut [u8]) -> Option<Table> {
   let input = stream.read_u32::<LittleEndian>().unwrap();  // (2)

But passing stream down to another function generates compilation error at line (2):

error[E0599]: no method named `read_u32` found for type `&mut [u8]` in the current scope
   --> src\metadata\root.rs:166:33
166 |         let input = stream.read_32::<LittleEndian>().unwrap();
    |                                 ^^^^^^^^
    = note: the method `read_u32` exists but the following trait bounds were not satisfied:
            `&mut [u8] : byteorder::ReadBytesExt`
            `[u8] : byteorder::ReadBytesExt`

How to interpret and fix it? Why read_u32 works in parse_stream on mutable &[u8] and does not work on &mut [u8] in parse_table? I'm probably missing something obvious here...

The byteorder::ReadBytesExt trait is implemented for every type that has std::io::Read implemented for it. std::io::Read is implemented for &[u8] but not &mut [u8], which you can see here in the "implementors" section: https://doc.rust-lang.org/std/io/trait.Read.html

I guess if stream is a &mut [u8], then you should be able to re-borrow it as a &[u8] via &*stream. Or perhaps by wrapping it in a std::io::Cursor.

1 Like

Ok, I see the reason. As std::io::Read is not implemented for &mut [u8] but for &[u8], this code does compile and solves my problem:

fn parse_table(mut stream : &[u8]) -> Option<Table> {
   let input = stream.read_u32::<LittleEndian>().unwrap();  // (2)

Although, as I am new to Rust, understanding the difference between mut stream : &[u8] and stream :&mut [u8] is by far not clear...

This impl is indeed tricky, and confused me a lot when I first saw it.

The key here is to realize that all the Read::read_* methods (which byteorder calls) operate on a &mut T, i.e. in this case a &mut &[u8]. This allows them to change where your slice is pointing at.

You can imagine the slice as being a struct (ignoring the lifetime tracked by the compiler)

struct Slice {
    addr: <memory address>,
    size: usize

Since Read::read gets a &mut Slice, it can update the addr and size members after advancing a certain amount.

To answer your final point: the binding has to be mut since where stream points to has changed fter reading. The slice itself doesn't since nobody is changing the u8 data it points to.


I hope the following diagram helps (red indicates mutability):

In the case of Read, the cursor (i.e. the "arrows") needs to adjust and advance at each read, but the contents of the slice are just read.


This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.