Copy a portion of a byte_array slice known size, mismatched types [no_std]

NOTE: There are multiple solutions to this problem. I'd recommend reading the entire discussion.

I have a byte array slice like [12, 63, 0, 0, 4, 0, 62] and need to pull out the values at indicies 2, 3, 4, and 5 and then convert that sub-slice into an i32.

Some things to note:

  1. I do not know the size of the bigger byte array and comp time
  2. I do not know the starting index of the 4 bytes of interest at comp time
  3. I DO know that it will always be exactly 4 bytes that I need to convert into an i32
  4. The larger byte stream is actually a heapless::Vec if that matters.
  5. This is in a no_std environment.

I have tried two things and gotten size errors each time.

Simple Slice

let byte_stream = heapless::Vec<u8, 20>::from_slice(&[12, 63, 0, 0, 4, 0, 62]);

let starting_index = 2; // discovered at runtime
let ending_index = 6; // discovered at runtime, exclusive

let value_bytes: &[u8; 4] = &byte_stream[starting_index..ending_index];
let value = i32::from_be_bytes(*value_bytes);

This fails with:

error[E0308]: mismatched types
  --> src/main.rs:20:33
   |
20 |     let value_bytes: &[u8; 4] = &byte_stream[starting_index..ending_index];
   |                      --------   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected array `[u8; 4]`, found slice `[u8]`
   |                      |
   |                      expected due to this
   |
   = note: expected reference `&[u8; 4]`
              found reference `&[u8]`

which makes sense. But, when I try let value_bytes: &[u8; 4], then I get the same mismatched types error, just at i32::from_be_bytes(*value_bytes);

Building an array

    let byte_stream: Vec<u8, 20> = Vec::from_slice(&[12, 63, 0, 0, 4, 0, 62]).unwrap();

    let starting_index = 2; // discovered at runtime
    let ending_index = 6; // discovered at runtime, exclusive

    let mut value_bytes: &[u8; 4] = &[0; 4];

    let mut i: usize = 0;
    for byte in &byte_stream[starting_index..ending_index] {
        value_bytes[i] = *byte;
        i += 1;
    }

    let value = Some(i32::from_be_bytes(*value_bytes));

    println!("{:?}", value);

This errors with

error[E0594]: cannot assign to `value_bytes[_]` which is behind a `&` reference
  --> src/main.rs:27:9
   |
23 |     let mut value_bytes: &[u8; 4] = &[0; 4];
   |                                     ------- help: consider changing this to be a mutable reference: `&mut [0; 4]`
...
27 |         value_bytes[i] = *byte;
   |         ^^^^^^^^^^^^^^^^^^^^^^ `value_bytes` is a `&` reference, so the data it refers to cannot be written

But value_bytes IS mutable?

What am I missing? Thanks!

Well, I think I just solved my own problem. I'll post it here for googlers.

    let byte_stream: Vec<u8, 20> = Vec::from_slice(&[12, 63, 0, 0, 4, 0, 62]).unwrap();

    let starting_index = 2; // discovered at runtime
    let ending_index = 6; // discovered at runtime, exclusive
    let value_bytes: &mut [u8; 4] = &mut [0; 4];

    let mut i: usize = 0;
    for byte in &byte_stream[starting_index..ending_index] {
        value_bytes[i] = *byte;
        i += 1;
    }

    let value = Some(i32::from_be_bytes(*value_bytes));

The issue was let value_bytes: &mut [u8; 4] = &mut [0; 4];

But when do we use mut in the reference vs the variable creation?

Also, there has to be a better way.

Three steps:

  • subslice it so that you have a slice of length 4
  • .try_into().unwrap() to convert the slice-of-length-4 to an array-of-4-items
  • use i32::from_[lbn]e_bytes to get the i32
    let byte_stream: Vec<u8> = vec![12, 63, 0, 0, 4, 0, 62];

    let starting_index = 2; // discovered at runtime
    let ending_index = 6; // discovered at runtime, exclusive

    let value = Some(i32::from_be_bytes(byte_stream[starting_index..ending_index].try_into().unwrap()));

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

3 Likes

Well, that is SO much cleaner than my solution. Thanks!

You could have used let mut value_bytes = [0_u8; 4];. There was really no need for the reference at all. The let mut value_bytes: &[u8; 4] = ... approach didn't work because it allowed you to modify the reference (e. g. make it point to some other place) but not modify the data behind the reference.

(Of course the solution by scottmcm is cleaner, just commenting on this because you asked.)

Also, in your solution you could have also replaced the for loop by a call to value_bytes.copy_from_slice(&byte_stream[starting_index..ending_index])

I really appreciate the suggestions. That all makes sense.

Well, @scottmcm's solution did work until I turned on my no_std flag. Unfortunately the try_into() is a standard only function. This does not solve my problem. However, my original solution does, and its made even cleaner by @steffahn's suggestion.

Afaict, the relevant trait and implementation are part of core, so it should work on no_std. TryInto is also part of the core prelude, at least on edition 2021. Code like this compiles fine, and in older editions you just need to add use core::convert::TryInto; to bring the try_into method into scope.

1 Like

That does indeed work, I just needed to import use core::convert::TryInto; instead

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.