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

Expression grammar

expr            ::= primary-postfix
                      ((arith-op | comp-op) primary-postfix)*
                      (('not')? 'in' primary-postfix)?
                      ('&&' expr | '||' expr)*

primary-postfix ::= unary-op* primary postfix*
unary-op        ::= '!' | '-'
postfix         ::= '.' Ident '(' expr-list ')'                  // n-arg method call
                  | '.' Ident                                     // field access OR zero-arg method call
                  | '[' expr ']'                                  // index
                  | '[' expr? '..' expr? ']'                      // slice
                  | '?'                                            // try (Result propagation)

primary         ::= literal
                  | '_'                                            // wildcard / anonymous binding (rule-atom arg positions only)
                  | '(' expr ')'
                  | list-or-comprehension
                  | if-expr
                  | match-expr
                  | block-expr
                  | meta-call
                  | aggregate
                  | subquery                                       // `query` (count/set/one/collect/exists braces)
                  | call-expr
                  | path
                  | struct-literal
                  | closure

literal         ::= INT | REAL | STRING | DATE | DATETIME | BOOL

list-or-comprehension
                ::= '[' ']'
                  | '[' expr (',' expr)* ','? ']'
                  | '[' expr 'for' Ident 'in' expr ('where' expr)? ']'

if-expr         ::= 'if' expr block ('else' (block | if-expr))?
match-expr      ::= 'match' expr '{' arm (',' arm)* ','? '}'
block-expr      ::= '{' stmt* expr? '}'
block           ::= '{' stmt* expr? '}'

call-expr       ::= path '(' expr-list ')'
meta-call       ::= 'meta' '(' expr ')'

aggregate       ::= ('sum'|'count'|'min'|'max'|'avg')
                    '(' expr ('for' Ident 'in' expr
                              ('where' expr)? (',' rule-atom)*)? ')'   // RFD 0029: trailing atoms refine the fold

struct-literal  ::= path '{' (struct-init-list)? '}'
struct-init-list ::= field-init (',' field-init)* ','?
                   | '..' expr (',' field-init)* ','?            // struct-update spread
field-init      ::= Ident (':' expr)?                            // `name` shorthand or `name: expr`

closure         ::= '|' closure-params? '|' ('->' TypeExpr)? closure-body
closure-params  ::= closure-param (',' closure-param)* ','?
closure-param   ::= Ident (':' TypeExpr)?
closure-body    ::= expr | block

path            ::= Ident ('::' Ident)*
expr-list       ::= (expr (',' expr)*)?

arith-op        ::= '+' | '-' | '*' | '/' | '%'
comp-op         ::= '==' | '!=' | '<' | '<=' | '>' | '>='        // non-chainable

Precedence (tightest to loosest):

  1. Postfix (.f(), [i], [a..b], .field, ?)
  2. Unary prefix (!, -)
  3. Multiplicative (*, /, %) — left-associative
  4. Additive (+, -) — left-associative
  5. Comparison (==, !=, <, <=, >, >=) — non-associative (a < b < c is rejected; use a < b && b < c)
  6. Membership (in, not in) — non-associative
  7. Logical AND (&&) — left-associative, short-circuit
  8. Logical OR (||) — left-associative, short-circuit

Turbofish disambiguates generic call sites at expression position: sort::<Money>(prices).

Rounding builtins (RFD 0029). A small family of rounding functions is available in expression position — both in rule-body bindings and in the fn/compute plane — operating exactly over the numeric tower (Decimal stays Decimal, never via f64):

BuiltinMeaning
round(x)nearest integer, ties away from zero
round(x, n)nearest multiple of 10⁻ⁿ, ties away from zero
round_half_even(x, n)nearest multiple of 10⁻ⁿ, ties to even (banker’s rounding)
trunc(x, n)toward zero at 10⁻ⁿ

round_half_even is the money default: financial rounding rounds half to even to avoid the systematic upward bias of half-away-from-zero. All four return an exact value (integral results collapse to Int). A wrong argument count is refused at compile with OE1333 RoundingBuiltinArity.

Closures capture environment by value (Argon is value-semantic; no references). A closure’s type is fn(T1, T2) -> U — interchangeable with declared fn references at any function-typed argument or binding.

Property-style accessors. The paren-less postfix .Ident resolves to a stored field if the receiver type declares one with that name; otherwise to a call of a zero-argument method with that name. This unifies the .field and .zero_arg_method() forms — both read obj.name. The 30.days, 5.minutes, 60.seconds shorthand in Numeric tower and coercion is exactly this affordance over zero-arg methods on numeric types. Methods with one or more arguments always require explicit parens.

Struct-literal evaluation. A struct-literal T { … } evaluates each field initializer and assembles a struct value of type T (the data semantics — structural equality, immutability, no identity — are stated in struct and enum — language built-ins (data declarations)). The literal must state every field: a missing field is OE0249, an undeclared one is OE0250, and a field given the wrong type is OE0251. The field shorthand in field-init (a bare Ident with no : expr) takes the field’s value from a same-named binding in scope.

Functional update — the ..base spread. The ..expr form in a struct-init-list constructs a new struct value from an existing one: it evaluates base, copies every field, then overrides the fields the literal restates. Row { ..base, a: 9 } is base with a replaced by 9 and every other field carried over. The result is a fresh value; base is never modified (struct values are immutable, struct and enum — language built-ins (data declarations)). The spread supplies the fields the literal omits, so the every-field rule is satisfied by the base — a spread literal need only name the fields it changes.

pub struct Row { a: Int, b: Int }

test "spread overrides one field and carries the rest" {
    let base = Row { a: 1, b: 2 };
    assert Row { ..base, a: 9 } == Row { a: 9, b: 2 };
    assert base == Row { a: 1, b: 2 };          // base unchanged
}

Field projection. The postfix .f reads field f off a struct value, by structure. Projection nests — outer.inner.v reads v off the inner struct off outer. A field the struct’s type does not declare is OE0254 StructFieldAccessUnknown.

pub struct Inner { v: Int }
pub struct Outer { inner: Inner, tag: String }

test "projection reads through nested struct values" {
    let o = Outer { inner: Inner { v: 7 }, tag: "x" };
    assert o.inner.v == 7;
    assert o.inner == Inner { v: 7 };
}

Enum payload binding in match. The constant-pattern subset of match — payloadless enum constants, literals, or-patterns of those, and _ — executes (Pattern matching). A payload-binding arm (match opt { Some(x) => …x… }) destructures one payload value into one fresh variable, bound over the arm body. In value position — a let right-hand side, a comparison operand, a fn body, a return/require value — this binds and evaluates: let r = match opt { Some(x) => x, None => 0 }; is the idiomatic way to read an enum payload. The arm binds exactly one variable positionally; a zero-, multi-, or record-binder arm is OE0257 EnumPayloadPattern. A payload-binding arm in statement position (an effectful arm inside a mutate statement-match) refuses with OE1319 rather than mis-evaluating; bind the payload in value position first. The rule-body membership test path is Some(binder) / path is None is the other supported reader (struct and enum — language built-ins (data declarations), RFD 0007 Rule-atom grammar).