Skip to content

Objects and Vectors

let nums = [1, 2, 3, 4]
print(nums[0])
print(nums[3])

The element type is inferred from the literal. [1, 2, 3] is Vec<int>; [1.0, 2.5] is Vec<number>. Mixing element types in a single literal is a compile error — every element must share a concrete type.

arr[i] reads the element at zero-based index i. Indexing out of bounds currently raises a runtime error rather than returning None:

let items = [10, 20, 30]
print(items[5]) // Runtime error: Index 5 out of bounds (length 3)

Use .first() / .last() when you want the first or last element, or guard with i < arr.len() before indexing. The arr[-1] shorthand for “last element” is NOT supported — use arr.last() instead.

let items = [10, 20, 30]
print(items.first())
print(items.last())
print(items.len())

Lifting bounds-checked access to return Option<T> is a v0.4 candidate; cite v0.4-vec-indexing-option-returns in the close-summary follow-up tracker.

Index expressions accept Rust-style range syntax for closed-range slice access:

let nums = [10, 20, 30, 40, 50]
print(nums[1..4])
print(nums[0..2])

Half-open ranges (nums[1..], nums[..3], nums[..]) and the inclusive form (nums[1..=3]) are recognized by the grammar but do not yet match the behavior the syntax suggests — half-open forms fail to parse inside arr[..], and ..= truncates one element off the right end. Use the closed form (start..end) until those paths are tightened up at v0.4. Cite v0.4-vec-slice-half-open and v0.4-vec-slice-inclusive-end in the follow-up tracker.

let nums = [1, 2, 3, 4]
let doubled = nums.map(|x| x * 2)
let evens = nums.filter(|x| x % 2 == 0)
print(doubled)
print(evens)
print(nums.first())
print(nums.last())
print(nums.len())

Object spread (...) inlines the contents of one object into another. Later keys override earlier ones:

let base = { x: 1, y: 2 }
let extended = { ...base, z: 3 } // intended: { x: 1, y: 2, z: 3 }
let overridden = { ...base, x: 99 } // intended: { x: 99, y: 2 }

Object spread parses today but emits an inferred field type that the strict post-inference verifier rejects (E0900 per the W17 FieldType::Any boundary audit at docs/cluster-audits/v0.3-w17-fieldtype-any-boundary-audit.md). Rebuilding the spread-merge expression so it lands on a typed inferred schema is a v0.4 candidate; cite v0.4-object-spread-typed-inference in the follow-up tracker.

Array spread ([...other, extra]) parses but hits the V3-S5 ckpt-5 consumer-cascade surface during construction (the deleted typed-array-data enum + Buf<T> carrier rebuild lands at ckpt-6 STRICT close per ADR-006 §2.7.24 Q25.A SUPERSEDED + docs/cluster-audits/w12-typed-array-data-deletion-audit.md):

let base = [1, 2, 3]
let extended = [...base, 4, 5] // SURFACE: V3-S5 ckpt-5

Cite v0.4-v3-s5-ckpt-6-strict-close in the follow-up tracker. Use .concat() for append today:

let base = [1, 2, 3]
let extended = base.concat([4, 5])
print(extended)

Build a vector from an iterator with optional filtering. The parser accepts the syntax, but every construction site funnels through the same V3-S5 ckpt-5 op_new_array SURFACE as array spread above:

let squares = [x * x for x in 0..5] // SURFACE: V3-S5 ckpt-5
let evens = [x for x in 0..10 if x % 2 == 0] // SURFACE: V3-S5 ckpt-5
let pairs = [{ i: i, j: j } for i in 0..3 for j in 0..3 if i != j]

Cite v0.4-v3-s5-ckpt-6-strict-close (same workstream as array spread). Use .map / .filter on an existing vector today:

let xs = [0, 1, 2, 3, 4]
let squares = xs.map(|x| x * x)
let evens = xs.filter(|x| x % 2 == 0)
print(squares)
print(evens)

Mapping a bare Range ((0..5).map(...)) does not yet thread the iterator element type through to the closure parameter; that surface lands alongside the same v0.4 inference push. Cite v0.4-range-map-element-type-inference in the follow-up tracker.

Anonymous objects are dynamic key-value containers. Fields are accessed by name:

let user = {
id: 1,
name: "Ada"
}
print(user.name)
print(user.id)

Adding a NEW field to an existing binding (user.score = 99) is a compile error: “Assignment to ‘user.score’ requires compile-time field resolution.” Rebuild via merge instead (see below) or use a typed struct (next section).

let cfg = {
server: {
host: "localhost",
port: 9091
}
}
print(cfg.server.host)

The + operator merges two anonymous objects, right side winning on key conflicts:

let a = { x: 1, y: 2 }
let b = { z: 3 }
let c = a + b
print(c)

Merging when both sides share a key currently surfaces a type-inference gap (string + {key} fails inference even though the runtime semantics are “right wins”); use disjoint-key merges today. Cite v0.4-object-merge-overlapping-key-inference in the follow-up tracker.

let user = { id: 1, name: "Ada" }
let renamed = user + { name: "Grace" } // inference: string + {name}

For fixed-shape records, declare a type with named fields. Typed-object field access is statically checked, generates direct field-offset reads, and runs identically under VM and JIT:

type Point { x: int, y: int }
let p = Point { x: 3, y: 4 }
print(p.x)
print(p.y)
print(p.x + p.y)

Nested typed structs presently surface a v0.3-vintage construction-site ordering issue; until that lands, prefer anonymous nesting ({ server: {...} }, as in Nested Objects). Cite v0.4-typed-object-nested-construction in the follow-up tracker.

Vec<T> carries its element type through method chains. The methods below are pure-Shape implementations defined in crates/shape-runtime/stdlib-src/core/vec.shape (verified at HEAD), with a few delegating to runtime intrinsics where noted.

MethodSignatureDescription
first()() -> TFirst element (runtime error if empty)
last()() -> TLast element (runtime error if empty)
len()() -> intElement count
push(x)(T) -> voidAppend in place (requires let mut)
pop()() -> TRemove and return last element
map(f)(T) -> UVec<U>Transform each element
filter(p)(T) -> boolVec<T>Keep matching elements
find(p)(T) -> boolTFirst match
findIndex(p)(T) -> boolintIndex of first match, -1 if none
some(p) / every(p)(T) -> boolboolExistential / universal check
includes(v)(T) -> boolMembership
indexOf(v)(T) -> intFirst index of value, -1 if none
forEach(f)(T) -> voidvoidSide-effect iteration
concat(other)(Vec<T>) -> Vec<T>Append another vector
take(n) / drop(n)(int) -> Vec<T>First / skip-first N elements
slice(s, e)(int, int) -> Vec<T>Sub-range as new vector
reverse()() -> Vec<T>Reversed copy
clone()() -> Vec<T>Shallow copy
join(sep)(string) -> stringJoin elements with separator

Type preservation: filter, take, drop, slice, concat, reverse, clone all return the same Vec<T> — the element type flows through the chain.

reduce(f, init), sort(cmp), flatMap(f), and groupBy(key_fn) are defined in the same vec.shape module but presently surface either VM/JIT divergence (reduce JIT-side fold-state, tracked under v0.4-vec-reduce-jit-fold-state) or a comparator type-inference gap (sort with |a, b| a - b does not resolve the operand kinds; tracked under v0.4-vec-sort-comparator-inference). Prefer a manual for loop or forEach if you hit one of these today.

Numeric-only methods — sum(), avg(), mean(), min(), max(), std(), variance(), dot(other), norm(), normalize(), cumsum(), diff(), abs() — are provided via impl NumericVec for Vec for Vec<number> / Vec<int> receivers.

HashMap<K, V> is an ordered key-value map. Keys are typically strings; integer-keyed maps work end-to-end but hit a string-key assertion in some internal paths today. Insertion order is preserved.

let m = HashMap().set("a", 1).set("b", 2).set("c", 3)
print(m.get("b"))
print(m.has("a"))
print(m.len())
print(m.isEmpty())

HashMap is immutableset and delete return a new HashMap rather than mutating in place. Chain the calls on a single let binding or use let mut and rebind:

// Compile error: bare `m.set(...)` after `let m = HashMap()` is rejected
// by the strict-typing borrow analyzer.
let m = HashMap()
let m2 = m.set("a", 1)
// Chain on a single binding:
let m = HashMap().set("a", 1).set("b", 2).set("c", 3)
print(m.get("b"))
print(m.len())
MethodSignatureDescription
get(key)K → Option<V>Look up by key
set(key, value)(K, V) → HashMap<K,V>Return new map with entry added/updated
has(key)K → boolCheck if key exists
delete(key)K → HashMap<K,V>Return new map without key
len()() → intNumber of entries
isEmpty()() → boolTrue if no entries
map(f)(K, V) → UHashMap<K, U>Transform values
filter(p)(K, V) → boolHashMap<K, V>Keep matching entries
forEach(f)(K, V) → voidvoidIterate over entries

keys(), values(), and entries() are defined in crates/shape-runtime/stdlib-src/core/hashmap_methods.shape but their runtime path currently surfaces the V3-S5 ckpt-5 consumer-cascade tier-3 surface (the Arc<TypedArrayData> result carrier was deleted across ckpt-1..ckpt-4 per ADR-006 §2.7.24 Q25.A SUPERSEDED; rebuild lands at ckpt-6 STRICT close). Cite v0.4-v3-s5-ckpt-6-strict-close in the follow-up tracker.

Object destructuring is the load-bearing path for binding several fields at once. The grammar accepts it:

let point = { x: 3, y: 4 }
let { x, y } = point // VM raises a TypedObject-exception machinery surface today
print(x)
print(y)

The VM destructuring path currently raises a Phase-2c TypedObject-exception machinery surface (AnyError TypedObject build / trace-frame build pending re-emission on the kinded Arc<TypedObjectStorage> model per ADR-006 §2.7.4); JIT works. Use direct field access (obj.field) until the destructuring path re-lands at ckpt-6 STRICT close. Cite v0.4-vm-destructure-typedobject-exception in the follow-up tracker.

let user = { name: "Ada", age: 30 }
print(user.name)
print(user.age)
  • Anonymous and typed-object field access are both statically checked.
  • Vectors and objects compose naturally with match, lambdas, and table transforms.
  • HashMap preserves insertion order and uses immutable semantics.
  • All snippets above without an explicit runnable=false label are byte-exact VM == JIT at HEAD (W15.2-LABEL Phase A convention; cite docs/cluster-audits/v0.3-w15-label-phase-a-close.md §1).