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 five-element array left by by positions and return the result. Elements that pass the beginning wrap around to the end.

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

For [1, 2, 3, 4, 5]:

Rotation Result
0 [1, 2, 3, 4, 5]
1 [2, 3, 4, 5, 1]
2 [3, 4, 5, 1, 2]
5 [1, 2, 3, 4, 5]
7 [3, 4, 5, 1, 2]

Accept any rotation count representable as usize. Preserve the values, including negatives and duplicates. Reducing the count modulo five makes complete turns have no effect.

Return the type the signature promises

[i32; 5] is an array of five i32 values. Its length is part of its type, so [i32; 5], [i32; 6] and Vec<i32> are different types. A vector does not automatically become an array just because it contains five values.

The starter returns a Vec<i32> where an array is required, producing E0308. It also adds i + by before reducing the result, which can overflow for a sufficiently large rotation count. Normalize the count before doing index arithmetic, or use the in-place rotation method below.

An array's elements are stored together, with no separate length field. [i32; 5] occupies 20 bytes. An array needs no separate allocation for its elements, but it is not necessarily on the stack: for example, a boxed array is stored on the heap.

Arrays, vectors and slices

Type What it provides
[T; N] An owned, fixed-length sequence with elements stored inline.
Vec<T> An owned sequence whose length and capacity can change.
&[T] A shared view of a sequence, carrying a pointer and length.

Vectors with nonzero capacity and non-zero-sized elements use a separate allocation. Empty vectors need not allocate, and zero-sized element types need no element storage. The sizes of vector and slice handles depend on the target; do not assume fixed byte counts across platforms.

Both &array and &vector can coerce to &[T]. That borrows their contents rather than transferring ownership.

Generic code can describe the element type and length with T and const N: usize. For example, a parameter of type [T; N] accepts the particular array length chosen when that generic function is instantiated. This exercise uses the fixed length five.

Copy the array, then rotate it

Arrays implement Copy when their element type does. Since i32 is Copy, this binding leaves both arrays usable:

let mut out = input;

Changing an element of out does not change input. This is the language's value semantics; optimized code does not have to perform a literal memory copy if it can produce the same observable result.

Call out.rotate_left(n) to rotate in place. It accepts counts up to and including the length, but panics if the count exceeds it. Passing by % 5 handles all valid usize counts without overflowing an addition. There is no empty-array case here, so the divisor is always nonzero.

Bounds checks still matter

Rust can diagnose some obvious out-of-bounds accesses before execution:

let a = [1, 2, 3];
let x = a[5];

This example triggers the deny-by-default unconditional_panic lint. That does not mean every invalid index is detected at compile time. A safe indexing operation that reaches execution with an out-of-bounds index panics. Use get when an invalid index should return None instead.

Passing requires compilation, correct rotations and Clippy with warnings treated as errors.