Does split_at require let?


#1

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?


#2

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


#3

Good idea. Thanks, will try.


#4

You should be able to write

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

#5

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


#6

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


#7

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.


#8

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