REENTRANCY ATE MY BABY

ยท updated 2026-05-17

WITH blocks are non-reentrant

In CLEAR, when you open a WITH block on an object to acquire a capability (like mutation or sharing), no other WITH block for that same object can start until the first one finishes.

The Mental Model: Capabilities are Edges

In CLEAR, we separate Types from Capabilities:

The Rule: Functions take the Type, not the Capability. You must unwrap the capability using WITH before calling the function.

-- CLEAR's Solution: Unwrapping at the call site
WITH sharedPlayer AS player {
  -- 'player' is now the raw Type (User), unwrapped from its 'shared' capability
  damage!(player);               -- OKAY
  heal!(player);                 -- OKAY
  
  -- heal!(sharedPlayer);       -- COMPILER ERROR! 
  -- Functions take User, not shared:locked User.
}

This prevents the "Infinite Mirror" pattern where a function modifying an object accidentally tries to re-acquire a lock it already holds.

Why this matters

Re-entrancy bugs (where state changes under your feet while you're in the middle of an operation) are the root of most concurrent complexity.

The Problem (Sheer Chaos):

In most languages (Java, C, Go, Swift), you cannot trust from one line of code to the next. While you are reading a list, another thread can pop() from it, causing your next line to crash with an IndexOutOfBounds error.

The CLEAR Solution: Scoped Sovereignty

By banning re-entrancy via WITH blocks, CLEAR guarantees that while you are inside that scope:

  1. You are the sole owner of that object's state.
  2. No one else (not other threads, and not your own callbacks) can touch it.

This turns a global problem (unpredictable concurrency bugs) into a local problem (simple logic).

Comparison with Other Models

Simplified Refactoring

In Rust, if you change an object from Rc (single-threaded) to Arc (multi-threaded), you must update every function signature that takes that object.

In CLEAR, you only change the capability at the definition site. Because your functions only take the raw Type (e.g., User), the rest of your codebase remains untouched. The "Refactoring Blast Radius" is zero.


A final note on local reasoning

CLEAR optimizes for the ROI on brain power.

By enforcing explicit scopes for capabilities, CLEAR ensures that "poisoning" (restricting access to data) is always visible and local. You don't need to be a systems programming expert to write blazing-fast, thread-safe code. You just need to follow the WITH block.

Source: docs/manifesto/REENTRANCY.md