Store Proxy Fetch

Published: (February 20, 2026 at 06:39 AM EST)
8 min read
Source: Dev.to

Source: Dev.to

Cases of UPPER – Part 12

Containers in Raku

This is part 12 of the Cases of UPPER series, which documents the Raku syntax elements that are written completely in UPPERCASE.
In this post we look at containers – the objects that actually hold values in Raku.

Binding vs. Assignment

Raku really only knows about binding (:=).
Assignment (=) is just syntactic sugar that ultimately performs a bind.

What?
Most scalar variables you see in Raku are objects that contain an attribute to which the value is bound when you assign to the variable. These objects are called Scalar containers (or simply containers).

In short: assignment in Raku is binding to an attribute in a Scalar object.
Understanding containers is a rite of passage for anyone who wants to become an efficient Raku developer – it was for me, too!

A (very) simplified model

# Pseudocode – not real Raku
class Scalar {
    has $!value;

    method STORE($new-value) {
        $!value := $new-value;
    }

    method FETCH() {
        $!value;
    }
}

An assignment such as

my $a;
$a = 42;

can be thought of as

my $a := Scalar.new;   # bind $a to a new container
$a.STORE(42);           # store the value

and printing the variable (say $a) is equivalent to

say $a.FETCH;

In reality things are a bit more complex because:

  • Type constraints (my Int $a) may be attached to a variable.
  • Variables can have default values (my $a is default(42)).

All that extra information lives in a container descriptor – an implementation detail that you should not rely on directly, because its interface may change.

Why the indirection?

Assignments are among the most frequently executed operations, so Raku implements them with many shortcuts for performance.
Having a mental model of containers helps you understand a surprising number of language constructs.

Returning containers (the rw trait)

Values returned from a block (or via return) are de‑containerized by default:

my $a = 42;
sub foo() { $a }   # returns the *value* 42
foo() = 666;        # ❌ cannot assign to an immutable Int

If you need to return the container itself, you have two easy options.

1. is rw trait

my $a = 42;
sub foo() is rw { $a }   # returns the container
foo() = 666;              # works
say $a;                   # 666

2. return-rw statement

my $a = 42;
sub foo() { return-rw $a }
foo() = 666;
say $a;                   # 666

These mechanisms are used, for example, when assigning to an array element.
In the core, both the postcircumfix […] operator and the underlying AT-POS method have the is rw trait for speed (the return-rw statement adds a tiny bit of overhead).

Containers in arrays and hashes

When an array is initialized with values, each element gets its own container.
You can fetch values, store new ones, or bind a container to another variable.

my @a = 1, 2, 3, 4, 5;
$_++ for @a;               # increment each element
say @a;                    # [2 3 4 5 6]
  • The topic variable $_ is bound to each element’s container during the loop, so $_++ actually calls STORE on that container.

Note: $_ is not a pointer or reference to a memory location. It is bound to the container object, which knows how to FETCH and STORE.

Slices also return containers

my @a = 1, 2, 3, 4, 5;
$_++ for @a[0, *-1];       # first and last element only
say @a;                    # [2 2 3 4 6]

The slice [0, *-1] produces two containers (first and last), and only those are mutated.

The same idea works for hashes:

my %h = a => 42, b => 666;
$_++ for %h.values;        # increment each value
say %h;                    # {a => 43, b => 667}

Direct binding in user code

You can bind one scalar to another explicitly:

my $a = 42;
my $b := $a;   # $b is bound to $a’s container
$b = 666;      # stores into the same container
say $a;        # 666

Assigning to $b actually stores into the container that lives inside $a.

Assigning to non‑existent array elements

Raku lets you assign to an array index that does not yet exist:

my @a;
@a[3] = 42;
say @a;   # [(Any) (Any) (Any) 42]

Even though there is no container at index 3 initially, the assignment creates one on the fly.

The secret again lies in the container descriptor.
Let’s refine our pseudocode to include it:

# Pseudocode – not real Raku
class Scalar {
    has $!descriptor;   # holds name, type, defaults, etc.
    has $!value;

    method STORE($value) {
        $!value := $!descriptor.process($value);
    }

    method FETCH() {
        $!value;
    }
}

Now STORE delegates to $!descriptor.process($value), which can:

  • Remember the variable’s name.
  • Enforce type constraints.
  • Supply a default value.
  • Perform any other bookkeeping required by the container.

Take‑away

  • Containers are the heart of Raku’s variable model.
  • Assignment (=) is just sugar for binding (:=).
  • The is rw trait and return‑rw let you return a container instead of its value.
  • Arrays, hashes, slices, and even loop topics work by binding to containers.
  • The container descriptor handles type checking, defaults, and other metadata.

Understanding these concepts will make many “magic‑looking” Raku features click into place, and will give you the confidence to write fast, idiomatic code. Happy container‑binding!

Containers and Proxies in Raku

There are many different types of container descriptor classes in the Rakudo implementation, all starting with the ContainerDescriptor:: name. All of them are considered implementation details.

Array and Hash Descriptors

When an AT‑POS method is called on a non‑existing element in an array, a container with a special type of descriptor is created that knows to which Array it belongs and at which index it should be stored.
The same is true for the descriptor of the container returned by AT‑KEY (as seen in part 10); it knows in which Hash it should store when assigned to, and which key should be used.

This special behaviour allows arrays and hashes to really just DWIM.
These descriptors also introduce an action‑at‑a‑distance feature that you may or may not like:

my @a;
my $b := @a[3];
say @a;   # []
$b = 42;
say @a;   # [(Any) (Any) (Any) 42]

Note: The element in the array was not initialized after the binding, but only after a value was assigned to $b. This behaviour was deliberately implemented to prevent accidental auto‑vivification.

Preventing Accidental Auto‑Vivification

my %h;
say %h:exists;  # False
say %h;         # {}
%h = 42;
say %h;         # {a => {b => 42}}

Even though %h is considered an Associative, the :exists test will not create a Hash in %h. Only after a value has been assigned do %h and its nested structures actually spring to life.

The Proxy Class

Raku supplies a fully customisable class that lets you create your own container logic: Proxy. Understanding how containers work is helpful when working with this class.

Creating a Simple Proxy

All you need to supply are:

  • a method for fetching the value (FETCH), and
  • a method for storing a value (STORE).

These are passed as named arguments to Proxy.new. A typical pattern is to wrap the creation in a subroutine for convenience:

sub answer is rw {
    my $value = 42;
    Proxy.new(
        FETCH => method () {
            $value
        },
        STORE => method ($new) {
            say "storing $new";
            $value = $new;
        }
    )
}

my $a := answer;   # bind, not assign
say $a;             # 42
$a = 666;           # storing 666
say $a;             # 666
  • The is rw trait on the subroutine is required; otherwise the Proxy would be de‑containerised on return.
  • The result of calling answer is bound (:=) rather than assigned; assignment would also cause de‑containerisation and defeat the purpose.

Because the supplied methods close over the lexical variable $value, that variable stays alive until the Proxy object is destroyed, providing an easy way to store the value for the proxy.

Flexibility Inside FETCH/STORE

Inside the supplied methods you are free to execute any code you like. For example, the Hash::MutableKeys distribution uses this capability (another case of BDD – Blog Driven Development).

If you need a descriptor for your Proxy objects, you can create one yourself; the default Proxy does not include a descriptor because it aims to give you maximum flexibility.

Episode Summary

This is the twelfth episode of “Cases of UPPER Language Elements in the Raku Programming Language,” the fifth episode discussing interface methods.

  • Containers were described, including the special descriptors for arrays and hashes.
  • The Proxy class was introduced, with its FETCH and STORE named arguments, showing how to build fully custom containers.

Stay tuned for the next episode!

0 views
Back to Blog

Related posts

Read more »