Take dictionary snapshots with recoverable allocation
keys() and values() build new lists in dictionary insertion order,
returning Result[list[T], AllocError]:
def names(scores: &dict[str, i64]) -> Result[list[str], AllocError]:
result = scores.keys_snapshot()?
Ok(result)
def main() -> Result[(), AllocError | IoError]:
mut scores = {"Ada": 10, "Bea": 20}?
saved = scores.values_snapshot()?
scores.insert("Ada", 30)?
print(names(&scores)?)?
print(saved)?
print(scores)?
Ok(())
["Ada", "Bea"]
[10, 20]
{"Ada": 30, "Bea": 20}
For numbers, strings, and other immutable values, the dictionary remains usable
after either outcome. String storage is shared, so the new list does not copy
string contents. The snapshot remains valid after the source
changes or leaves scope. An empty dictionary produces Ok([]) if its new list
header can be allocated.
For owned values such as lists or classes, values() requires an owned
temporary. Request duplication explicitly to keep the
original:
def rows(data: &dict[str, list[i64]]) -> Result[list[list[i64]], AllocError]:
copy(data)?.values_snapshot()
def main() -> Result[(), AllocError | IoError]:
data = {"first": [1, 2]?, "second": [3]?}?
print(rows(&data)?)?
print(data)?
Ok(())
[[1, 2], [3]]
{"first": [1, 2], "second": [3]}
Calling make_dictionary().values_snapshot() instead transfers owned values from
that temporary into the list. If allocation fails, the temporary and its values
are cleaned up. keys_snapshot() never takes the dictionary’s values, so it works on
borrowed dictionaries regardless of their value type.
Both operations reserve all list storage before retaining or transferring any
elements. They report OutOfMemory or CapacityOverflow through AllocError.