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:
| Reference | Resolves To |
|---|---|
m_count | Value before the current route fires |
^m_count | Value 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
^m_xcan only be used in a member transform for a route wherem_xalso has a transform. Ifm_xhas no transform in router, then^m_xin routeris the same asm_x.- Circular temporal dependencies (where
^m_adepends on^m_bwhich depends on^m_a) are detected and rejected by the compiler. - Temporal references are resolved statically at compile time – there is no runtime overhead.