Search code examples
rustrust-rustlings

Rustlings slice primitives


One of the rustlings exercises on primitive is about slices.

When I tried to solve this exercise I started by using the [start...end] syntax to take a slice into the given array as such

fn slice_out_of_array() {
    let a: [u8; 5] = [1, 2, 3, 4, 5];
    let nice_slice = a[1..4];
    assert_eq!([2, 3, 4], nice_slice)
}

The compiler complains and tells me:

error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
  --> exercises/primitive_types/primitive_types4.rs:10:9
   |
10 |     let nice_slice = a[1..4];
   |         ^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `[u8]`
   = note: all local variables must have a statically known size
   = help: unsized locals are gated as an unstable feature
help: consider borrowing here
   |
10 |     let nice_slice = &a[1..4];
   |                      +

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.

running rustc --explain E0277 doesn't really answer my question. It explains what it means for an argument not to implement a trait when the function specifies it should. That is clear to me, but i am not completely clear on what is going wrong here.

This is what i think is happening, but I'd like to hear other's opinions.

  1. The compiler can't figure out from the slice syntax how big the resulting slice will be and so it can't allocate the proper space on the stack.

  2. By default slices are primitives and live on the stack instead of the heap.

  3. If I take the suggestion and add & the compiler pushes on the stack a reference -- which has a known size -- to the original array already on the stack. This solves the problem.

Is this correct? Is there a case where i can take a slice and not add the & symbol?


Solution

  • The compiler can't figure out from the slice syntax how big the resulting slice will be and so it can't allocate the proper space on the stack.

    This is true. We say that slices are Dynamically Sized Types (DSTs), or that they are not Sized, or that they do not implement the Sized trait (this is the reason rustc gives you E0277, type does not implement trait).

    By default slices are primitives and live on the stack instead of the heap.

    This is not precise: slices are primitives, but Rust primitives, like any other object, can also live on the heap. It is just that you are trying to move it, so the compiler needs to reserve space for it on the stack.

    If I take the suggestion and add & the compiler pushes on the stack a reference -- which has a known size -- to the original array already on the stack. This solves the problem.

    Yes. Essentially, the compiler just offsets the address and shorten the length. A reference to slice is a pair of (address, length).

    Is there a case where i can take a slice and not add the & symbol?

    Yes and no.

    No, because you always take a reference of some kind. All DSTs can only come behind an indirection (a reference, a Box, a Rc...).

    But also yes, because you does not always need to add the & symbol. Sometimes the compiler is doing it for you: when you call a method on the slice, thanks to autoref:

    fn slice_out_of_array() {
        let a: [u8; 5] = [1, 2, 3, 4, 5];
        a[1..4].is_empty(); // Just an example. Equivalent to:
        (&a[1..4]).is_empty();
    }
    

    You can also moves it to the heap (but this also takes a reference under the hood):

    fn slice_out_of_array() {
        let a: [u8; 5] = [1, 2, 3, 4, 5];
        let b: Box<[u8]> = a[1..4].into();
    }