`byteorder`, array slices and mutability

Hi,

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: Read in std::io - Rust

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.

2 Likes

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.

4 Likes

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