Skip to content

The language

Arrays

An array [T] is a growable list of values of one type. Arrays are values: let b = a gives you an independent copy (made lazily, so it’s cheap), and an index outside the array stops the program unless the compiler proved it can’t happen.

fn main():
var xs = [3, 1, 2]
xs.push(5)
xs.sort()
print(xs, len(xs), xs[0], 5 in xs)
Output
[1, 2, 3, 5] 4 1 true

Write the elements in brackets, repeat one value with * n, or build an array with a comprehension:

fn main():
let primes = [2, 3, 5, 7]
let zeros = [0.0] * 4
let squares = [i * i for i in 0..6]
let even_squares = [i * i for i in 0..10 if i % 2 == 0]
let words = ["fig", "pear", "plum"]
let lengths = [len(w) for w in words]
print(primes, zeros, squares)
print(even_squares, lengths)
Output
[2, 3, 5, 7] [0.0, 0.0, 0.0, 0.0] [0, 1, 4, 9, 16, 25]
[0, 4, 16, 36, 64] [3, 4, 4]
  • [x] * n makes n copies of x. The value goes on the left: 3 * [0] is an error. A negative n panics.
  • A comprehension is [expr for name in iterable] with an optional if filter. The iterable is a range (0..n, 0..=n, range(a, b, step)), an array, a string or a map. Tuples unpack: [name for name, age in people if age > 30].
  • One for per comprehension: [x for row in grid for x in row] is an error (E0110). Use a loop with extend instead.
  • An empty array needs a type: var names: [str] = []. let xs = [] alone is error E0308.

par [...] builds the array on every core. See parallelism.

fn main():
let xs = [10, 20, 30, 40]
print(xs[0], xs[len(xs) - 1], len(xs))
print(20 in xs, xs.contains(99), 99 not in xs)
print(xs.index(30), xs.index(99))
print(xs == [10, 20, 30, 40], xs != [40, 30, 20, 10])
Output
10 40 4
true false true
Some(2) None
true true
  • xs[i] reads element i, counting from 0.
  • x in xs and xs.contains(x) are the same test.
  • xs.index(x) returns Option[int]: Some(i) for the first match, None if there is none. See error handling for Option.
  • == and != compare length and every element. Arrays have no <: only numbers and strings are ordered.

An index outside the array is a panic:

fn main():
let xs = [1, 2, 3]
let i = len(args()) + 5
print(xs[i])
Output
panic: index 5 is out of bounds for an array of length 3
--> main.br:4:11

The program exits with code 101. When the compiler can prove an index is in range (as in for i in 0..len(xs): xs[i]), it removes the check. brine check --report lists the checks that remain. See proofs.

Brine has no negative indexes. xs[-1] isn’t the last element; it’s out of bounds, and panics like any other bad index. Write the index out:

fn main():
let xs = [1, 2, 3]
let last = xs[len(xs) - 1]
let tail = xs[len(xs) - 2:]
print(last, tail)
Output
3 [2, 3]

Python’s xs[-1] hides an off-by-one when the index is computed: i - 1 with i at 0 silently gives the last element. In Brine it stops with a clear message.

Methods that change an array need a var:

fn main():
var xs = [3, 1, 2]
xs.push(5)
let last = xs.pop()
let first = xs.pop(0)
print(last, first, xs)
xs.insert(0, 9)
xs.extend([7, 8])
print(xs)
xs.sort()
print(xs)
xs.reverse()
print(xs)
xs[0] = 100
xs[1] += 1
print(xs)
xs.clear()
print(xs, len(xs))
Output
5 3 [1, 2]
[9, 1, 2, 7, 8]
[1, 2, 7, 8, 9]
[9, 8, 7, 2, 1]
[100, 9, 7, 2, 1]
[] 0
  • pop() on an empty array panics (pop() from an empty array), and so do pop(i) and insert(i, x) with i out of range. insert(len(xs), x) appends.
  • sort() works on arrays of numbers and strings and is stable. Arrays of tuples, structs or bool can’t be sorted (E0305).
  • There is no + for arrays. Use extend.

xs[a:b] is a new array with the elements from index a up to, not including, b. Leave out a to start at 0 and b to go to the end:

fn main():
let xs = [10, 20, 30, 40, 50]
var middle = xs[1:4]
middle[0] = 0
print(middle, xs[:2], xs[3:], xs[5:], xs[:])
print(xs)
Output
[0, 30, 40] [10, 20] [40, 50] [] [10, 20, 30, 40, 50]
[10, 20, 30, 40, 50]

A slice is a copy: changing it never changes the original. Bounds must satisfy 0 <= a <= b <= len(xs); anything else panics, with no clamping as in Python:

panic: slice [1:5] is out of bounds for an array of length 3

An array can hold arrays. Build a grid with a comprehension, so each row is its own value:

fn main():
var grid = [[0] * 3 for _ in 0..2]
grid[1][2] = 7
let copy = grid
grid[0][0] = 1
print(grid)
print(copy)
print(len(grid), len(grid[0]), grid[1][2])
Output
[[1, 0, 0], [0, 0, 7]]
[[0, 0, 0], [0, 0, 7]]
2 3 7

[[0] * 3] * 2 works too. Because arrays are values, its rows are independent: setting g[0][0] changes only the first row, unlike Python lists.

Assigning an array, passing it to a function, or storing it in a struct copies it. The copy is lazy (copy-on-write): both names share one buffer until one of them changes, and only then is the buffer copied. Copies cost almost nothing, and changing one array never changes another.

fn add_one(mut xs: [int]) -> [int]:
for i in 0..len(xs):
xs[i] += 1
return xs
fn main():
var a = [3, 1, 2]
let b = a
a.sort()
let c = add_one(a)
print(a, b, c)
Output
[1, 2, 3] [3, 1, 2] [2, 3, 4]

To let a function change the caller’s array, pass it by reference: fn fill(ref xs: [int], v: int), called as fill(ref data, 0). See functions.

for x in xs walks a snapshot of xs taken when the loop starts, so the body may change xs safely:

fn main():
var xs = [1, 2, 3]
for x in xs:
xs.push(x * 10)
print(xs)
for i, x in enumerate(["a", "b"]):
print(i, x)
Output
[1, 2, 3, 10, 20, 30]
0 a
1 b

The loop runs three times. Changes made in the body show up after the loop. See control flow for more loop forms.

A statement can’t both change a variable and read it somewhere else, or change it twice. Otherwise the result would depend on which part runs first:

fn main():
var xs = [1, 2, 3]
print(xs.pop(), xs)
error[E0405]: `xs` is changed and also read in the same expression
--> main.br:3:11
|
3 | print(xs.pop(), xs)
| ^^^^^^^^ changed here
| -- read here
|
= help: make the change in its own statement first, then use the result
= run `brine explain E0405` for more about this error
error: could not compile due to 1 error

Split it in two: let last = xs.pop(), then print(last, xs). The same applies to xs.push(xs.pop()). Reading the array in the changing call’s own arguments is fine (xs.push(len(xs))), and so is using it on the two sides of and, or or if ... else. See E0405.

sum, product, min, max, any and all take a generator: a comprehension without the brackets. No array is built.

fn main():
let xs = [4, -2, 8, 5]
print(sum(x for x in xs), product(x for x in xs))
print(min(x for x in xs), max(x * x for x in xs))
print(any(x < 0 for x in xs), all(x < 10 for x in xs))
print(sum(i for i in 0..101 if i % 2 == 0))
print(sum(float(x) / 2.0 for x in xs))
Output
15 -320
-2 64
true true
2550
7.5
  • Over an empty sequence, sum is 0, product is 1, any is false and all is true. min and max panic: min() of an empty sequence.
  • min(a, b) and max(a, b) with two arguments are the plain built-ins.
  • A generator can only be passed to one of these six; len(x for x in xs) is error E0316. Use [x for x in xs] to build an array.
  • Overflow in a reduction panics like any other overflow.
  • par sum(...) and the others run on every core and give the same bits on any machine. See deterministic reductions.
Operation Result Notes
len(xs) int
xs[i] T panics if out of range; no negative indexes
xs[a:b], xs[:b], xs[a:] [T] a copy; panics if out of range
x in xs, xs.contains(x) bool
xs.index(x) Option[int] first match
xs == ys bool element by element
xs.push(x) append
xs.pop() T remove the last; panics if empty
xs.pop(i) T remove at i, shifting the rest left
xs.insert(i, x) put x at i, shifting the rest right; 0 <= i <= len(xs)
xs.extend(ys) append every element of ys
xs.sort() numbers and strings; stable
xs.reverse()
xs.clear() remove everything
sum, product, min, max, any, all over a generator: sum(x for x in xs)
  • Elements all have one type. An empty array needs an annotation.
  • Indexes start at 0 and are never negative. Out-of-range indexes and slices panic.
  • let b = a copies; changing b never changes a.
  • for x in xs loops over a snapshot.
  • A statement can’t change an array and read it elsewhere.

Related: strings, maps, values and types, built-in functions.