Pure Functions
Pure functions are top-level, stateless computations declared outside any entity. They can be called from any entity in the same file and are guaranteed to have no side effects – their output depends only on their inputs.
Syntax
pure fn name(param1: Type1, param2: Type2) -> ReturnType {
body_expression
}
The pure fn keywords introduce the function. The body is a single expression
(which can be a block) that produces the return value.
Basic Examples
pure fn max(a: u64, b: u64) -> u64 {
if a >= b { a } else { b }
}
pure fn min(a: u64, b: u64) -> u64 {
if a <= b { a } else { b }
}
pure fn clamp(val: u64, lo: u64, hi: u64) -> u64 {
if val < lo { lo }
else if val > hi { hi }
else { val }
}
Restrictions
Pure functions have strict constraints:
| Allowed | Forbidden |
|---|---|
| Parameters | Read member values |
| Arithmetic, logic | Write member values |
if/else, match | Send messages |
let bindings | Access msg:: or sys:: |
| Call other pure functions | Side effects (~>, deploy, emit, evm::) |
| Type casts | Call macros |
These constraints ensure that pure functions are deterministic, testable, and free of dependencies on entity state.
Block Bodies
Complex pure functions use block expressions with let bindings:
pure fn compute_fee(amount: U256, rate_bps: u64) -> U256 {
let rate = rate_bps as U256;
let fee = amount * rate / 10000;
fee
}
pure fn weighted_average(a: u64, b: u64, weight_a: u64, weight_b: u64) -> u64 {
let total_weight = weight_a + weight_b;
let weighted_sum = a * weight_a + b * weight_b;
weighted_sum / total_weight
}
Calling Pure Functions
Pure functions are called by name from any expression context:
In Route Actions
routes {
swap(amount_in: U256) => [
let fee = compute_fee(amount_in, 30);
let net = amount_in - fee;
Transfer(net) ~> m_recipient
]
}
In Member Transforms
m_fee_collected: U256 {
in trade(amount) => m_fee_collected + compute_fee(amount, 30)
}
In Where Clauses
routes {
trade(amount: U256)
where amount >= min_trade_amount(m_tier) : throw 100
=> []
}
In Other Pure Functions
pure fn abs_diff(a: u64, b: u64) -> u64 {
if a >= b { a - b } else { b - a }
}
pure fn is_close(a: u64, b: u64, tolerance: u64) -> bool {
abs_diff(a, b) <= tolerance
}
Pure Functions vs. Macros
| Feature | Pure Function | Macro |
|---|---|---|
| Declaration | pure fn name(...) -> T { ... } | macro name(...) -> T = { ... } |
| Location | Top-level (outside entities) | Inside entity |
| State access | None | Can read members |
| Scope | All entities in the file | Enclosing entity only |
| Call syntax | name(args) | @name(args) |
Use pure functions for reusable logic that does not depend on state. Use macros when you need to combine state access with helper logic. See Macros.
Pure Functions vs. Pure Routes
| Feature | Pure Function | Pure Route |
|---|---|---|
| Declaration | pure fn name(...) -> T { ... } | pure name(...) -> T => [return(...)] |
| Callable externally | No | Yes (via message) |
| Internal calls | Yes, from any expression | As a route |
| Use case | Internal computation | On-chain utility API |
Complete Example
pure fn compute_output(
amount_in: U256,
reserve_in: U256,
reserve_out: U256,
fee_bps: u64
) -> U256 {
let fee = compute_fee(amount_in, fee_bps);
let effective_in = amount_in - fee;
effective_in * reserve_out / (reserve_in + effective_in)
}
pure fn compute_fee(amount: U256, bps: u64) -> U256 {
amount * (bps as U256) / 10000
}
entity DEX {
routes {
swap(amount_in: U256, min_out: U256)
where amount_in > 0 : throw 100
=> [
let out = compute_output(
amount_in, m_reserve_a, m_reserve_b, 30
);
if out < min_out => [
// slippage protection
] else [
Token::transfer(msg::sender, out) ~> m_token_b
]
]
}
}