Walk recursive data with a reference
A reference binding declared with mut can advance through a recursive
structure by borrowing each next step from itself:
class Folder:
name: str
inside: list[Folder]
def innermost(top: &Folder) -> str:
mut folder = top
while len(folder.inside) > 0:
folder = &folder.inside[0]
folder.name
def main() -> Result[(), AllocError | IoError]:
notes = Folder("notes", []?)
work = Folder("work", [notes]?)
home = Folder("home", [work]?)
print(innermost(&home))?
Ok(())
notes
folder starts at top. Each &folder.inside[0] is borrowed from folder, so
the assignment is allowed. home stays borrowed until folder’s last use.
The binding can also be pointed at another place within the value it first borrowed, without going through itself. This retargets it: the old loan ends at the assignment. A search can remember its best element this way, without keeping an index:
def longest(words: &list[str]) -> &str:
mut best = &words[0]
for i in range(1, len(words)):
if words[i].byte_len() > best.byte_len():
best = &words[i]
best
def main() -> Result[(), AllocError | IoError]:
words = ["fig", "banana", "kiwi"]?
print(longest(&words))?
Ok(())
banana
best first borrows from words, so &words[i] is a valid new target. See
reference binding reassignment
for the full rule. Pointing it at a different value is rejected, even one of the
same type:
def main() -> Result[(), AllocError | IoError]:
first = [1, 2]?
second = [3]?
mut view = &first
view = &second
print(view)?
Ok(())
reference binding `view` can be reassigned only to a reference into the owner it first borrowed
Matching borrowed values also leaves the owner in place while inspecting its contents.
The same descendant-cursor pattern can lend exclusive access while updating each node. See the complete mutable chain walk. For branching traversal, a stack of references is currently unsupported; an arena with a worklist of IDs is an alternative.