Records, arrays, and bounds you choose taught the container whose bounds are yours to pick. This page is about a different pair of types — vector[3] of int64, matrix[2, 3] of float64 — that are not containers first. They are math values with shape: the shape lives in the type, products whose dimensions cannot meet are refused before the program exists, and the operators they carry are the linear algebra a formula means.

The example is examples/VectorsAndMatrices in the tutorial repository.

Getting the file

make -C examples/VectorsAndMatrices run
VectorsAndMatrices

A, row by row
  1 2 3
  4 5 6
v              1 2 3

v + v          2 4 6
3 * (v + v)    6 12 18
minus v        5 10 15

A * v          14 32
Dot(v, v)      14
Transpose(A)   1 4 / 2 5 / 3 6
A * T          14 32 / 32 77

after b := v and b[0] := 99, v[0] is still 1

The example, walked

Shape in the type

type
    Vec3  = vector[3] of int64;
    Mat23 = matrix[2, 3] of int64;

The 3 and the 2×3 are facts of the type, not properties of a value somebody must remember to check. Indexing runs from zero — A[r, c], v[i] — because these are math values with one fixed indexing convention, where an array lets you choose bounds precisely because it is your container and your domain. An index a constant can prove wrong is refused at compile time; the third experiment below reads that refusal.

Elementwise, and the scalar meet

u := v + v;
u := 3 * u;
u := u - v;

+ and - work element by element between tensors of one shape, and a scalar meets a tensor from either side — 3 * u and u * 3 mean the same scaling. Integer literals adopt the element type; a float scalar must be exactly the element type, because widening a whole tensor behind your back is the kind of quiet cost this surface refuses to have.

The product family

w := A * v;
G := A * Transpose(A);

* is the linear-algebra product: matrix times vector, matrix times matrix, and chains of either — an operand may itself be an operation’s result. The shapes must meet, and the compiler checks that they do; the first experiment reads that refusal too.

What * is deliberately not is the dot product. Between two vectors it has no meaning at all, and the refusal states the design in one breath — the dot product is the named function Dot, a matrix quotient would smuggle a hidden inverse, and a plain storage array joins no algebra. Transpose answers the flipped matrix, a named verb like Dot, because a formula reads best when its exotic moves have names.

The value rule, unchanged

b := v;
b[0] := 99;

v[0] is still 1. Assignment copies the whole value, exactly as it copies a record or an array — shape does not buy an exception to the one rule the language keeps everywhere.

Where the family continues

  • WYSIWYG slicingRow, Column, SubMatrix, Window: pieces of a shape as named copies.
  • Spans and the lending law — the borrowed view when a copy is the wrong cost, inside a lifetime the caller can see.
  • Deterministic reductionsSum, Mean and their kin over these values, one documented pairwise tree, bit-exact serially and in parallel. Float tensors get their exactness story there.
  • Generics — shape-generic code: the gen clause ranges over dimensions as well as types, so one routine serves every vector[N].

Try it

1. Make the shapes not meet. Change w := A * v; to w := A * w; — a 2×3 against a 2-vector — and rebuild:

analyzer error 5334: the product 'multiplication' requires the left operand's columns to meet the right operand's leading dimension: found 'Mat23' and 'Vec2'

The dimension check most numeric code performs at run time — or forgets — happened before the program existed.

2. Multiply two vectors. Try s := v * u; with an int64 s:

analyzer error 5336: the operator 'multiplication' has no meaning on 'Vec3' and 'Vec3': tensors define '+', '-' elementwise and '*' as the linear-algebra product — the dot product is the 'Dot' function, a matrix quotient would smuggle a hidden inverse, and a plain storage array joins no algebra

The whole operator design, stated by the refusal itself. Write Dot(v, u).

3. Step past the shape. Write v[3] := 1; and rebuild:

analyzer error 5128: constant index expression at position 1 in selection operation 'index' has value '3' outside array index domain 'subrange[0..2] of int64'

A constant the compiler can read is checked where it stands; a runtime index is trapped at its named line, exactly as the aggregates page promised for arrays.

What the compiler proved

Every dimension agreement in this program is a compile-time fact, every constant index is checked where it is written, and a copy is a copy under the one value rule. The arithmetic itself ran identically on x86-64 and ARM64 — and for float elements, the reductions page states the stronger promise: bit-exact results, serial or parallel.

Next

The math unit — the scalar verbs beside these types: generic over widths, constants as declarations, and the libm seam. Every example behind the series lives in the tutorial repository.