Idiomatic way of advancing and taking first of `&mut &[u8]`

Is there a way to perform the below more idiomatically?

let data: &mut &[u8] = ...
while !data.is_empty() {
    let first = data[0];
    data = &data[1..];
    // do something with data; we may need to perform `data = &data[n..];` here
}

The idea is that we have data.is_empty(), [0] and [1..] that perform quite similar operations. I was wondering if there is something more idiomatic than the above (e.g. in the sense that it produces less instructions)

1 Like

You are iterating over the data. You can simply call data.into_iter() and then do what you want to do.
Or maybe event for c in data { ... }.

1 Like

I need to advance more than one item at times. I also need to store slices of the data, so it is not possible to iterate item by item in this case since into_iter consumes the iterator

Can you write a rough pseudocode for what you want to do?

No idea if it is a particularly “good”/ideomatic way, but using split_first for the loop is an option, e.g.

let data: &mut &[u8] = ……;
while let Some((&first, rest)) = data.split_first() {
    *data = rest;

    // ……
}

and inside of the loop, there’s the option of doing

let slice;
(slice, *data) = data.split_at(n);

instead of

let slice = &data[..n];
*data = &data[n..];

I’ll leave it for others to judge if either of these approaches are improvements.

6 Likes
let mut data: &[u8] = ...;
while !data.is_empty() {
    let (first, rest) = data.split_at(1);
    data = rest;
    /* do stuff */
}

With destructuring assignments you could also shorten that to

let mut data: &[u8] = ...;
let mut chunk: &[u8] = &[];
while !data.is_empty() {
    (chunk, data) = data.split_at(1);
    /* do stuff */
}

That's essentially the only way to consume chunks of data at the lower level, but at the higher level there are alternatives, depending on what you need to do. For example, if you just need to handle fixed-sized chunks of data you could use

for chunk in data.chunks(chunk_size) { ... }

Or maybe you are doing deserialization. If your types support serde::{Serialize, Deserialize} and your format has something like

fn from_reader<'de, R: Read, T: Deserialize<'de>>(reader: R) -> Result<T>;

(most do), then you could do

let mut reader: &[u8] = ...;
let x: Foo = from_reader(&mut reader)?;
let y: Bar = from_reader(&mut reader)?;
/* etc */

This way the handling of slice chunking will be handled for you by the Read impl for &mut &[u8] and by the deserializer.

1 Like

How about destructuring with while let?

fn f(data:&mut &[u8]) {
    while let [first, rest @ .. ] = data {
        *data = rest;
        
        // do something with data; we may need to perform `data = &data[n..];` here
    }
}
9 Likes

Well, it's currently not stable, but it sounds like you're looking for exactly https://doc.rust-lang.org/nightly/std/primitive.slice.html#method.take_first

let mut data: &[u8] = b"Hello world";
while let Some(first) = data.take_first() {
    dbg!((first, data));
}

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=f21dfa1bd4883fcb2dd50242be853c01

It's a pretty simple method, so you could just copy the code into your own helper.

(And it uses split_first internally, like @steffahn 's solution did.)

5 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.