Skip to content

← Ground Rules: Values, Types, Control Flow step 19 of 24

Easy Primitives

Arrays: the length is part of the type

Rotate a fixed five-element array left by by positions and return the result.

pub fn rotate_fixed(input: [i32; 5], by: usize) -> [i32; 5]

([1, 2, 3, 4, 5], 2) gives [3, 4, 5, 1, 2]. by = 0 and by = 5 are the identity. by = 7 behaves like by = 2 — rotations wrap.

The starter does not compile, and its error is the definition of the item.

[i32; 5] and [i32; 6] are different types

An array’s length is part of its type. Not metadata, not a runtime field — part of the type, checked at compile time, and [i32; 5] is no more assignable to [i32; 6] than i32 is to String. That is why the starter’s Vec<i32> cannot be returned from a function declared -> [i32; 5]: E0308: expected [i32; 5], found Vec<i32>.

That 5 is a const generic parameter — the length is a compile-time value the type is generic over — and for most learners this is the first one they meet. It is why impl<T, const N: usize> … appears all over the standard library, and why arrays only recently gained traits for arbitrary lengths.

The upside is real: an array’s size is known at compile time, so it lives inline — in a local, in a struct field, in a register — with no heap allocation, no pointer to follow and no length to store. size_of::<[i32; 5]>() is exactly 20 bytes.

The array / Vec / slice trio, in one paragraph

Three types that all “hold a run of T“, and are not arbitrary once you see the axis they differ on:

  • [T; N] — length fixed at compile time, stored inline, Copy if T is.
  • Vec<T> — length chosen and changed at run time, stored on the heap, owns its buffer. 24 bytes of pointer, length, capacity.
  • &[T] — a view of a run of T that you do not own, with the length carried alongside the pointer. 16 bytes. Both of the above coerce to it.

Item 1.20 and item 1.21 take the other two in turn.

Copy, and why let mut out = input; works

[i32; 5] is Copy, because i32 is Copy and arrays inherit it. So

let mut out = input;

does not move input away — it copies all twenty bytes, and both bindings stay usable. That is the whole of Track 1’s mental model, and it is exactly what Track 2 will take away from you the moment the element type stops being Copy.

Once you have your own copy, [T]::rotate_left(n) does the work in place. Its sibling rotate_right exists too. Both panic if n is greater than the length, which is why by needs reducing before you hand it over.

Do not try to index out of bounds on purpose

let a = [1, 2, 3];
let x = a[5];        // error: this operation will panic at runtime

That is a compile error, not a runtime panic: it is the deny-by-default unconditional_panic lint, and it fires whenever the compiler can evaluate the index and see it is out of range. An index that came in through a parameter is opaque to that analysis and panics at run time instead. Same rule you met with constant arithmetic overflow in item 1.5 — Rust catches what it can see.