Ruta graveolens  ·  notes from a language experiment  ·  cultivated since 2025

Fixed-Size Arrays

Array Literals

array_literal = "[" [ expression { "," expression } ] "]" ;

An array literal creates an array with the given elements.

All elements MUST have the same type.

The number of elements MUST match the declared array size.

fn main() -> i32 {
    let arr: [i32; 3] = [10, 20, 12];
    arr[0] + arr[1] + arr[2]  // 42
}

Array-Repeat Literals

array_literal = "[" ( [ expression { "," expression } ]
                    | expression ";" array_length ) "]" ;

An array literal has two forms. The list form [e0, e1, …] gives each element explicitly. The repeat form [value; count] constructs an array of count elements, each equal to value. The result type is [T; count] where T is the type of value.

In the repeat form, count MUST be a non-negative integer compile-time constant — an integer literal or an identifier naming a compile-time constant (a file-level const or an in-scope comptime value parameter), resolved by the same rules as an array-type length.

The element type of a repeat literal MUST be Copy. A repeat literal materializes count copies of a single value, which is only well-defined when the value can be copied; a non-Copy element type is a compile-time error.

The value expression is evaluated exactly once; its result is copied into each of the count array slots.

fn main() -> i32 {
    let zeros: [i32; 128] = [0; 128];   // 128 copies of 0
    let sevens = [7; 3];                 // [7, 7, 7]
    zeros[0] + sevens[0] + sevens[1] + sevens[2]  // 21
}

Array Indexing

Array elements are accessed using bracket notation arr[index].

The index MUST be an integer type.

fn main() -> i32 {
    let arr: [i32; 3] = [100, 42, 200];
    arr[1]  // 42
}

Bounds Checking

For constant indices, bounds MUST be checked at compile time.

For variable indices, bounds MUST be checked at runtime.

Out-of-bounds access MUST cause a runtime panic.

Mutable Arrays

Mutable arrays allow element assignment.

fn main() -> i32 {
    let mut arr: [i32; 2] = [0, 0];
    arr[0] = 20;
    arr[1] = 22;
    arr[0] + arr[1]  // 42
}

Array Type Syntax

array_type   = "[" type ";" array_length "]" ;
array_length = INTEGER | IDENTIFIER ;

The length MUST be a non-negative integer value known at compile time. It is either an integer literal, or an identifier naming a compile-time constant — a file-level const or an in-scope comptime value parameter.

Compile-Time Array Lengths

An array length MAY be an identifier naming a compile-time constant in addition to an integer literal. A file-level const of an integer type and a comptime value parameter both qualify.

A named array length MUST resolve to a non-negative integer compile-time constant. A runtime variable, a negative value, or an undefined name in length position is a compile-time error.

When an array length names a comptime value parameter, the length is resolved at each specialization. The same generic definition therefore yields arrays of different sizes for different argument values, so a comptime value parameter can parameterize a type's memory layout — for example a fixed-capacity buffer or stack — and not only its behavior.

fn Buffer(comptime N: i32) -> type {
    struct { data: [i32; N], len: u32 }
}

fn main() -> i32 {
    let B2 = Buffer(2);
    let B4 = Buffer(4);
    let b2: B2 = B2 { data: [1, 2], len: 2 };
    let b4: B4 = B4 { data: [10, 20, 30, 40], len: 4 };
    b2.data[1] + b4.data[3]  // 42
}

Nested Arrays

Arrays MAY contain other arrays as elements, forming multi-dimensional arrays.

Nested arrays are indexed using chained bracket notation, evaluated left to right.

fn main() -> i32 {
    let matrix: [[i32; 2]; 2] = [[1, 2], [3, 4]];
    matrix[1][1]  // 4
}

Arrays in Structs

Struct fields MAY have array types.

Array fields are accessed by combining field access with array indexing.

struct Container { values: [i32; 3] }

fn main() -> i32 {
    let c = Container { values: [10, 20, 30] };
    c.values[1]  // 20
}

Arrays as Function Parameters

Functions MAY accept arrays as parameters.

Array parameters are passed by value; the entire array is copied to the function.

fn sum(arr: [i32; 3]) -> i32 {
    arr[0] + arr[1] + arr[2]
}

fn main() -> i32 {
    let data: [i32; 3] = [10, 20, 12];
    sum(data)  // 42
}

Array Projection Semantics

Array indexing operates as a projection. Reading an element does not move the array itself.

When reading an element of a Copy type (e.g., integers, booleans), the element is copied out.

fn main() -> i32 {
    let arr: [i32; 3] = [10, 20, 30];
    let x = arr[0];     // i32 is Copy, so x is a copy
    let y = arr[0];     // Can read same element again
    x + y               // 20
}

When reading an element of a non-Copy type, the read moves the element out of the array only when the index is a compile-time constant and the indexing applies directly to an array variable (per-element move tracking; see Array Element Moves in the Move Semantics chapter). Any other such read — a non-constant index, or an array reached through another projection or computed by an expression — is a compile-time error: with a runtime index the compiler cannot know which element moved, so neither use-after-move checking nor drop elaboration could remain sound.

struct BigThing { value: i32 }

fn first_index() -> u64 { 0 }

fn main() -> i32 {
    let arr: [BigThing; 2] = [BigThing { value: 1 }, BigThing { value: 2 }];
    let x = arr[0];             // OK: constant index moves arr[0] out
    let i = first_index();
    let y = arr[i];             // ERROR: cannot move out of indexed position
    x.value
}

Array element assignment is an in-place mutation. It modifies the array without moving it.

fn main() -> i32 {
    let mut arr: [i32; 3] = [1, 2, 3];
    arr[0] = 10;        // Mutates in place
    arr[1] = 20;        // Another mutation
    arr[0] + arr[1]     // 30
}