Skip to content

Data Structures

Arrays (Array)

Arrays are ordered, mutable sequences of values.

Literals

Inline with brackets:

let arr = [1, 2, 3]
let mixed = [1, "hello", true, nil]

... splices another collection or an iterator into the literal; see Spreading:

let extras = [4, 5, 6]
let all = [1, 2, 3, ...extras]
assert_eq $all [1, 2, 3, 4, 5, 6]

See the Array API for methods.

Tuples (Tuple)

Tuples are immutable, ordered sequences of values.

Comma-separated items within parentheses construct a tuple. A single item needs a trailing comma to distinguish it from mere grouping:

let tup = (1, "two", true)
assert_eq $tup[1] two
let empty = ()
let single = (1,)

Spread a sequence into a tuple with ...:

let rest = [2, 3]
assert_eq (1, ...rest) (Tuple [1, 2, 3])

The Tuple type object also builds a tuple from an iterable:

let tup = Tuple [1, 2, 3]

See the Tuple API for details.

Dictionaries (Dict)

Dictionaries are ordered, mutable key-value mappings. They preserve insertion order and are actually multi-maps: a single key can have multiple values.

Literals

Inline with braces:

let d = {name: "Alice", age: 30}

Symbol Keys vs String Keys

A literal key: in a dict literal creates a symbol key:

let d = {name: "Alice"}
# key is the symbol :name:

To interpret the key as a variable instead, prefix it with $:

let key = "name"
let d = {$key: "Alice"}
# key is the string "name"

Constants, quoted strings, parenthesized expressions, and other literals are automatically treated as expressions. Values are always treated as full, whitespace-insensitive expressions.

Positional Elements

Dict literals can contain values without explicit keys. These receive incrementing integer keys starting at 0:

let d = {1, foo: "bar", 3}
# d[0] == 1, d[:foo:] == "bar", d[1] == 3

Ordering

Dictionaries preserve insertion order. Iteration yields entries in the order they were inserted.

Multi-Map Semantics

Dicts are multi-maps: a key can map to multiple values. This matters in specific cases:

  • Construction: Duplicate keys in a literal or spread preserve all values:

    let d = {...{a: 1}, ...{a: 2}}
    # {a: 1, a: 2}
    
  • Plain indexing (d[key]) returns only the last (most recently inserted) value for a key.

  • Plain assignment (d[key] = value) replaces all values for a key.
  • Methods like insert, get, and pop respect the multi-map nature of dict

See the Dict API for details.

Records (Record)

Records are immutable sequences of positional items and symbol-keyed items, like a tuple with keys. Read an item by indexing with its symbol or position:

let r = (name: "Alice", age: 30)
echo $r[:name:] # Alice

Parentheses make a record rather than a tuple when at least one key is specified. Positional items get integer keys counting from 0:

let name = "Alice"
let r = (:name, age: 30)
let mixed = (1, 2, tag: "x")
assert_eq $mixed[1] 2

The record function builds a record from its arguments:

let r = record name: Alice age: 30

A key may repeat, and a lookup finds its latest value. Iterating a record yields key/value tuples in order, with a positional item's position as its key.

Use with to replace keys and without to remove them in a new record:

let updated = r.with status: ready
let cleaned = updated.without :status:

with keeps each replaced key at its first position, removes later occurrences, and appends new keys. without removes all occurrences of the supplied keys. Both leave the original unchanged.

Spreading a repeated key into a call binds its first value to a parameter of that name, and a ** or ... rest takes the later ones in order. Without a rest, the repeats raise UnexpectedKeyError.

See the Record API for details.

Structs (Struct)

Structs are mutable sequences of positional items and symbol-keyed items, like a record whose keys are fields. A struct has no methods of its own, so a name reads or writes the item it keys:

let s = struct host: localhost port: 22
s.port = 2222
s.user = "root"
echo $s.host # localhost

Reading a name with no item raises FieldError. Assigning one replaces its item in place, or adds an item at the end. Calling a name calls its item. Indexing reads and writes items by symbol or position.

The struct function builds a struct from its arguments, and Struct from one spreadable source, as Record does:

let s = Struct {host: "localhost", port: 22}

A key appears at most once, so building a struct with a repeated key raises ValueError. Iterating, spreading and unpacking work as for a record.

Since every plain name is an item, operations take the struct as an argument to a class method instead:

let port = Struct.get $s :port: default: 22
if (Struct.contains s :user:)
  echo $s.user

The type Struct[S] takes a schema, which types each item. Reading or writing a name the schema doesn't admit is an error:

def connect target @ Struct[{host: Str, port: Int, ?user: Str}]
  echo "$(target.host):$(target.port)"

BaseStruct[S] is the read-only view of a struct. A parameter that only reads takes a BaseStruct, which accepts structs with narrower items, as BaseDict does for dicts.

See the Struct API for details.

Sets (Set)

Sets are ordered, mutable collections with unique membership semantics.

Sets do not have a dedicated literal syntax. Construct them with the Set type object from any iterable:

let empty = Set()
let s = Set [3, 1, 2, 1]
assert_eq [...s] [3, 1, 2]

Iteration preserves insertion order. Adding an existing value is a no-op and does not move it to the end.

See the Set API for methods such as add, contains, union, and diff.

Spreading

... spreads a value into an array, tuple, dictionary, or record literal, or into a call's arguments. What it contributes depends on both the value and the context:

Context Spread input Behavior
Arguments Array, tuple, set, Iter Positional arguments, including pair elements
Arguments Dict, record, struct Mixed arguments: sequential integer keys are positional, symbol keys are keywords
Array or tuple Array, tuple, set, Iter Positional elements
Array or tuple Dict, record, struct Key/value pairs
Dictionary or record Array, tuple, set, Iter Elements assigned incrementing positional keys
Dictionary or record Dict, record, struct Preserves keyed and positional entries
Any kv() adapter Keyed entries; arrays and tuples reject them

Spread a dictionary directly to preserve its keys:

let base = {name: "Alice"}
let extended = {...base, age: 30}

Spreading dicts preserves duplicate keys and order. Spreading any Iter, including a dict or record pair iterator, inserts its elements positionally.

Use kv() when you want an iterable of pairs to spread as key/value entries:

let entries = [("x", 1), ("y", 2)]
let dict = {...entries.kv()}

The adapter returned by kv() emits keyed entries in every spread context. It supports spreading only: it cannot be iterated or used with iterator methods. A keyed spread into an array or tuple raises an error.

An Iterable without spread support must first be converted to an iterator with iter(). A rest bound by a pattern can be spread repeatedly, unless it is the rest of an iterator.