Maps ({"a": 1})

Maps in Luma are key-value collections. They’re the right choice when you want to look things up by a key instead of by index.

Think of them as “dictionaries” or “hash maps” in other languages.

Map Types

Map types use curly braces with a key type and a value type:

{K: V}

Some common examples:

{str: int}    // map from strings to ints
{int: str}    // map from ints to strings
{str: bool}   // feature flags by name
{str: Person} // map from username to a Person struct
{str: ?int}   // map from string to optional int

You use map types in variable declarations just like any other type:

ages: {str: int}
users: {int: str}
configs: {str: ?int}

Quick Reference

Operation Syntax Description
Declare ages: {str: int} = {} Empty typed map
Literal {"Alice": 30, "Bob": 25} Map with initial values
Read ages["Alice"] Get value by key (zero value if missing)
Write ages["Alice"] = 31 Set or update a key
Length ages.len() Number of key-value pairs
Walk (k, v) ages.walk(k, v) -> { ... } Iterate over keys and values
Walk (v) ages.walk(v) -> { ... } Iterate over values only

Map Literals

You create a map value using { key: value, ... }:

ages: {str: int} = {
    "Alice": 30,
    "Bob": 25,
}

prices: {str: float} = {
    "apple": 0.99,
    "banana": 1.19,
}

A few notes:

  • Keys and values can be any valid expressions, as long as they match the declared types.
  • Trailing commas are allowed and often make diffs nicer.
  • An empty map literal is just {} but you usually want to give the variable a type so the compiler knows key/value types:
counters: {str: int} = {}

Reading Values

Index into a map with map[key]:

ages: {str: int} = {
    "Alice": 30,
    "Bob": 25,
}

print(ages["Alice"])  // 30
print(ages["Bob"])    // 25

If a key is missing, you get the zero value for the value type (0 for int, "" for str, false for bool). To distinguish “missing” from “present but zero”, use an optional value type:

scores: {str: ?int} = {}
scores["Alice"] = 42

print(scores["Alice"]) // 42
print(scores["Bob"])   // nil (no score recorded)

Updating Maps

Assign to a map entry with map[key] = value:

ages: {str: int} = {
    "Alice": 30,
}

ages["Alice"] = 31
ages["Bob"] = 22

print(ages["Alice"])  // 31
print(ages["Bob"])    // 22

This works for both updating existing keys and adding new ones.

Length

ages: {str: int} = {"Alice": 30, "Bob": 25}
print(ages.len())    // 2

empty: {str: int} = {}
print(empty.len())   // 0

Walking a Map

Use .walk() to iterate over key-value pairs:

ages: {str: int} = {
    "Alice": 30,
    "Bob": 25,
}

ages.walk(key, value) -> {
    print("${key}: ${value}")
}

With one parameter, you get the values:

ages.walk(value) -> print(value)
// 30
// 25

Maps are unordered — iteration order is not guaranteed.

Use skip to skip an iteration, or stop to exit the walk early:

ages.walk(key, value) -> {
    if value < 18 { skip }
    print("${key} is an adult")
}

See Walk Loops (skip & stop) for more details.

Maps With Structs

struct Person {
    name: str
    age: int
}

people: {str: Person} = {
    "alice": Person("Alice", 30),
    "bob":   Person("Bob", 25),
}

alice: Person = people["alice"]
print(alice.name) // Alice
print(alice.age)  // 30

Maps With Optional Values

Optional values are useful when “no value yet” is different from “value is zero”:

last_login: {str: ?int} = {}

user: str = "alice"

if last_login[user] == nil {
    print("First login for ${user}")
} else {
    print("Welcome back, ${user}")
}

Maps With List Values

people_by_city: {str: [str]} = {
    "Tallinn": ["Alice", "Bob"],
    "Riga":    ["Carol"],
}

print(people_by_city["Tallinn"])     // [Alice Bob]
print(people_by_city["Tallinn"][0])  // Alice

Lists of Maps

A common pattern is a list of maps representing rows of data:

rows: [{str: int}] = [
    {"year": 2023, "users": 100},
    {"year": 2024, "users": 250},
]

rows.walk(row) -> {
    print("Year ${row["year"]}, users: ${row["users"]}")
}

Maps and lists compose cleanly and follow the same type syntax:

  • [T] — list of T
  • {K: V} — map from K to V
  • ?T — optional T

Tips

  • Always give maps an explicit type in declarations. This lets the compiler generate typed Go maps instead of a generic map[interface{}]interface{}.
data = {"a": 1}         // generic map (less type safety)
data: {str: int} = {    // preferred
    "a": 1,
}
  • Missing keys return the zero value for the value type. If you need to distinguish “missing” from “present”, store an optional value (?T).
  • Maps are unordered. Don’t rely on iteration order.
Last updated on