Records
Records are named product types (structs) that group related fields together. They are declared inside an entity or at program scope (shared by all entities) and can be used as member types, route parameters, and intermediate values.
Declaration
A record is declared with the record keyword followed by named, typed fields:
entity Marketplace {
record Listing {
seller: address,
price: U256,
active: bool
}
record Bid {
bidder: address,
amount: U256,
timestamp: u64
}
}
Each field has a name and a type, separated by a colon. Fields are separated by commas.
Construction
Records are constructed by providing values for all fields:
m_listing: Listing {
in list(price) => {
let listing = Listing {
seller: msg::sender,
price: price,
active: true
};
listing
}
}
Every field must be specified – there is no default value mechanism. The order of fields in the constructor does not need to match the declaration order. A literal that omits a field, names an unknown field, or sets a field twice is rejected.
A record literal may appear in block contexts: a pure fn body, a { ... }
block (including a let inside it), or a member-transform body wrapped in
braces. It cannot be the right-hand side of a route-action let or a direct
call argument — bind it in a block let first.
Field Access
Fields are accessed with dot notation:
let price = listing.price;
let who = listing.seller;
let open_price = if listing.active { listing.price } else { 0 };
Functional Update
Cambrian supports a functional update syntax that creates a new record value with some fields changed while keeping the rest:
{
let updated = listing { active: false };
updated.price
}
This produces a new Listing with active set to false and seller and
price carried over from the original. The original listing is not mutated.
Functional update is especially useful in member transforms, where you need to produce a new state value based on the current one. As with any record literal, wrap the transform body in braces:
m_listing: Listing {
in create(seller, price) => {
Listing {
seller: seller,
price: price,
active: true
}
}
in deactivate() => { m_listing { active: false } }
in update_price(new_price) => { m_listing { price: new_price } }
}
You can update multiple fields at once:
m_listing: Listing {
in relist(new_price) => {
m_listing {
price: new_price,
active: true
}
}
}
Records as Member Types
Records are commonly used as the types of members to group related state:
entity Escrow {
record Deal {
buyer: address,
seller: address,
amount: U256,
released: bool
}
routes {
#[factory_only]
init create(buyer: address, seller: address, amount: U256) => []
release()
where msg::sender == m_deal.buyer : throw 100
&& m_deal.released == false : throw 101
=> []
}
m_deal: Deal {
in create(buyer, seller, amount) => {
Deal {
buyer: buyer,
seller: seller,
amount: amount,
released: false
}
}
in release() => { m_deal { released: true } }
}
}
Records in Collections
Records can be stored in generic collections:
m_bids: Vec<Bid> {
in place_bid(amount) => {
let bid = Bid {
bidder: msg::sender,
amount: amount,
timestamp: sys::now
};
m_bids.push(bid)
}
}
Nesting
Records can contain other records as field types:
entity Vault {
record Config {
fee_rate: u64,
limits: Limits
}
record Limits {
max_deposit: U256,
min_deposit: U256
}
}
Nesting works the same for program-scope records. A record literal cannot
be written inline as a field value; bind the inner record with a block let
first:
m_config: Config {
in configure(max) => {
let limits = Limits { max_deposit: max, min_deposit: 1 };
Config { fee_rate: 3, limits: limits }
}
}
Access nested fields with chained dot notation:
let max = m_config.limits.max_deposit;