ddonche/goblin-lang
0.46.24
1
0
docs overview
[[memory-model]]

Goblin Memory Model


Goblin doesn't hide how computers work. This page explains what actually happens when your code runs.


Stashes and Addresses

When you write:

x | 5
x | 5

Goblin allocates memory — a stash — somewhere in your system. The number 5 goes inside that stash. The name x becomes a tether: a rope tied to the address of that stash.

Memory:
[0x55555555f2c0]: 5

Tethers:
x -----> 0x55555555f2c0

You can see this yourself:

x | 5
:memaddr(x)    /// -> "0x55555555f2c0"
x | 5
:memaddr(x)    /// -> "0x55555555f2c0"

The address will be different every time you run your program — the operating system randomizes it for security. But the relationship is always the same: the name is a tether, the tether points to an address, the address is where the value lives.

The name x does not contain 5. It points to a stash that contains 5.


Most Stashes Are Sealed

When you retether a name:

x | 5
x |= 6
x | 5
x |= 6

The stash containing 5 is not modified. Goblin creates a new stash for 6 at a different address and moves the tether.

After x |= 6:
[0x55555555f2c0]: 5  (sealed, abandoned)
[0x55555555f2e0]: 6

x -----> 0x55555555f2e0

Prove it yourself:

x | 5
:memaddr(x)    /// -> "0x55555555f2c0"

x |= 6
:memaddr(x)    /// -> "0x55555555f2e0"  (different address)
x | 5
:memaddr(x)    /// -> "0x55555555f2c0"

x |= 6
:memaddr(x)    /// -> "0x55555555f2e0"  (different address)

The old stash still exists — sealed, unchanged, abandoned. The garbage collector will reclaim it when nothing is tethered to it anymore.

When you "change" a value in Goblin, you are creating a new stash and moving the tether.


Tethers Point to Addresses, Not to Each Other

x | 5
y | x
x |= 6
y        /// -> 5
x | 5
y | x
x |= 6
y        /// -> 5

When you wrote y | x, you tethered y to the address x was pointing to at that moment. When x moved to a new stash, y stayed where it was.

After y | x:
[0x55555555f2c0]: 5

x -----> 0x55555555f2c0
y -----> 0x55555555f2c0

After x |= 6:
[0x55555555f2c0]: 5
[0x55555555f2e0]: 6

x -----> 0x55555555f2e0  (moved)
y -----> 0x55555555f2c0  (unchanged)

Tethers point to addresses, not to other tethers. y | x means "point to wherever x is pointing right now" — not "follow x forever."


Sharing a Stash

When two names point to the same address, they share the same stash:

items  | [:a :b :c]
backup | items

:memaddr(items)   /// -> "0x5555555a1040"
:memaddr(backup)  /// -> "0x5555555a1040"  (same)
items  | [:a :b :c]
backup | items

:memaddr(items)   /// -> "0x5555555a1040"
:memaddr(backup)  /// -> "0x5555555a1040"  (same)

This is safe in Goblin because stashes are sealed. Neither items nor backup can change what's inside that stash — they can only retether to a different one.


Garbage Collection

When a stash has no tethers left, it becomes abandoned. The garbage collector reclaims it.

Think of it like a shared fridge. If your name isn't on the food, you can't be upset when it disappears.

No tether, no claim. Finders keepers.

x | "sandwich"
x |= "pizza"
/// The "sandwich" stash is now abandoned.
/// GC will reclaim it.
x | "sandwich"
x |= "pizza"
/// The "sandwich" stash is now abandoned.
/// GC will reclaim it.


Why This Matters

This model eliminates an entire class of bugs common in other languages:

original | [:a :b :c]
modified | original

modified[0] |! :z

original    /// -> [:a :b :c]  (unchanged)
modified    /// -> [:z :b :c]
original | [:a :b :c]
modified | original

modified[0] |! :z

original    /// -> [:a :b :c]  (unchanged)
modified    /// -> [:z :b :c]

Because |! creates a new stash and retethers modified, original is never touched. In Python, this same code would silently corrupt original because Python lists are mutable containers.

Sealed stashes also make concurrency safe by default — multiple threads can read the same stash with no locks because the contents can never change.


The Tether Operators

All four tether operators create stashes and tether to them. They differ in when you use them and what they do:

Operator What it does
\| Tether a new name for the first time
\|= Retether an existing name to a new value
[= Shadow tether — temporary override in a nested scope
\|! Update elements inside a collection and retether

See Tether, Retether, Shadow, and Update for the full treatment of each.


Introspection

Goblin lets you observe the memory model directly:

x | [:a :b :c]
:memaddr(x)    /// -> memory address of the stash
x | [:a :b :c]
:memaddr(x)    /// -> memory address of the stash

More introspection tools are Memory Introspection.