Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Temporal References (^x)

Temporal references allow a member transform to access the post-transform value of another member within the same route execution. The ^ prefix distinguishes the “after” value from the default “before” value.

The Problem

Consider a counter and a flag that should be set when the counter exceeds a threshold:

m_count: u64 {
    in increment() => m_count + 1
}

m_over_limit: bool {
    in increment() => m_count > 100  // BUG: uses pre-transform value
}

Here m_count in the m_over_limit transform refers to the value before the increment. If m_count is 100, the increment makes it 101, but m_over_limit sees 100 and evaluates to false. The flag is always one step behind.

The Solution: ^m_name

The ^ prefix accesses the member’s value after its transform has been applied:

m_count: u64 {
    in increment() => m_count + 1
}

m_over_limit: bool {
    in increment() => ^m_count > 100  // CORRECT: uses post-transform value
}

Now ^m_count is m_count + 1 (the result of m_count’s own transform in the increment route). When m_count is 100, ^m_count is 101, and m_over_limit correctly becomes true.

Syntax

^m_member_name

The ^ prefix can be applied to any member that has a transform in the same route. It evaluates to the value that member will have after the route completes.

Common Patterns

Derived State

Compute a member value based on another member’s updated value:

m_balance: U256 {
    in deposit(amount) => m_balance + amount
}

m_is_funded: bool {
    in deposit(_) => ^m_balance > 0
}

Maintaining Invariants

Keep a count consistent with a collection:

m_items: Vec<u64> {
    in add_item(item) => m_items.push(item)
}

m_item_count: u64 {
    in add_item(_) => ^m_items.len()
}

Chained Dependencies

Temporal references can chain through multiple members:

m_price: U256 {
    in update_price(new_price) => new_price
}

m_fee: U256 {
    in update_price(_) => ^m_price * 3 / 100
}

m_net_price: U256 {
    in update_price(_) => ^m_price - ^m_fee
}

Each ^ reference resolves to the post-transform value of the referenced member. The compiler determines the correct evaluation order.

Without ^ (Pre-Transform Values)

Without the ^ prefix, member references always resolve to the pre-transform (current) value:

ReferenceResolves To
m_countValue before the current route fires
^m_countValue after m_count’s transform

This distinction is fundamental to Cambrian’s state model. It enables deterministic computation where every transform sees a consistent snapshot of the “before” state, while still allowing members to depend on each other’s updated values when needed.

Complete Example

A staking entity where rewards are proportional to the updated stake:

entity Staking {
    routes {
        stake(amount: U256)
            where msg::value >= amount : throw 100
        => []

        claim() => [
            ~> msg::sender with { value: m_pending_rewards[msg::sender] }
        ]
    }

    m_stakes: HashMap<address, U256> {
        in stake(amount) => {
            let current = m_stakes.get(msg::sender).unwrap_or(0);
            m_stakes.set(msg::sender, current + amount)
        }
    }

    m_total_staked: U256 {
        in stake(amount) => m_total_staked + amount
    }

    m_reward_rate: U256 {
        in stake(_) => if ^m_total_staked > 0 {
            m_reward_pool / ^m_total_staked
        } else {
            0
        }
    }

    m_pending_rewards: HashMap<address, U256> {
        in claim() => m_pending_rewards.set(msg::sender, 0)
    }
}

Here ^m_total_staked in the m_reward_rate transform uses the updated total (after adding the new stake), ensuring the reward rate reflects the new state.

Rules

  1. ^m_x can only be used in a member transform for a route where m_x also has a transform. If m_x has no transform in route r, then ^m_x in route r is the same as m_x.
  2. Circular temporal dependencies (where ^m_a depends on ^m_b which depends on ^m_a) are detected and rejected by the compiler.
  3. Temporal references are resolved statically at compile time – there is no runtime overhead.