paranoid_android
paranoid_android

Reputation: 77

Rust: can I have a fixed size slice by borrowing the whole fixed size array in a smaller scope in a simple way

I saw the workarounds and they where kinda long. Am I missing a feature of Rust or a simple solution (Important: not workaround). I feel like I should be able to do this with maybe a simple macro but arrayref crate implementations aren't what I am looking for. Is this a feature that needs to be added to Rust or creating fixed size slicing from fixed sized array in a smaller scope is something bad. Basically what I want to do is this;

fn f(arr:[u8;4]){
    arr[0];
}

fn basic(){
    let mut arr:[u8;12] = [0;12];
    // can't I borrow the whole array but but a fixed slice to it?
    f(&mut arr[8..12]); // But this is know on compile time?
    f(&mut arr[8..12] as &[u8;4]); // Why can't I do these things?
}

What I want can be achieved by below code(from other so threads)

use array_ref;

fn foo(){
    let buf:[u8;12] = [0;12];
    let (_, fixed_slice) = mut_array_refs![
        &mut buf,
        8,
        4
    ];
    write_u32_into(fixed_slice,0);
}

fn write_u32_into(fixed_slice:&mut [u8;12],num:u32){
    // won't have to check if fixed_slice.len() == 12 and won't panic
}

But I looked into the crate and even though this never panics there are many unsafe blocks and many lines of code. It is a workaround for the Rust itself. In the first place I wanted something like this to get rid of the overhead of checking the size and the possible runtime panic.

Also this is a little overhead it doesn't matter isn't a valid answer because technically I should be able to guarantee this in compile time even if the overhead is small this doesn't mean rust doesn't need to have this type of feature or I should not be looking for an ideal way. Note: Can this be solved with lifetimes?

Edit: If we where able to have a different syntax for fixed slices such as arr[12;;16] and when I borrowed them this way it would borrow it would borrow the whole arr. I think this way many functions for example (write_u32) would be implemented in a more "rusty" way.

Upvotes: 3

Views: 3460

Answers (2)

Deadbeef
Deadbeef

Reputation: 1681

Use let binding with slice_patterns feature. It was stabilized in Rust 1.42.

let v = [1, 2, 3]; // inferred [i32; 3]
let [_, ref subarray @ ..] = v; // subarray is &[i32; 2]
let a = v[0]; // 1
let b = subarray[1]; // 3

Here is a section from the Rust reference about slice patterns.

Upvotes: 7

Buzzec
Buzzec

Reputation: 567

Why it doesn't work

What you want is not available as a feature in rust stable or nightly because multiple things related to const are not stabilized yet, namely const generics and const traits. The reason traits are involved is because the arr[8..12] is a call to the core::ops::Index::<Range<usize>> trait that returns a reference to a slice, in your case [u8]. This type is unsized and not equal to [u8; 4] even if the compiler could figure out that it is, rust is inherently safe and can be overprotective sometimes to ensure safety.

What can you do then?

You have a few routes you can take to solve this issue, I'll stay in a no_std environment for all this as that seems to be where you're working and will avoid extra crates.

Change the function signature

The current function signature you have takes the four u8s as an owned value. If you only are asking for 4 values you can instead take those values as parameters to the function. This option breaks down when you need larger arrays but at that point, it would be better to take the array as a reference or using the method below.

The most common way, and the best way in my opinion, is to take the array in as a reference to a slice (&[u8] or &mut [u8]). This is not the same as taking a pointer to the value in C, slices in rust also carry the length of themselves so you can safely iterate through them without worrying about buffer overruns or if you read all the data. This does require changing the algorithms below to account for variable-sized input but most of the time there is a just as good option to use.

The safe way

Slice can be converted to arrays using TryInto, but this comes at the cost of runtime size checking which you seem to want to avoid. This is an option though and may result in a minimal performance impact.

Example:

fn f(arr: [u8;4]){
  arr[0];
}

fn basic(){
  let mut arr:[u8;12] = [0;12];
  f(arr[8..12].try_into().unwrap());
}

The unsafe way

If you're willing to leave the land of safety there are quite a few things you can do to force the compiler to recognize the data as you want it to, but they can be abused. It's usually better to use rust idioms rather than force other methods in but this is a valid option.

fn basic(){
  let mut arr:[u8;12] = [0;12];
  f(unsafe {*(arr[8..12].as_ptr() as *const [u8; 4])});
}

TL;DR

I recommend changing your types to utilize slices rather than arrays but if that's not feasible I'd suggest avoiding unsafety, the performance won't be as bad as you think.

Upvotes: 1

Related Questions