Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.