Does split_at require let?

I am trying to write code that repeatedly splits a slice into smaller pieces based on the data in the splice, as below:

fn my_fn(data: &'a [u8]) -> ... { // say data is 100 bytes long
    let (foo, data) = data.split_at(4); // now the last 96 bytes alone are in data
    let (bar, data) = data.split_at(2); // now data has 94 bytes, etc. 
    if some_cond(foo, bar) {
          let (baz, data) = data.split_at(6);
          // ... use baz ...
    }
    data // return what's left of the original slice named data
}

The above is not right, because the let essentially re-declares the variable data. So, rustc correctly points out that the variable data inside the some_cond check is not being used. However, when I remove the 'let', I get the error:

left-hand of expression not valid

The usage examples of split_at invariably use 'let'.

I am getting around this by doing:

let mut data = data;
if some_cond(foo, bar) {
   let (baz, data1) = data.split_at(6);
   data = data1;
   // ... use baz ... 
}

This seems clunky. Is there a better way?

You can create helper trait.

trait Bite<'a> {
    fn bite(&mut self, len: usize) -> &'a [u8];
}

impl<'a> Bite<'a> for &'a [u8] {
    fn bite(&mut self, len: usize) -> &'a [u8] {
        let (ret, rest) = self.split_at(len);
        *self = rest;
        ret
    }
}

Then your code becomes

fn my_fn(mut data: &[u8]) -> &[u8] { // say data is 100 bytes long
    let foo = data.bite(4); // now the last 96 bytes alone are in data
    let bar = data.bite(2); // now data has 94 bytes, etc. 
    if some_cond(foo, bar) {
          let baz = data.bite(6);
          println!("{:?}", baz);
    }
    data // return what's left of the original slice named data
}

Playground

1 Like

Good idea. Thanks, will try.

You should be able to write

let (foo, _) = data.split_at(6);
1 Like

In fact, that already exists in the standard library, more or less, in the form of impl<'a> Read for &'a [u8]. (see Read)

1 Like

Yes. However there's runtime overhead and additional complexity when dealing with Results. Of course, which is better depends on the task.

After some fun with the borrow checker and lifetimes, I went back to basics and tried this out by itself in the playground.

It fails because the original buffer does not get chopped. The "*self = rest" should have done that but does not, for some reason. The code is:

   let buf: Vec<u8> = vec![0x02, 0x04, 0x06, 0x08];
   let chunk = buf.as_slice().bite(2);
   println!("chunk: {:?} buf: {:?}", chunk, buf);

First, it is surprising that it compiles because buf is immutable and as_slice() returns an immutable slice, whereas bite() expects a mutable one.

Secondly, the output I actually see is:

rest: [6, 8]
chunk: [2, 4] buf: [2, 4, 6, 8]

which clearly shows that buf has not been modified.

Making buf mutable does not change the outcome.

Thanks in advance.

You are biting from temporary slice created by as_slice, buf is not affected. You need to create a variable for a slice to bite from. gist

1 Like