Containers and Binding
Assignment copies a value into a container, binding shares the container itself, and itemization wraps a value in one; together they decide what is copied, what is shared and what counts as a single item.
A Raku variable is a name for a container, and the container holds the value. Most of the time the difference is invisible: $x = 5 puts 5 into the container that $x names, and reading $x takes it out again. It becomes visible when two names share one container, when a list must count as a single thing, and when a change has to be undone later.
Three operations are behind all of it. Assignment, =, copies a value into a container. Binding, :=, makes a name refer directly to a container or a value, without copying anything. Itemization wraps a value in a container so that it counts as one item in a list. Whether = is item or list assignment is decided by the target's sigil, which is covered in Who Takes the Operand; the methods of lists and hashes are in Lists, Arrays, Seqs and Slips and Hashes, Maps and Pairs. This chapter is about the containers underneath.
5.1 A $ variable keeps its value in a Scalar container
Method calls and operators look through a container to the value inside, so $x.^name names the type of the value. .VAR is the way to reach the container itself. An Array is its own container and keeps one Scalar per element, which is what makes @a[0] = 5 possible; a List, built by the comma, holds its values bare.
my $x = 42; say $x.^name; say $x.VAR.^name; my @a = 1, 2; say @a.VAR.^name; say @a[0].VAR.^name; say (1, 2)[0].VAR.^name;
Int Scalar Array Scalar Int
A container is more than a box. It knows the variable's name, its type constraint and its default, and assigning Nil to it puts the default back:
my Int $count is default(0) = 5; say $count.VAR.name; say $count.VAR.of.^name; $count = Nil; say $count;
$count Int 0
The editor’s engine, Raku++, prints something else here
$count Mu 0
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
5.2 := gives a container a second name; = copies the value
Assignment reads the value on the right and stores it in the container on the left. Binding stores nothing: it makes the name on the left refer to whatever the right side is, here the container of $a. After my $b := $a the two names are one variable.
my $a = 1; my $b := $a; my $c = $a; $b = 2; say "$a $b $c"; say $a =:= $b; say $a =:= $c;
2 2 1 True False
=:= asks whether two names lead to the same container. It chains like any comparison, and it is not ===: two elements that hold equal values are ===, but they live in different containers.
my $a = 1; my $b := $a; my $c := $b; say $a =:= $b =:= $c; my @x = 1; my @y = 1; say @x[0] === @y[0]; say @x[0] =:= @y[0];
True True False
5.3 A name bound to a value cannot be assigned to
The right side of := does not have to be a variable. When it is any other expression, the name is bound to the resulting value itself, and there is no container to receive a later assignment:
my $x = 5; my $y := $x + 1; say $y.VAR.^name; $y = 7;
Int
Cannot assign to an immutable value in block <unit> at example.raku line 4
The editor’s engine, Raku++, prints something else here
Scalar
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
The operator ::= parses, but Rakudo refuses it at compile time:
my $x ::= 1;
(nothing)===SORRY!=== Error while compiling example.raku "::=" not yet implemented. Sorry. at example.raku:1 ------> my $x ::= 1<HERE>;
5.4 A sigilless name is a binding
my \x = … looks like an assignment but is a binding: a name without a sigil never gets a container of its own. Given a variable, it becomes another name for that variable's container. Given a value, it is that value, and a list bound this way is iterated element by element.
my $y = 1; my \x = $y; x = 5; say $y; my \z = 42; say z.VAR.^name; my \w = (1, 2, 3); my $n = 0; $n++ for w; say $n;
5 Int 3
The editor’s engine, Raku++, prints something else here
1 Int 3
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
5.5 A $ name bound to a list is not an item
Assigning a list to a $ variable puts the list inside a Scalar, and a list inside a Scalar counts as one item, so for sees one thing. Binding skips the container: the same list bound to a $ name is iterated element by element, and list assignment spreads it. .raku shows the difference as a leading $.
my $assigned = (1, 2, 3); my $bound := (1, 2, 3); say $assigned.raku; say $bound.raku; for $assigned { say "assigned: $_" } for $bound { say "bound: $_" } my @a = $bound; say @a.elems;
$(1, 2, 3) (1, 2, 3) assigned: 1 2 3 bound: 1 bound: 2 bound: 3 3
The editor’s engine, Raku++, prints something else here
$(1, 2, 3) (1, 2, 3) assigned: 1 2 3 bound: 1 2 3 3
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
5.6 Assignment returns the container it assigned to
The value of an assignment is its left side, the container itself, not a copy of the value that went in. That is why assignments chain from the right, and why the result of an assignment can be used, incremented or assigned to again:
my $x; my $y = ($x = 5) + 1; say "$x $y"; ($x = 10)++; say $x; my $a = my $b = 3; say "$a $b"; say ($a = 7).VAR.^name;
5 6 11 3 3 Scalar
The editor’s engine, Raku++, prints something else here
5 6 11 3 3 Int
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
5.7 An assignment metaoperator takes a whole expression on its right
$x op= y applies op to the variable and the right side, then assigns the result. The right side is everything down to the item-assignment level, so a ternary, a min or a concatenation on the right is finished before op is applied:
my $s = "ab"; $s x= 1 + 1; say $s; my $m = 5; $m max= 3 min 1; say $m; my $n = 1; $n += 1 ?? 10 !! 20; say $n; my $c = "a"; $c ~= "b" ~ "c"; say $c;
abab 5 11 abc
$m max= 3 min 1 compares 5 with 3 min 1, which is 1, and keeps 5. Had max= taken only the 3, the result would have been (5 max 3) min 1, which is 1.
5.8 An assignment metaoperator is an item assignment, even on an array
A plain = becomes list assignment, looser than the comma, when its target has an @ or % sigil, even when the target is a single element such as @a[0] (the sigil of the target decides). The metaoperators do not follow that rule: +=, ~= and the rest always sit on the item-assignment level, tighter than the comma, whatever the sigil of the target.
my @a = 1, 2; @a[0] = 10, 20; say @a.raku; my @b = 1, 2; @b[0] += 10, 20; say @b.raku;
[(10, 20), 2] [11, 2]
WARNINGS for example.raku: Useless use of constant integer 20 in sink context (lines 5, 5)
The = 10, 20 stores a list in the slot; the += 10, 20 adds 10 and leaves the 20 behind, which the compiler reports as useless.
5.9 .= assigns a method's result, on the method-call level
$s .= uc is $s = $s.uc: the method is called on the variable's value and the result is assigned back. Unlike the other metaoperators, .= sits on the method-postfix level, the tightest of all, so it is done before any infix around it:
my $s = "ab"; $s .= uc; say $s; my $t = "ab"; say ($t .= uc ?? "yes" !! "no"); say $t; my @a = 3, 1, 2; @a .= sort; say @a;
AB yes AB [1 2 3]
In the second say the ternary tests the result of $t .= uc, the string "AB", and the assignment has already happened.
5.10 //=, ||= and &&= evaluate their right side only when needed
The short-circuit operators keep their short circuit in the assignment form. $u //= f() calls f only when $u is undefined, ||= only when it is false, and &&= only when it is true:
my $calls = 0; sub fallback { $calls++; 99 } my $u = 0; $u //= fallback; say "$u, calls: $calls"; $u ||= fallback; say "$u, calls: $calls"; my $v = 1; $v &&= fallback; say "$v, calls: $calls";
0, calls: 0 99, calls: 1 99, calls: 2
0 is defined, so //= leaves it alone; it is false, so ||= replaces it.
5.11 A List holds bare values, unless it was given containers
A List is immutable: its slots cannot be assigned to or bound, and push refuses to grow it. A List built from variables, though, holds their containers, and assigning through such a slot changes the variable:
my $l = (1, 2); try { $l[0] = 5 }; say $!.message; try { $l[0] := 5 }; say $!.^name; try { $l.push(3) }; say $!.message; my $x = 1; my $m = ($x, 2); $m[0] = 5; say $x;
Cannot modify an immutable List ((1 2)) X::Bind Cannot call 'push' on an immutable 'List' 5
The editor’s engine, Raku++, prints something else here
Cannot modify an immutable List ((1 2)) X::Bind Cannot call 'push' on an immutable 'List'
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
Turning an Array into a List with .List copies the values out of the element containers. The List is a snapshot: later writes to the Array do not reach it.
my @a = 1, 2; my $snapshot = @a.List; @a[0] = 9; say $snapshot; say $snapshot[0].VAR.^name;
(1 2) Int
5.12 Iterating an Array hands out its containers
for and .map set $_ to each element's own Scalar, so a loop over an array can change it. A pointy-block parameter is read-only even when the element is a container, unless it is introduced with <-> (or declared is rw); is copy gives the block a private copy.
my @a = 1, 2; for @a { $_ *= 10 } say @a; @a.map({ $_ = 0 }); say @a; for @a <-> $x { $x = 5 } say @a; for @a -> $x is copy { $x = 7 } say @a;
[10 20] [0 0] [5 5] [5 5]
A loop over literal values gets the values, which cannot be assigned, and a plain -> $x refuses too, with a different message. A comma list of variables is not an Array, but it holds the variables' containers, so a loop over it writes through:
my @a = 1, 2; try { for 1, 2 { $_ = 0 } }; say $!.message; try { for @a -> $x { $x = 0 } }; say $!.message; my ($p, $q) = 1, 2; for $p, $q { $_ *= 3 } say "$p $q";
Cannot assign to an immutable value Cannot assign to a readonly variable or a value 3 6
5.13 Iterating a Hash hands out its containers too
Every value of a Hash lives in its own Scalar. .values, the Pairs of plain iteration and .kv all hand out those containers:
my %h = a => 1, b => 2; for %h.values { $_ *= 10 } say %h.sort; for %h { .value += 1 } say %h.sort; for %h.kv -> $k, $v is rw { $v = 0 } say %h.sort;
(a => 10 b => 20) (a => 11 b => 21) (a => 0 b => 0)
The editor’s engine, Raku++, prints something else here
(a => 10 b => 20) (a => 10 b => 20) (a => 0 b => 0)
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
Without is rw, the $v of the last loop would be read-only, as for arrays.
5.14 := between elements makes two slots one
An array or hash element can be bound like a variable. Binding one element to another makes the two slots share a container; binding a variable to an element, or an element to a variable, makes an alias in the same way.
my @a = 1, 2; @a[0] := @a[1]; @a[1] = 7; say @a; my $x := @a[0]; $x = 9; say @a; my %h; my $v = 1; %h<k> := $v; $v = 3; say %h<k>;
[7 7] [9 9] 3
Binding a slot to a plain value leaves it with no container, and the slot then refuses assignment:
my %h; %h<k> := 5; %h<k> = 6;
(nothing)Cannot assign to an immutable value in block <unit> at example.raku line 3
5.15 my (…) := binds read-only, and is rw makes an alias
Parentheses after my on the left of := are a signature, and binding a list to it works like passing arguments to a routine: each parameter takes one element, read-only, even when the element is an Array's container. A parameter declared is rw is bound to the container itself.
my @a = 1, 2; my ($x, $y) := @a; say "$x $y"; try { $x = 9 }; say $!.message; my ($p is rw, $q) := @a; $p = 9; say @a;
1 2 Cannot assign to a readonly variable or a value [9 2]
The editor’s engine, Raku++, prints something else here
(nothing on standard output; standard error says:)
===SORRY!=== Parse error at line 6: Two terms in a row
6 | my ($p is rw, $q) := @a;
at example.raku:6Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
5.16 my (…) := checks the count; my (…) = does not
Assigning to a list of variables fills them in order, resets any left over to their default and drops extra values. Binding is a signature match, so the counts must fit, unless the signature allows otherwise with a slurpy or an optional parameter:
my ($a, $b) = 1, 2, 3; say ($a, $b).raku; my ($c, $d) = 1; say ($c, $d).raku; my ($e, *@rest) := (1, 2, 3); say ($e, @rest).raku; my ($x, $y) := (1, 2, 3);
(1, 2) (1, Any) (1, [2, 3])
Too many positionals passed to '<unit>'; expected 2 arguments but got 3 in block <unit> at example.raku line 7
The editor’s engine, Raku++, prints something else here
(1, 2) (1, Any) (1, [2, 3])
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
The message calls the signature's owner <unit>, the name of the program's mainline, because no routine is involved.
5.17 A bound @ or % name takes the object's own type
my @a = … creates an Array and copies values into it. my @a := … makes @a a name for the object on the right, which only has to be Positional: a List, a Range or an Array will do. The variable then behaves as that object does. A Seq is not Positional, and binding one to an @ name is a type error.
my @l := (1, 2); say @l.^name; my @r := 1..3; say @r.^name; my %m := Map.new((a => 1)); say %m.^name; try { my @s := (1, 2).map(* + 1) }; say $!.message;
List Range Map Type check failed in binding; expected Positional but got Seq ((2, 3).Seq)
The editor’s engine, Raku++, prints something else here
List Array Map Nil
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
Assigning to a bound List does not replace it. The values on the right are assigned to the List's elements one by one, the same element-by-element assignment that ($a, $b) = 3, 4 performs. A List of plain values fails at its first element; a List of variables passes the values on to them:
my @k := (1, 2); try { @k = 3, 4 }; say $!.message; my ($a, $b); my @l := ($a, $b); @l = 3, 4; say "$a $b";
Cannot modify an immutable Int (1) 3 4
The editor’s engine, Raku++, prints something else here
Nil
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
5.19 @a = @a, 3 puts the array inside itself
An array on the right of a list assignment is stored as one element, not copied, as the previous corner showed. So an array that appears in its own assignment ends up containing itself. @b ,= 3 is the same assignment written shorter, and it does the same:
my @a = 1, 2; @a = @a, 3; say @a.elems; say @a[0] === @a; my @b = 1, 2; @b ,= 3; say @b[0] === @b; my @c = 1, 2; @c = |@c, 3; say @c;
2 True True [1 2 3]
A slip, |@c, spreads the old elements into the new list, which is what was meant; @c.push(3) does it without building a new list.
5.20 $(…) and .item make a value one item
Putting a value in a Scalar itemizes it: whatever is inside, the Scalar counts as a single item in any list. $( … ), the method .item and the sub item all do it, and $@a and $%h itemize an array or hash variable. A list assignment then stores the item as one element:
my @a = (1, 2); my @b = $(1, 2); my @c = (1, 2).item; say @a.raku; say @b.raku; say @c.raku; my @d = 3, 4; say $@d.raku; say item(1, 2).raku;
[1, 2] [(1, 2),] [(1, 2),] $[3, 4] $(1, 2)
A $ variable that holds a list is an item already, which is the difference between the two assignments below:
my @x = [1, 2, 3]; my $r = [1, 2, 3]; my @y = $r; say @x.elems; say @y.elems;
3 1
5.21 for iterates an item once
for walks through a list, but it does not walk into an item. A list, an array or a hash held in a $ variable is one item, so the loop body runs once, with the whole thing in $_:
my $list = (1, 2, 3); for $list { say "got: $_" } for @$list { say "each: $_" } my $h = { a => 1, b => 2 }; my $n = 0; $n++ for $h; say $n;
got: 1 2 3 each: 1 each: 2 each: 3 1
@$list, or $list.list, asks for the list inside the item; %$h does the same for a hash.
5.22 <> takes a value out of its container
The zen slice <> decontainerizes: it returns the value that a Scalar holds, without the Scalar. It is the shortest way to make an item behave as a list again, and it works on array elements, which are all Scalars:
my $list = (1, 2, 3); say $list.VAR.^name; say $list<>.VAR.^name; for $list<> { say $_ } my @a = (1, 2), 3; for @a[0] { say "once: $_" } for @a[0]<> { say "each: $_" }
Scalar List 1 2 3 once: 1 2 each: 1 each: 2
5.23 .raku marks an item with $, except inside an Array
say and .gist never show containers. .raku does: an itemized list prints with a leading $, and so does a hash value that holds a list. Inside an Array every element is in a Scalar, so the marker would tell nothing, and .raku leaves it out:
say $(1, 2); say $(1, 2).raku; say (1, $(2, 3)).raku; say [1, $(2, 3)].raku; my %h = a => [1, 2]; say %h.raku; say (a => [1, 2]).raku;
(1 2)
$(1, 2)
(1, $(2, 3))
[1, (2, 3)]
{:a($[1, 2])}
:a([1, 2])The last two lines differ because a hash stores each value in a Scalar of its own, while a Pair holds exactly what it was given, here a bare Array.
5.24 flat stops at a Scalar container
flat descends into every list it meets, except those in a Scalar. Array elements are all in Scalars, so .flat on an array of arrays flattens nothing, while the same arrays sitting directly in a List are spread:
say (1, (2, (3, 4))).flat.raku; say (1, [2, 3]).flat.raku; say [1, [2, 3]].flat.raku; say (1, $(2, 3)).flat.raku; say [1, [2, 3]].flat(:hammer).raku;
(1, 2, 3, 4).Seq (1, 2, 3).Seq (1, $[2, 3]).Seq (1, $(2, 3)).Seq (1, 2, 3).Seq
:hammer breaks through the containers. A slip does not: |@a puts the elements into a new list, but each one is still in its Scalar. Taking the lists out of their containers first works:
my @a = [1, 2], [3, 4]; say @a.flat.elems; say (|@a).flat.elems; say @a.map(*.Slip).elems; say @a>>.list.flat.elems;
2 2 4 4
The editor’s engine, Raku++, prints something else here
2 4 4 4
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
5.25 ($a, $b) = … reads the whole right side first
($a, $b) = … is a list assignment to each variable in turn. The right side is read into values before any variable changes, so a swap needs no temporary. A * in the list skips a value, a variable with no value left is reset to its default (a typed variable to its type object), and an array target takes all the rest:
my $x = 1; my $y = 2; ($x, $y) = ($y, $x); say "$x $y"; my ($p, $q); ($p, *, $q) = 1, 2, 3; say "$p $q"; my Int $i = 5; ($i, my $z) = (); say ($i, $z).raku; my ($first, @rest) = 1, 2, 3; say ($first, @rest).raku;
2 1 1 3 (Int, Any) (1, [2, 3])
The editor’s engine, Raku++, prints something else here
2 1 1 3 (Any, Any) (1, [2, 3])
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
The * works only with variables declared beforehand. Parentheses after my are a signature, and my ($a, *, $c) is a compile-time error, Malformed parameter.
5.26 A nested group in my (…) = does not destructure
Inside my (…) on the left of =, a parenthesised group of variables does not receive a sublist. Its variables are declared but left as Mu, not even Any, and the value in its position is dropped. With := the same declaration is a signature with a sub-signature, and it unpacks as expected:
my ($a, ($b, $c), $d) = 1, (2, 3), 4; say ($a, $b, $c, $d).raku; my ($e, ($f, $g), $h) := 1, (2, 3), 4; say ($e, $f, $g, $h).raku;
(1, Mu, Mu, 4) (1, 2, 3, 4)
The editor’s engine, Raku++, prints something else here
(1, 2, 3, 4) (1, 2, 3, 4)
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
5.27 A routine strips the container unless declared is rw or is raw
A do block hands back the container of its last expression, so it can be assigned to. A routine strips the container: the caller receives the value, and assigning to the call fails. A routine declared is rw or is raw returns the container itself.
my $n = 1; (do { $n }) = 2; say $n; sub raw is raw { $n } raw() = 3; say $n; sub number { $n } number() = 4;
2 3
Cannot modify an immutable Int (3) in block <unit> at example.raku line 8
Being an item survives the stripping. A routine that returns a $ variable holding a list returns one item, which a for loop does not walk into:
my $l = (1, 2); sub items { $l } say items().raku; for items() { say "got: $_" }
$(1, 2) got: 1 2
5.28 A Pair keeps the container of its value
key => $x does not copy $x: the Pair holds the container of $x, and writing through .value changes the variable. The Pairs that a hash hands out hold the hash's own containers in the same way. A Pair built from a literal holds a plain value, and its .value is read-only; the key is always read-only.
my $x = 1; my $p = a => $x; $p.value = 5; say $x; $x = 7; say $p.value; my %h = a => 1; %h.pairs[0].value = 9; say %h<a>; my $q = a => 1; try { $q.value = 2 }; say $!.^name; try { $p.key = "b" }; say $!.^name;
5 7 9 X::Assignment::RO X::Assignment::RO
Storing the Pair in a hash is different: hash assignment copies each value into a new container, so after my %g = a => $x, assigning to %g<a> leaves $x alone.
5.29 Pairs that hold the same variable are not identical
=== compares two Pairs by key and value only when the values are plain values. A Pair that holds a container compares by identity, so two Pairs built from the same variable are different, although eqv finds them equal. .unique goes by ===:
my $x = 1; say (a => 1) === (a => 1); say (a => $x) === (a => $x); say (a => $x) eqv (a => 1); say ((a => 1), (a => 1)).unique.elems; say ((a => $x), (a => $x)).unique.elems;
True False True 1 2
The editor’s engine, Raku++, prints something else here
True True True 1 1
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
5.30 A Map is immutable, but the containers inside it are not
A Map refuses to change: adding a key, deleting one, binding one and assigning to one that holds a plain value all die. What a Map stores, though, is whatever its Pairs held. A Pair built from a variable brings the variable's container, and Map.new(%h) takes over the hash's containers, so a write through the Map, or through the hash, still gets through. New keys added to the hash do not appear. %h.Map copies the values instead.
my $x = 1; my $m = Map.new((a => $x, b => 2)); $m<a> = 5; say $x; try { $m<b> = 5 }; say $!.message; my %h = k => 1; my $live = Map.new(%h); my $snap = %h.Map; %h<k> = 2; %h<new> = 3; say $live<k>; say $snap<k>; say $live.elems;
5 Cannot change key 'b' in an immutable Map 2 1 1
The editor’s engine, Raku++, prints something else here
(nothing on standard output; standard error says:)
Cannot change key 'a' in an immutable Map
in block <unit> at example.raku line 3
3 | $m<a> = 5;Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
5.31 temp restores a variable when its block is left
temp $x saves the current value of $x and puts it back when the enclosing block is left, normally or by an exception. In between the variable can be changed freely, and every routine called from inside the block sees the new value:
our $level = 1; sub show { say "level $level" } sub deeper { temp $level = 2; show; } deeper; show;
level 2 level 1
It works on whole arrays and hashes as well as on scalars and single elements:
my $x = 1; try { temp $x = 2; die "oops" }; say $x; my @a = 1, 2; { temp @a; @a.push(3); say @a.elems } say @a.elems; my %h = a => 1; { temp %h<a> = 9; say %h<a> } say %h<a>;
1 3 2 9 1
5.32 temp takes one term, and it must be a container
temp and let are prefix operators on the autoincrement level. They take a single term, so in temp $x = 2 ~ "z" the assignment gets the whole concatenation. temp returns the container it saved, so it can be the target of any assignment, and without one the variable keeps its current value until it is changed. Applied to something that is not a container, temp dies at run time:
my $x = 1; { temp $x = 2 ~ "z"; say $x } say $x; { (temp $x) ~= "!"; say $x } say $x; try { temp 42 }; say $!.^name;
2z 1 1! 1 X::Localizer::NoContainer
The editor’s engine, Raku++, prints something else here
2z 1 1! 1 X::Assignment::RO
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
5.33 A do block returns a temp variable already restored
A do block returns the container of its last expression, not a copy of its value, as shown above. When that container was localized with temp, the caller reads it after the block has been left, and by then the old value is back. A routine strips the container before it leaves, so it returns the temporary value:
my $x = 1; say do { temp $x = 5; $x }; say do { temp $x = 5; $x<> }; say do { temp $x = 5; +$x }; sub f { temp $x = 5; $x } say f();
1 5 5 5
The editor’s engine, Raku++, prints something else here
5 5 5 5
Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.
Decontainerizing with <>, or any expression that computes a new value, fixes the value before the block ends.
5.34 let keeps its change only if the block succeeds
let is temp with a condition. The old value comes back when the block is left by an exception, or when it ends with an undefined value such as Nil, Any or a Failure. Any defined value counts as success and keeps the change, even 0 or False, and it does not matter whether the caller uses the value.
my $x; sub ends-zero { let $x = 2; 0 } sub ends-false { let $x = 2; False } sub ends-nil { let $x = 2; Nil } sub ends-fail { let $x = 2; fail "no" } $x = 1; ends-zero; say "0: $x"; $x = 1; ends-false; say "False: $x"; $x = 1; ends-nil; say "Nil: $x"; $x = 1; my $f = ends-fail; say "Failure: $x"; $x = 1; try { let $x = 2; die "oops" }; say "die: $x"; $x = 1; { let $x = 2; Any }; say "Any: $x";
0: 2 False: 2 Nil: 1 Failure: 1 die: 1 Any: 1
The last line shows that the rule is not limited to routines: a bare block that ends with Any restores as well.