Skip to content

The language

Maps

A map {K: V} stores values by key and remembers the order keys were first inserted. Reading a key that isn’t there panics, so test with in or use m.get(k), which returns an Option. Like arrays, maps are values: let b = a copies.

fn main():
var ages = {"ann": 31, "bob": 27}
ages["cy"] = 40
ages["ann"] += 1
print(ages, len(ages))
if "bob" in ages:
print(ages["bob"])
print(ages.get("zed"), ages.get("zed", 0))
Output
{"ann": 32, "bob": 27, "cy": 40} 3
27
None 0

Write key: value pairs in braces. The key and value types are inferred from the first pair. An empty map needs a type annotation:

fn main():
let squares = {1: 1, 2: 4, 3: 9}
var counts: {str: int} = {}
for w in ["a", "b", "a", "c", "a"]:
if w in counts:
counts[w] += 1
else:
counts[w] = 1
print(squares, counts)
Output
{1: 1, 2: 4, 3: 9} {"a": 3, "b": 1, "c": 1}

let m = {} with no type is error E0308. A map type can appear anywhere a type can: fn count(words: [str]) -> {str: int}, a struct field, a map of arrays {str: [int]}.

  • m[k] reads the value for k. A missing key panics.
  • m[k] = v inserts k, or replaces its value if it’s already there. Replacing keeps the key’s original position.
  • m[k] += 1 and the other compound assignments change a value in place. The key must exist.
  • k in m and k not in m test for a key.
  • m.get(k) returns Some(value) or None; m.get(k, default) returns the value or default.
  • m.remove(k) removes k and returns true if it was there.
fn main():
var m: {str: int} = {}
m["z"] = 1
m["a"] = 2
m["z"] = 3
print(m)
let first = m.remove("z")
let second = m.remove("z")
print(first, second, m)
m["z"] = 4
print(m)
Output
{"z": 3, "a": 2}
true false {"a": 2}
{"a": 2, "z": 4}

A key that was removed and inserted again goes to the end.

fn main():
let ages = {"ann": 31}
print(ages["zed"])
Output
panic: key "zed" is not in the map
--> main.br:3:11

A missing key usually means a bug or bad input. When it’s expected, say so with get:

fn main():
let prices = {"apple": 3, "pear": 4}
let order = ["apple", "kiwi", "pear"]
var total = 0
for item in order:
match prices.get(item):
case Some(p):
total += p
case None:
print("no price for", item)
print(total, prices.get("kiwi", 0))
Output
no price for kiwi
7 0

for k in m walks the keys in insertion order. m.items() gives (key, value) pairs to unpack:

fn main():
var stock = {"apple": 3, "pear": 0, "fig": 7}
for name in stock:
if stock[name] == 0:
stock.remove(name)
for name, n in stock.items():
print(name, n)
print(stock.keys(), stock.values(), stock.items())
Output
apple 3
fig 7
["apple", "fig"] [3, 7] [("apple", 3), ("fig", 7)]

The loop walks a snapshot of the map taken when it starts, so the body may insert and remove keys. Keys added in the body don’t show up in that loop.

keys(), values() and items() return new arrays, in insertion order.

Keys can be integers, bool, str, enums, and arrays, tuples and structs made of those. Floats can’t be keys:

struct Cell:
row: int
col: int
enum Color:
Red
Green
fn main():
var grid: {(int, int): str} = {}
grid[(0, 1)] = "x"
let cells = {Cell(row=1, col=2): 5}
let lists = {Color.Red: [1, 2], Color.Green: []}
print(grid, cells[Cell(row=1, col=2)], lists[Color.Red])
Output
{(0, 1): "x"} 5 [1, 2]
fn main():
let m: {float: str} = {}
error[E0335]: `float` can't be used as a map key
--> main.br:2:12
|
2 | let m: {float: str} = {}
| ^^^^^^^^^^^^ invalid key type
|
= note: map keys must be integers, `bool`, `str`, enums, or arrays, tuples and structs of those
= help: floats aren't allowed because `nan != nan`; use an integer key instead
= run `brine explain E0335` for more about this error
error: could not compile due to 1 error

A key must be equal to itself for lookups to work, and nan isn’t. To key by a measurement, round it to an integer first (for example, millimetres instead of metres).

Assigning a map or passing it to a function copies it. The copy is lazy (copy-on-write), so it costs nothing until one side changes:

fn main():
var a = {"x": 1}
let b = a
a["x"] = 2
a["y"] = 3
print(a, b)
print({"p": 1, "q": 2} == {"q": 2, "p": 1})
Output
{"x": 2, "y": 3} {"x": 1}
true

== compares keys and values and ignores order. To let a function change the caller’s map, take it as ref: fn add(ref m: {str: int}, k: str). See functions.

par bodies may read maps but not change outer ones. See parallelism.

Operation Result Notes
{k1: v1, k2: v2} {K: V} empty: var m: {K: V} = {}
len(m) int number of keys
m[k] V panics if k is missing
m[k] = v insert or replace
k in m, k not in m bool
m.get(k) Option[V]
m.get(k, default) V
m.remove(k) bool true if k was there
m.keys() [K] insertion order
m.values() [V] insertion order
m.items() [(K, V)] insertion order
for k in m keys, in insertion order, over a snapshot
m == n bool same keys and values, any order
  • Maps remember insertion order; replacing a value keeps the key’s place.
  • m[k] panics on a missing key. Use in or get when a key may be absent.
  • Keys can’t be floats, or contain floats.
  • let b = a copies; changing b never changes a.
  • A loop over a map sees a snapshot, so the body may change the map.

Related: arrays, strings, generics (maps work with type parameters: fn invert[K, V](m: {K: V}) -> {V: K}), error handling.