Members
Members are persistent state variables owned by an entity. Unlike traditional programming where state is mutated imperatively, Cambrian members declare how they change in response to routes. Each member defines its own transformation logic – this is the member-centric state model.
Declaring Members
Members are declared directly in the entity body, at the same level as
routes { } — there is no separate members { } wrapper in the grammar:
m_owner: address {
in setup(owner) => owner
}
m_balance: U256 {
in deposit(amount) => m_balance + amount
in withdraw(amount) => m_balance - amount
}
m_count: u64 {
in increment() => m_count + 1
in reset() => 0
}
Member Declaration
Each member has a name, a type, and one or more transforms:
m_name: Type {
in route_name(params) => new_value_expression
in another_route(params) => another_expression
}
| Part | Description |
|---|---|
| Name | m_ prefix followed by snake_case |
| Type | Any Cambrian type |
| Transforms | One in clause per route that modifies this member |
The m_ Convention
Member names use the m_ prefix by convention. This distinguishes state
variables from local bindings, parameters, and other names. While the compiler
does not enforce the prefix, it is universally used in Cambrian code.
Reading Members
Member values are accessible by name in:
- Where clauses:
where m_balance >= amount : throw 100 - Route actions:
~> m_owner with { value: m_balance } - Macros:
macro is_owner() -> bool = { msg::sender == m_owner } - Other member transforms:
in transfer(to, amount) => m_balance - amount
When read in a route’s action list or where clause, the value is the pre-transform value (the value before the current route’s transforms are applied).
Transform Basics
Transforms define the new value of a member after a route executes. The transform body is an expression:
m_count: u64 {
in increment() => m_count + 1
}
This says: “When the increment route fires, the new value of m_count is
the current value plus one.”
See State Transforms for detailed coverage.
Members Without Transforms
If a member does not list a transform for a route, it remains unchanged when
that route fires. Only members that explicitly declare in route_name(...) are
affected by that route.
Temporal References
Within a single route execution, you may need to reference the post-transform
value of a member. The ^ prefix provides this:
m_count: u64 {
in increment() => m_count + 1
}
m_count_doubled: u64 {
in increment() => ^m_count * 2
}
Here ^m_count refers to the value of m_count after its transform in the
increment route (i.e., m_count + 1).
See Temporal References for details.
Member Types
Members can be of any type:
| Type | Example Use |
|---|---|
u64, U256 | Counters, balances, amounts |
bool | Flags, status indicators |
address | Owner, treasury, linked entities |
String | Names, labels |
HashMap<K, V> | Mappings (balances, approvals) |
Vec<T> | Lists (participants, history) |
Option<T> | Optional configuration |
| Records | Grouped state (deal details, config) |
| Enums | State machines (status, phase) |
Complete Example
entity Token {
routes {
init deploy(name: String, symbol: String, owner: address) => []
mint(to: address, amount: U256)
where msg::sender == m_owner : throw 100
=> []
transfer(to: address, amount: U256)
where m_balances[msg::sender] >= amount : throw 101
=> []
}
m_name: String {
in deploy(name, _, _) => name
}
m_symbol: String {
in deploy(_, symbol, _) => symbol
}
m_owner: address {
in deploy(_, _, owner) => owner
}
m_total_supply: U256 {
in deploy(_, _, _) => 0
in mint(_, amount) => m_total_supply + amount
}
m_balances: HashMap<address, U256> {
in mint(to, amount) => {
let current = m_balances.get(to).unwrap_or(0);
m_balances.set(to, current + amount)
}
in transfer(to, amount) => {
let from = msg::sender;
let from_bal = m_balances[from];
let to_bal = m_balances.get(to).unwrap_or(0);
m_balances
.set(from, from_bal - amount)
.set(to, to_bal + amount)
}
}
}