A value says what it counts. A function says what it may do.
A program declares its units and the equations between them, then integer types counted in them: money in cents, sizes in bytes, rates in basis points. Arithmetic stays exact, widening is free, and narrowing is written where it happens, with the rounding you chose. Units, quantities, identities and ranges arrived in 0.23.
Ten effect classes describe what a function can reach: syscall, block, time, entropy, env, ffi, publish, spawn, recursion and alloc. A function can declare which it uses, plus a numeric budget such as zero allocations per call, and the compiler proves the declaration across everything the function can reach. A violation names the path.
unit cent;
unit USD = 100 cent;
unit pct = 1/100;
unit bp = 1/100 pct;
type Money = quantity Int in cent;
type Ledger = Money { round: half_even; }
type Rate = quantity Int in bp;
fn main() {
let fee = 1_250_000USD * 3bp; // exactly 375 USD
let odd = 1_234_567USD * 3bp; // 370.3701 USD: still exact here
let paid: Ledger = odd; // rounded where the policy is
let cents = odd.in(cent) or ceil; // or name the rounding yourself
println(fee, " ", odd, " ", paid, " ", cents);
}effect io = { syscall, block };
@effects(none: {io})
@budget(alloc_per_call = 0)
fn checksum(seed: Int, n: Int) -> Int {
let mut acc = seed;
let mut i = 0;
while i < n {
acc = acc * 31 + i;
i += 1;
}
return acc;
}