Collections
Working with Arrays and Objects in Walrus.
Walrus provides two primary runtime collection types for managing groups of data: Arrays and Objects. Both are heap-allocated and follow move semantics. Walrus also exposes higher-level core abstractions such as Map, Tuple, container, and view<T> directly in the language/runtime model.
Arrays
Arrays are ordered, integer-indexed collections of values. They are mutable and can grow dynamically.
Creating Arrays
Use square brackets [] to create an array literal.
let scores = [10, 20, 30]
let mixed = [1, "two", 3.0] # Arrays can store mixed typesAccessing Elements
Access elements using zero-based integer indexing.
let values = [100, 200, 300]
let first = values[0]
values[1] = 500 # MutationArray Methods
Walrus provides several built-in functions for manipulating arrays:
| Function | Description |
|---|---|
len(array) -> int | Returns the number of elements in the array. |
push(array, value) -> int | Appends a value to the end of the array. Returns the new length. |
pop(array) -> any | Removes and returns the last element of the array. |
removeAt(array, index) | Removes the element at the specified index. |
removeValue(array, value) -> bool | Removes the first occurrence of value. Returns true if found. |
let list = [1, 2]
push(list, 3) # list is [1, 2, 3]
let last = pop(list) # last is 3, list is [1, 2]
removeAt(list, 0) # list is [2]Objects
Objects are collections of named properties. They behave like string-keyed maps or "plain old data" structures.
Creating Objects
Use curly braces {} to create an object literal.
let player = { name = "Hero", hp = 100, x = 10 }Accessing Properties
Use dot syntax . to read or write properties.
let current_hp = player.hp
player.x = player.x + 5Property Shorthand
If you have a local variable with the same name as a property, you can use the shorthand syntax:
let name = "Walrus"
let hp = 50
let enemy = { name, hp } # Equivalent to { name = name, hp = hp }Object Built-ins
| Function | Description |
|---|---|
len(object) -> int | Returns the number of properties currently in the object. |
delete(object, key) | Removes a property from the object by its name (string). |
let state = { score = 10, active = true }
delete(state, "active") # state now only has 'score'Performance and Ownership
- Heap Allocated: Both arrays and objects live on the heap and are managed by reference counting.
- Move Semantics: Assigning a collection to a new variable moves ownership. The original variable becomes invalid.
- No Borrowed Storage: You cannot store a
reformut refinside an array or object. Collections must always own their contents.
Core Containers
Use the built-in container helpers when you want a more structured API than raw arrays and objects:
let stats = mapOf<string, int>()
stats.set("hp", 120)
stats.set("mp", 40)
let spawn = tuple<int, int>(12, 24)
let playerSpawn = tuple<int, int>(64, 96)Map<K, V>
Map<K, V> stores key/value pairs in an internal entry array. That keeps it safe for arbitrary Walrus values, but it also means lookups are currently linear rather than hashed. It is a good fit for small gameplay tables, settings, and metadata.
Creating a Map
Use mapOf<K, V>() to create an empty map:
let stats = mapOf<string, int>()
let lootCounts = mapOf<string, int>()Inserting and Updating Entries
Use set(key, value) to add a new entry or overwrite an existing one:
let stats = mapOf<string, int>()
stats.set("hp", 120)
stats.set("mp", 40)
stats.set("hp", 150) # overwrite existing entryReading Values
Use getOr(key, fallback) when a key may not exist:
let stats = mapOf<string, int>()
stats.set("hp", 120)
let hp = stats.getOr("hp", 0)
let armor = stats.getOr("armor", 0)If "hp" exists, hp is 120. If "armor" does not exist, armor becomes the fallback 0.
Checking for Keys
Use has(key) when you need to know whether an entry exists before making a decision:
if stats.has("hp"):
print("hp exists")Removing Entries
Use remove(key) to erase an entry. It returns true when the key existed and false when nothing was removed:
let removed = stats.remove("mp")Map Methods
| Method | Description |
|---|---|
len() -> int | Returns the number of entries. |
isEmpty() -> bool | Returns true when no entries exist. |
has(key: K) -> bool | Returns true if the key exists. |
set(key: K, value: V) | Inserts or overwrites a key. |
getOr(key: K, fallback: V) -> V | Returns the stored value or a fallback. |
remove(key: K) -> bool | Removes a key and reports whether it existed. |
Full Map Example
let stats = mapOf<string, int>()
stats.set("hp", 120)
stats.set("mp", 40)
if stats.has("hp"):
let hp = stats.getOr("hp", 0)
print(hp)
stats.remove("mp")
print(stats.len())Tuple<TFirst, TSecond>
Tuple<TFirst, TSecond> is a compact fixed-size pair with first and second fields.
Creating a Tuple
Use tuple(first, second) to create a pair:
let spawn = tuple<int, int>(12, 24)
let namedPoint = tuple<string, int>("coins", 10)Accessing Tuple Values
Read tuple members through first and second:
let spawn = tuple<int, int>(12, 24)
let x = spawn.first
let y = spawn.secondSwapping a Tuple
Use swap() when you want the pair reversed:
let pair = tuple<int, string>(7, "walrus")
let swapped = pair.swap()
# swapped.first is "walrus"
# swapped.second is 7Tuple Fields and Methods
| Member | Description |
|---|---|
first | The first stored value. |
second | The second stored value. |
swap() | Returns a new tuple with the positions reversed. |
Full Tuple Example
let screen = tuple<int, int>(1920, 1080)
let flipped = screen.swap()
if screen.first == 1920 && screen.second == 1080:
print(flipped.first)Containers and Views
Walrus is moving toward a more data-oriented model, and container plus view<T> are part of that shift.
container
Use container when you want structured data that is meant to participate in Walrus' thread-aware, data-oriented pipeline:
container velocity:
int speed = 5
int direction_x = 0Conceptually, a container is for runtime data layout and batched processing, not just generic object behavior.
In the current first pass:
- containers have named fields
- fields can have defaults
- containers can be constructed with the normal record-style syntax
- containers are the only valid element type for
view<T>
view<T>
view<T> represents lightweight access over container data:
let world = [
velocity { speed = 2, direction_x = 10 },
velocity { speed = 4, direction_x = 20 }
]
let all = viewOf<velocity>(world)
let head = all.slice(0, 1)Use it when you want to process batches of structured values rather than treat data as isolated objects.
Mutable iteration
for mut v => all:
v.direction_x += v.speedRead-only iteration
let ro = all.readonly()
for v => ro:
print(v.speed)Thread-safe direction
container and view<T> are part of Walrus' thread-safe design direction:
- containers define structured runtime data
- views make access windows explicit
- readonly vs writable access is visible in the source
That is important groundwork for future real multi-threaded scheduling, even though the current first pass still uses a simple runtime path underneath.