Referencing Array Slice in Struct requires reference?

I am curious about the reason behind this error. I have struct which has an a fixed size array as a field. Something like

struct SomeStruct {
array : [u8; 16],
}

I also have a trait to do some operations on a instance of the struct:

impl SomeStruct {
    fn modify(&mut self) {
       for i in self.array[0..8] {
          do_something();
        }
    }

}

This does not work. I need to write the code as:

for i in &self.array[0..8] {
    do_something();
}

otherwise I get an error as such:

error[E0277]: the size for values of type `[u8]` cannot be known at compilation time

I am unsure as to why this would be a problem. I have specified the size of the array for compile time. How does it work if it becomes a reference?

You index into your array with a range. self.array[0..8] returns a slice (size unknown during compile time), not an array

5 Likes

Rust considers indexing expressions like [0..8] to be entirely runtime-calculated index values. You might as well have used two random values x and y and written self.array[x..y], so the size of this part of the array is completely unknown; hence it’s represented as the slice type [u8] which can only be interacted with in Rust if its kept behind some indirection.

So the expression &self.array[0..8] works fine. (Don’t think of the self.array[0..8] part of this expression as evaluating to it’s own thing, on a first approximation, the &foo[ix] syntax stands for a single step/operation at run-time).

Iterating over &[T] with a for loop works, too, due to its IntoIterator implementation. The items i will be of type &T, in this case &u8. If you’d like to have them by-value, you might as well write for &i in &self.array[0..8] to pattern match the level of reference away.

Analogous operations are possible with mutable references, too, so something like

for i in &mut self.array[0..8] {
    *i += 1;
}

would increment the first 8 elements of array.

4 Likes

I'm curious, why does Rust consider self.array[0..8] as a unknown sized slice? Is there a reason why the compiler cannot see the range mentioned? Or are there some design reasons for this?

I'm also still unsure why does the indirection via reference works. Why would the compiler let you do that?

I tried to explain everything above. Try to ask a more specific question, what part you don’t understand :slight_smile:

I assume your question is why the compiler isn’t “smart enough” so see the compile-time-known indices here. The answer is: It’s hard to offer this via the same API without potentially getting some confusing or inconsistent behavior. Whether something is or isn’t “compile-time-known” would presumably be more of a heuristic result than a clear rule, on the other hand the return type ([T] vs [T; N]) would depend on that, and types should be more predictable.

Also, changing it now could lead to breakage as [T] and [T; N] are – though usable interchangably in many contexts – distinct types. The only reasonably action would be to introduce a new syntax or method for constant-indexing arrays. In fact some work in this direction is being done with this or this method, and on stable rust, you can also convert &[T] back to fixed-sized &[T; 8] (or other lengths) with the relevant TryFrom implementation and an unwrap.

2 Likes

If you mean, how is it possible to work with unsized (dynamically sized) types behind a reference when it's not possible when they are "bare", it's because references (and other pointers) to unsized types are wide references which store enough information to calculate their size (and sometimes other things, depending on the unsized type).

2 Likes

Right, that makes sense, thanks!