Structured data access
config = {
server: { host: "localhost", port: 8080 },
features: ["auth", "cache"],
}
print(config.server.port) # 8080
print(config.features[0]) # "auth"
config.server.port = 9090 # path assignment; root must be mutable
Keys, bare or quoted
A bare key must be an identifier - keywords are allowed, so x.type is fine. A quoted key can be anything, which is what makes HTTP headers and hyphenated JSON expressible inline:
http.response(303, "", { location: "/", "set-cookie": "a=b; HttpOnly" })
{ a: 1 } and { "a": 1 } are the same map. A quoted key is taken verbatim - {expr} does not interpolate in key position, because a map literal's shape is fixed at parse time. ecko fmt canonicalizes to the bare form where the key is a plain identifier.
Access is strict
xs[9] # index 9 is out of bounds (len 3)
m.key # map has no key 'key'
s.field # struct has no field 'field'
The nullable lookup is get:
get(m, "key") # null on a miss
get(xs, 9) # null on a miss
Reach for get when absence is expected, and indexing when it is not. That choice is the whole point: a strict read fails at the mistake, while a nullable read that silently returns null propagates the problem into unrelated code. Rule 1 of the error dialect is that only the documented nullable lookups return null.
match tests rather than accesses, so a non-matching pattern is never an error - which makes it the right tool for data of uncertain shape.
Slicing
Strings, lists and bytes slice with range syntax. Strings slice by character, so multi-byte UTF-8 is handled correctly:
s = "hello"
s[1..3] # "el" exclusive end
s[0..=2] # "hel" inclusive end
s[..3] # "hel" open start
s[2..] # "llo" open end
s[-3..] # "llo" negative from the end
s[1..-1] # "ell"
xs = [1, 2, 3, 4]
xs[1..3] # [2, 3]
Slices are total where indexing is strict: out-of-range bounds clamp (s[2..99] is "llo", s[7..9] is "") and a reversed range is empty. Slicing takes what is there and never errors.
That asymmetry is deliberate. xs[i] asks for one specific element, and its absence is a mistake. xs[a..b] asks for a region, and "less than you asked for" is a sensible answer.
..= requires an end index: s[0..=] is a parse error, and s[0..] is the open form.
Determinism
Maps print and JSON-encode with sorted keys, and map iteration yields pairs in sorted key order. Output is reproducible without sorting anything yourself.