Choosing collection types¶
Use builtin list, dict, and set first. Reach for std.collections when the collection's behavior is part of the program design rather than just a storage detail.
| Need | Use |
|---|---|
| Append and pop at both ends | Deque[T] |
| Count occurrences | Counter[T] |
| Materialize a value for missing keys | DefaultDict[K, V] |
| Keep insertion order visible | OrderedDict[K, V] or OrderedSet[T] |
| Traverse in sorted order | SortedDict[K, V] or SortedSet[T] |
| Layer overrides over defaults | ChainMap[K, V] |
| Always process the next highest-priority item | PriorityQueue[T] |
Use Deque for front-and-back queues¶
Use Deque[T] when both ends are active. If you only append and pop at the end, a builtin list is simpler.
from std.collections import Deque
def main() -> None:
mut queue = Deque[str]()
queue.append("normal")
queue.appendleft("urgent")
next = queue.popleft()
println(next)
Deque exposes both Python-style names (appendleft, popleft) and Rust-style names (push_front, pop_front). Choose one style in a module and stay consistent.
Use Counter for counted membership¶
Use Counter[T] when the count is the data model. Do not hand-roll a dict[T, int] unless the update rules are unusual enough that the named collection would hide the intent.
from std.collections import Counter
def main() -> None:
counts = Counter.from_iter(["red", "blue", "red"])
assert counts.get("red") == 2
assert counts.total() == 3
Use DefaultDict when missing keys should create values¶
Use DefaultDict[K, V] when reading a missing key should materialize a default. Use get() when you want to inspect a key without creating it.
from std.collections import DefaultDict
def zero() -> int:
return 0
def main() -> None:
mut counts: DefaultDict[str, int] = DefaultDict.with_factory(zero)
counts["red"] = counts["red"] + 1
assert counts.get("red") == Some(1)
Use with_default(value) when cloning one value is right for every missing key. Use with_factory(factory) when each missing key should get a fresh value.
Use ordered collections when insertion order matters¶
Use OrderedDict[K, V] and OrderedSet[T] when display, serialization, or user-facing iteration must follow insertion order. Use builtin dict or set when order is not part of the contract.
from std.collections import OrderedDict, OrderedSet
def main() -> None:
mut headers = OrderedDict[str, str]()
headers.set("content-type", "application/json")
headers.set("cache-control", "no-store")
assert headers.keys() == ["content-type", "cache-control"]
mut seen = OrderedSet[str]()
seen.add("alice")
seen.add("bob")
seen.add("alice")
assert seen.to_list() == ["alice", "bob"]
Use sorted collections for deterministic sorted traversal¶
Use SortedDict[K, V] and SortedSet[T] when consumers rely on sorted order. The key or value type must support normal ordering.
from std.collections import SortedDict, SortedSet
def main() -> None:
mut scores = SortedDict[str, int]()
scores.set("bob", 7)
scores.set("alice", 10)
assert scores.keys() == ["alice", "bob"]
mut ids = SortedSet[int]()
ids.add(30)
ids.add(10)
ids.add(20)
assert ids.to_list() == [10, 20, 30]
Use ChainMap for layered configuration¶
Use ChainMap[K, V] when lookup should prefer overrides but fall back to defaults. Earlier layers win.
from std.collections import ChainMap, OrderedDict
def main() -> None:
overrides = OrderedDict.from_items([("region", "us-east-1")])
defaults = OrderedDict.from_items([("region", "eu-west-1"), ("retries", "3")])
cfg = ChainMap.from_layers([overrides, defaults])
assert cfg["region"] == "us-east-1"
assert cfg["retries"] == "3"
For model or class defaults, pass a field snapshot. The snapshot is read-only inside the chain map.
from std.collections import ChainMap, OrderedDict
model RuntimeDefaults:
host: int
port: int
def main() -> None:
defaults = RuntimeDefaults(host=2, port=3)
mut cfg: ChainMap[str, int] = ChainMap.from_field_layers([defaults.__field_items__()])
cfg.push_layer(OrderedDict.from_items([("host", 1)]))
assert cfg["host"] == 1
assert cfg["port"] == 3
Use PriorityQueue for next-item scheduling¶
Use PriorityQueue[T] when every pop should return the next item by ordering. The default is min-first; use PriorityOrder.MaxFirst when larger values should come first.
from std.collections import PriorityOrder, PriorityQueue
def main() -> None:
mut next_retry = PriorityQueue[int]()
next_retry.push(30)
next_retry.push(10)
next_retry.push(20)
assert next_retry.pop() == 10
mut largest_first = PriorityQueue[int].with_order(PriorityOrder.MaxFirst)
largest_first.push(30)
largest_first.push(10)
assert largest_first.pop() == 30