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

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() => m_balance + msg::value
    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, an optional initial value, and a block of transforms (the block is required, but may be empty):

m_name: Type {
    in route_name(params) => new_value_expression
    in another_route(params) => another_expression
}

m_name: Type = initial_value { ... }

Without = initial_value, a member starts at its type’s default (zero, false, empty) unless the init route’s transform sets it:

m_limit: U256 = 1_000 {}

m_open: bool = true {
    in close() => false
}
PartDescription
Namem_ prefix followed by snake_case
TypeAny Cambrian type
TransformsOne 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

Which value you see depends on where you read it:

  • from / where clauses and transform bodies see the value from before the route (use ^m_x inside a transform to read another member’s new value).
  • In an unphased route, the action list runs after all transforms are stored. Arguments of sends, call, and deploy still read the value from before the route; let, emit, if conditions, and return read the new value (see Actions and State).

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:

TypeExample Use
u64, U256Counters, balances, amounts
boolFlags, status indicators
addressOwner, treasury, linked entities
StringNames, labels
HashMap<K, V>Mappings (balances, approvals)
Vec<T>Lists (participants, history)
Option<T>Optional configuration
RecordsGrouped state (deal details, config)
EnumsState machines (status, phase)

Complete Example

entity Token {
    routes {
        #[factory_only]
        init setup(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
            && to != msg::sender : throw 102
        => []
    }

    m_name: String {
        in setup(name, _, _) => name
    }

    m_symbol: String {
        in setup(_, symbol, _) => symbol
    }

    m_owner: address {
        in setup(_, _, owner) => owner
    }

    m_total_supply: U256 {
        in mint(_, amount) => m_total_supply + amount
    }

    m_balances: HashMap<address, U256> {
        in mint(to, amount) => m_balances.insert(to, m_balances[to] + amount)
        in transfer(to, amount) => {
            let sender = msg::sender;
            m_balances
                .insert(sender, m_balances[sender] - amount)
                .insert(to, m_balances[to] + amount)
        }
    }
}