Tuple types

A tuple type describes a fixed-length sequence where each position has its own type, both length and per-position types being part of the type. You read an element with a literal index and get that position's exact type; an out-of-bounds or non-literal index is a compile error. Assignability is invariant and length-exact.

A tuple type describes a fixed-length sequence where each position has its own type. Both the length and the per-position types are part of the type.

type Pair = [string, number]

A Pair is exactly two elements: a string followed by a number. This is how you model a small, fixed record addressed by position rather than by name — a coordinate, a key-value entry, the paired result of a function that returns two things.

Positional access

You read an element with a constant index, and you get back that position's exact type. The index must be a static, non-negative integer literal.

const p: [string, number] = ["a", 1]
p[0]   // string
p[1]   // number

An index past the end of the tuple is a compile-time error, not a value typed T | undefined. Because the length is known, the checker can tell that the access can never succeed and refuses it outright.

const t: [string, number] = ["a", 1]
t[2]   // error: index out of bounds

An index that is not a literal — a variable, a computed value — is refused as well. The checker only allows an access it can resolve to a specific position at compile time.

What you can assign

Tuple assignability is invariant and length-exact. Two tuple types match only when they have the same length and each position is equivalent. There is no length flexibility and no positional covariance, and a tuple is never interchangeable with an array of the same element type. A [string, number] is not an Array<string | number> and vice versa.

const bad: [string, number] = ["a", 1, true]   // error: element-count mismatch

This is stricter than TypeScript, which allows a range of tuple-to-tuple and tuple-to-array conversions. CruftScript treats a tuple as an exact shape.

Why it works this way

The value of a tuple is that its length and layout are known facts the checker can rely on. Making out-of-bounds access a compile error, rather than widening it to T | undefined, turns a whole class of off-by-one mistakes into errors you see before the program runs. Requiring a literal index is what makes that possible: the checker can only prove an access is in bounds when it knows which position you mean. Invariant equivalence keeps those guarantees intact, since any looser rule would let a tuple be viewed through a type that misstates its length or its positions.

Limitations

  • Only the plain fixed form is supported. Optional elements, rest elements, named elements, and spread elements are not in the accepted grammar. A malformed interior segment is silently dropped rather than reported, so avoid writing these forms at all — the result will not be what you wrote.
  • readonly is erased. A readonly [string, number] parses, but the modifier is discarded and has no effect on the resulting type.
  • No dynamic indexing. You cannot index a tuple with a variable. Every access needs a literal position known at compile time.