Slide 32 of 74

Mutability and value semantics

A second name is a copy
proc main(): void {
	mut Str[dyn] a = ["x", "y"]
	b := a                    // a copy, as far as anyone can tell
	append(a, "z")
	echo b                    // ["x", "y"]
}
Two mut names share, until one is rebound
proc main(): void {
	mut Str[dyn] a = ["x", "y"]
	mut Str[dyn] b = a        // one storage, two names
	append(b, "z")
	echo a                    // ["x", "y", "z"]

	b = ["replaced"]          // rebinds b alone
	echo a                    // still the three
}
A mut parameter writes through the caller's variable
proc grow(vec: mut Str[dyn], tag: Str): void {
	append(vec, tag)
}

proc main(): void {
	mut Str[dyn] tags = ["bee"]
	grow(tags, "hive")
	echo tags                 // ["bee", "hive"]
}

Every value in Hive behaves as a value. Binding a vector, a table, a map or a record to a second name never lets a write through one show up in the other — the compiler copies wherever it has to, deeply, and skips the copy only where it can prove nobody could tell. The exception is asked for: when both names are mut, mut b = a shares the storage, so writing through either reaches both, append included. Rebinding one of them, b = […], gives that name storage of its own and leaves the other holding what they shared. A scalar — a number, a Str, or a record of nothing but scalars — has no storage to share, so two mut names for one are simply two values.

A parameter is a copy the callee cannot change, unless it is declared name: mut T. That is the one place storage crosses a call on purpose: the callee writes through the caller's own variable, so the argument has to be a mut variable or a path into one, like grow(tags) or grow(box.items). Only a proc may declare one, which is the next slide.

A call started on a thread of its own with async is handed a copy of every argument instead, made before the thread starts, so the two sides never write the same storage.