Skip to Content

Array

T[] is a fixed-length array: the length is fixed, elements are readable and writable, and assignment and argument passing share the same storage. array<T> is its named alias; the two spellings are the same type.

Creation

Three routes: the unannotated collection literal, annotated construction to a target type, and allocation by length:

val xs = [1, 2, 3]; print(xs, xs.Length, xs.Count); val buf = new int[3]; buf[2] = 7; print(buf, buf[0]); val names = new string?[2]; names[0] = "a"; print(names);
[1, 2, 3] 3 3 [null, null, 7] 0 [a, null]

An unannotated [1, 2, 3] is inferred as an array — the default shape of a collection literal. new T[n] allocates n zero-value slots: numeric slots read 0 and boolean slots read False. For reference elements, write the nullable element type new T?[n], or fill with a collection literal or the factory form.

Factory-form allocation

new T[n](f) fills the slots one by one with the function value f, bypassing zero-value allocation. Each slot gets exactly one call, and the slots are independent, not shared: the zero-arg form gives each slot f(), and the index form with a single int argument gives slot i f(i). f may not carry fail / uses / async, and its return type must be assignable to the element type:

val boxes = new Box[2](() => new Box()); val squares = new int[5](i => i * i); print(boxes); print(squares);
[Main.Box, Main.Box] [0, 1, 4, 9, 16]

The same constraint applies inside generic functions: new T[n] (T a type parameter) is rejected outright at compile time, so instances must be created with the factory form:

func Fill<T>(make: func() -> T, n: int) -> T[] { return new T[n](make); } val xs = Fill<int>(() => 0, 3); // [0, 0, 0] val cs = Fill<Box>(() => new Box(), 2); // two independent instances

Shared references

Array assignment and argument passing do not copy; both sides see the same object:

val a = xs; a[1] = 99; print(xs[1]);
99

a and xs are the same array; writing a[1] writes xs[1]. For an independent copy, use ToList() and convert back.

Reading, writing, and members

xs[0] = 9; print(xs[1], xs.Contains(99)); val alias: array<int> = xs; print(alias[1], alias.Contains(99)); alias.Set(2, 30); print(alias, alias.ToSortedList(), alias.Reversed());
99 True 99 True [9, 99, 30] [9, 30, 99] [30, 99, 9]

Beyond indexer reads and writes, the method surface offers Get(i) / Set(i, v) / Contains(v) / ToList() / Reversed() (reversed copy) / ToSortedList() (sorted copy). Length and Count are both properties.

Slicing always produces a new array

xs[start..end] slices a range out of an array or a list; the result is an independent new array that no longer shares with the source:

print(xs[1..3]); val tail = xs[1..3]; tail[0] = -1; print(xs[1], tail[0]);
[99, 30] 99 -1

Writes to tail[0] do not touch the original, and later changes to the original are not reflected in the slice either. An out-of-range or reversed range is always clipped (xs[2..1] yields empty); only a single out-of-range index is an error.

Converting to and from List

Arrays and lists have no implicit conversion in either direction; assigning one to the other reports MS3101. The explicit conversions are lst.ToArray() and arr.ToList(), and both sides produce a new container:

val ls: list<int> = [4, 5]; ls.Add(6); val back = ls.ToArray(); back[0] = 40; print(ls[0], back[0]); val again = back.ToList(); again.Add(1); print(again, again.Count);
4 40 [40, 5, 6, 1] 4

Targeted construction of collection literals still applies: a target type of list<int> produces a list, a target of int[] produces an array.

Notes

  • for n in xs iterates the elements directly.
  • int[] and array<int> can be mixed freely — the two spellings are equivalent, but pick one and stick with it.
  • Array equality is reference identity; to compare by content use ContentEquals. See equality and hashing.
Last updated on October 11, 2026