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

Member semantics: two planes

Members split by how they are consumed; both planes elaborate by monomorphization — each impl member lowers to an ordinary rule (Self ↦ target type) flowing through the standard pipeline (classifier, stratifier, evaluability gate, reasoner) with no new evaluation machinery.

Rule plane (derive, check, query) — clause union

The trait declares the predicate; each impl contributes a type-guarded clause:

pub trait Adulthood {
    derive Adult(Self)
}
impl Adulthood for USPerson     { derive Adult(p: Self) :- p.age >= 18 }
impl Adulthood for GermanPerson { derive Adult(p: Self) :- p.age >= 18, p.has_residence }

elaborates to two ordinary rules with one head, Adulthood::Adult — Datalog unions same-head rules natively, and the head-parameter guards close over <: (World assumptions (CWA / OWA)). Dispatch is derivation: an atom Adult(p) in

pub derive CanVote(p: Person) :- Adult(p), p.registered

holds per-individual under whichever impl covers the individual’s actual type — the relational transposition of dyn dispatch, with the no-overlap rule (Conformance) keeping the selection single-valued per declared type (per-individual multiple classification is defined in Conformance). A query member carries the full query signature — Self in parameter positions, a Self-free return type (return-position Self is OE0675: it would need union types or bounded generics) — and each impl provides a full select … from … body; the trait signature fixes the projection shape. Query members are declared, conformance-checked, and lowered per impl, and they surface through the same per-impl query dispatch top-level queries use — each impl’s clause is queryable under its per-impl identity Trait::member@Target. They are not rule-body atoms: a query-member atom in a derive/check body is refused loudly at ox check, the same envelope discipline fn members get (Resolution) — top-level queries are not rule-body predicates either, and the member form inherits exactly that. A single dispatch endpoint returning the union of per-impl results is reserved. A check member monomorphizes to ordinary RuleMode::Check rules; discharge, payload, and delivery follow RFD 0025 unchanged. A check member’s signature may pin its severity with the payload-arrow suffix (check OverLimit(Self) => Severity::Error;RFD 0026 amendment 2026-06-11, issue #230): the pin makes blocking behavior part of the trait contract. Under a pin, an impl’s Diagnostic { … } payload may omit severity: (it inherits the pinned one), may restate the same severity (harmless), and is refused with OE0676 when it states a different one — a contract whose blocking behavior varies by implementor is a weak contract. Only the severity is pinnable: code: and message: stay per-impl, and a full Diagnostic { … } payload on the trait-side signature is refused (OE1323). An unpinned member keeps per-impl severity freedom. #[observe] on an impl member whose pinned severity is Error remains legal (a discharge-mode opt-out, not a severity change).

Invocation plane (fn, mutate) — receiver resolution

A body is a single value or a single transaction; resolution is to exactly one impl. An invocation-plane member leads with the self receiver — its first parameter must be self (OE0675 otherwise; further Self-typed parameters are admitted after it) — because the receiver is what dispatch selects on. A mutate member —

pub trait Closable {
    mutate close(self, reason: String);
}
impl Closable for SavingsAccount {
    mutate close(self, reason: String) {
        update self: SavingsAccount set { status = "closed" }
    }
}

— monomorphizes per impl (full RFD 0015 imperative bodies; self is the receiver variable inside the body, Self splices to the target type) into the mutation catalog under the per-impl identity Closable::close@SavingsAccount, callable as Closable::close: invocation dispatches on the receiver individual’s actual committed type to the unique covering impl (<:-aware — an impl for Account covers a SavingsAccount receiver). An ambiguous receiver — classified under two <:-incomparable covered targets — and a receiver with no covering impl are both loud runtime refusals naming the impls / the receiver’s types (Conformance), never an arbitrary pick. Invocation flows through the standard mutation-dispatch surface (CLI harness / serve / SDK); the receiver is passed as the self argument (an individual reference), exactly like any RFD 0019 entity-ref parameter. fn members elaborate and dispatch the same way through the compute surface (Resolution has the precise execution envelope). Mutations do not call mutations; member mutates do not change that.