#Grammar
Status: language specification draft.
This chapter collects the hd-lang grammar in EBNF.
- grammar.scope.syntaxThe grammar specifies syntactic form, not name resolution, typing, exhaustiveness, or runtime behavior.
- grammar.scope.tokensThe grammar consumes the token stream produced by Lexical Structure.
- grammar.scope.layout-tokens
NEWLINE,INDENT,DEDENT,SUITE_END, andEOFare abstract layout tokens. - grammar.scope.commentsComments do not appear in this grammar.
- grammar.scope.consolidatedThe feature chapters refine the semantic constraints on these productions, but do not maintain separate extension grammars.
fn demo() -> i32:
x := +1
x + 1#Source Files And Suites
A source file is a sequence of top-level items:
source_file = { NEWLINE | top_level_item }, EOF ;
top_level_item = use_decl
| tests_block
| decorated_decl
| declaration
| top_level_statement
;
top_level_statement = suite_statement
| simple_statement, NEWLINE
;
- grammar.suite.same-lineA same-line
suite_bodyends atSUITE_END. - grammar.suite.indentedAn indented
suite_bodybegins on the following logical line. - grammar.suite.nonemptyThe production requires at least one statement in an indented body; use
passwhen an explicit no-op body is required. - grammar.suite.local-declarationsNamed declarations and implementations may also occur in executable block suites.
- grammar.suite.use-top-levelUse declarations remain top-level items.
- grammar.suite.methodsMethods occur inside trait and implementation declarations through their dedicated grammar productions.
- grammar.limit.nestingAn implementation may limit how deeply brackets, suites, closures, and string interpolations nest within one another. The limit is implementation-defined.
- grammar.limit.nesting.errorSource that nests deeper than that limit is an error, reported on the first construct past it. Error:
nesting-too-deep.
#Error Recovery
Error recovery limits cascades from rejected syntax:
fn broken(a: i32, b: i32, c: i32) -> i32:
value := a and b < c < a # error: syntax-error
+1
fn unfinished() -> i32:
values := [ # error: unclosed-delimiter
+1
+2- grammar.recovery.statementAfter the first parsing error in a statement, an implementation must suppress further parsing errors rooted in that statement.
- grammar.recovery.unclosedAfter an
unclosed-delimitererror, an implementation must suppress parsing errors on later lines within that delimiter.
Why. One diagnostic identifies the root cause without presenting its syntactic fallout as independent mistakes.
#Test Blocks
A file may hold one tests: block, whose items are compiled only for tests:
tests_block = "tests", ":", NEWLINE, INDENT, tests_item, { tests_item },
DEDENT ;
tests_item = use_decl
| decorated_decl
| declaration
| top_level_statement
;
fn late_fee(days: i32) -> i32:
if days > 30: 5 else: 0
tests:
use std.testing.assert_equal
fn overdue() -> i32: 31
it("charges a fee after 30 days"):
assert_equal(late_fee(overdue()), 5, reason="one day late")- grammar.tests.blockA
tests:block is a top-level item that holds its module's test-only items. - grammar.tests.item-formsIts items take the forms of top-level items: use declarations, decorated declarations, declarations, and statements.
- grammar.tests.top-levelA
tests:block may appear only at module top level. Atests:block inside a suite or inside anothertests:block is an error. Error:syntax-error. - grammar.tests.onceA file may have at most one
tests:block. A second block is an error. Error:duplicate-tests-block. - grammar.tests.registrationEach statement of the block must be a test registration call, as
module.testing.position-statementsspecifies. - grammar.tests.keyword
testsis a reserved word, so it never names a declaration or binding.
fn helper() -> void:
tests: # error: syntax-error
pass
tests:
it("first block"):
pass
tests: # error: duplicate-tests-block
it("second block"):
passWhy. Like Rust's
#[cfg(test)] mod tests, one block groups a file's test-only code, and the normal build never sees it.
See also: Tests Blocks, Standard Testing.
#Statements
Statements, including local declarations, follow this grammar:
statement = suite_statement
| function_decl
| data_decl
| enum_decl
| trait_decl
| type_decl
| impl_decl
| simple_statement, NEWLINE
;
suite_statement = defer_statement
| suite_expression
| trailing_block_call
| "_", ":=", suite_right_side
| identifier, ":=", { identifier, ":=" },
suite_right_side
| "let", pattern, [ ":", type ], "=",
suite_right_side
| "using", identifier, [ ":", type ], "=",
suite_right_side
| let_else_statement
| postfix_expression, ( "=" | compound_assign_op ),
suite_right_side
| "return", suite_right_side
| "break", suite_right_side
;
suite_right_side = suite_expression | trailing_block_call ;
defer_statement = "defer", ":", suite_body ;
simple_statement = let_statement
| using_statement
| discard_statement
| assignment_statement
| return_statement
| break_statement
| continue_statement
| expression_statement
;
let_statement = "let", pattern, [ ":", type ], "=",
closed_expression ;
let_else_statement = "let", pattern, [ ":", type ], "=",
closed_expression, "else", ":", suite_body ;
using_statement = "using", identifier, [ ":", type ], "=",
closed_expression ;
discard_statement = "_", ":=", closed_expression ;
assignment_statement = postfix_expression, ( "=" | compound_assign_op ),
closed_expression ;
compound_assign_op = "+=" | "-=" | "*=" | "/=" | "%="
| "&=" | "|=" | "^=" | "<<=" | ">>="
;
return_statement = "return", [ closed_expression ] ;
break_statement = "break", [ closed_expression ] ;
continue_statement = "continue" ;
expression_statement = closed_expression ;
inline_statement = "let", pattern, [ ":", type ], "=", inline_expression
| "using", identifier, [ ":", type ], "=", inline_expression
| "_", ":=", inline_expression
| postfix_expression, ( "=" | compound_assign_op ),
inline_expression
| "return", [ inline_expression ]
| "break", [ inline_expression ]
| continue_statement
| inline_expression
;
#Let Statements
A let statement binds the names of a pattern to the parts of its
initializer:
struct Point:
x: i32
y: i32
fn total(point: Point, pair: (i32, i32)) -> i32:
let Point { x, y: py } = point
let (low, _) = pair
let sum = x + py
sum = sum + low
sum- grammar.stmt.let-patternA
lettakes anymatchpattern: a name,_, a literal, or a tuple, data, or variant pattern, nested to any depth. - grammar.stmt.let-pattern.tupleA multi-name
letis a tuple pattern, so its names stand in parentheses, as inlet (a, b) = .... - grammar.stmt.let-list.bareA multi-name
letwithout parentheses, as inlet a, b = ...orlet mut log, db = ..., is an error whose fix-it adds the parentheses. Error:syntax-error. - grammar.stmt.let-pattern.parenthesizedParentheses around one pattern with no comma form no pattern, so
let (a) = ...is an error. Error:syntax-error. - grammar.stmt.let-pattern.mutIn a
letpattern,mutmay precede each name the pattern binds, as inlet mut user = ...,let (mut log, db) = ..., andlet Point { mut tags, name } = .... - grammar.stmt.let-mut.per-nameA
mutbelongs to the one name it precedes. What it makes writable is a semantic rule of Writable Places. - grammar.stmt.let-mut.only-letOnly
letaccepts it:mut user := ...andfor mut item in items:are errors. Error:syntax-error. - grammar.stmt.let-pattern.semanticWhether a pattern may fail, and so needs an
else, is a semantic rule of Let Patterns.
fn pair() -> (List[i32], List[i32]): ([1], [2])
fn invalid() -> void:
let (first, second) = pair() # valid: no mut needed
mut total := 0 # error: syntax-error
let mut log, db = pair() # error: syntax-error
let a, b = pair() # error: syntax-error
let (only) = pair() # error: syntax-errorWhy. In
let mut log, db, a reader may takemutas covering both names. The parentheses show that it belongs tologalone. A multi-nameletuses them even withoutmut, since one shape reads better than two spellings.
#Let-Else Statements
A let-else statement adds else: and a block, which runs when the
pattern does not match:
fn find(id: i32) -> i32?:
if id == 0: .Some(7) else: .None
fn sum(id: i32) -> i32?:
let .Some(first) = find(id) else: return .None
let .Some(second) = find(id + 1) else:
return .None
.Some(first + second)- grammar.stmt.let-elseA
letstatement may end inelse,:, and a suite body, as thelet_else_statementproduction states. - grammar.stmt.let-else.same-lineThe else body may be a same-line suite, as in
let .Some(user) = find(id) else: return .None, or an indented suite. - grammar.stmt.let-else.closedThe initializer of a let-else is a
closed_expression, so it cannot end in a suite. Parentheses hold such an initializer, as inlet .Some(v) = (if c: a else: b) else: return. - grammar.stmt.let-else.not-inlineA let-else is never a same-line suite body. In
if ok: let .Some(v) = f() else: return, layout ends theifsuite beforeelse, so theelsebelongs to theif. - grammar.stmt.let-else.semanticThe else block must diverge, which is a semantic rule of Let Patterns.
Why. An else body that returns early keeps the matched names at the function's own indentation. Rust's
let ... elseand Swift'sguard let ... elseread the same way.
#Short Binding Lists
A := binding binds exactly one name; destructuring uses let:
fn pair() -> (i32, i32): (1, 2)
fn split() -> i32:
total := 0
let (low, high) = pair()
total + low + high- grammar.stmt.short-binding.one-name
:=binds exactly one name, as intotal := 0. - grammar.stmt.short-binding.let-onlyA pattern before
:=, such as(a, b) := pair,Point { x, y } := p, or.Some(v) := found, is an error whose fix-it writesletand=, as inlet (a, b) = pair. Error:missing-let. - grammar.stmt.short-binding.bare-listA bare list before
:=, as ina, b := pair, is an error whose fix-it writeslet (a, b) = pair. Error:syntax-error. - grammar.stmt.typed-binding.letA binding with a type annotation must begin with
let. Sox: T = vis an error whose fix-it writeslet x: T = v, orx := vwithout the type. Error:missing-let.
struct Point:
x: i32
y: i32
fn sum(p: Point, pair: (i32, i32)) -> i32:
Point { x, y } := p # error: missing-let
(low, high) := pair # error: missing-let
a, b := pair # error: syntax-error
display_name: string = "Ada" # error: missing-let
x + yNote. For
x: T = v, themissing-letmessage reads: "a binding with a type annotation must begin with 'let'; writelet x: T = ..., orx := ...without the type". It does not call the binding mutable.
Why. One way to destructure is easier to read and to write than two.
letalready takes every pattern, and:=stays the short form for one name.
#Discard And Defer Statements
- grammar.stmt.discardThe dedicated discard forms make
_ := expressiona statement without making the placeholder_an identifier or a binding pattern. - grammar.stmt.discard.suiteThe suite form of a discard exists for the same reason when the discarded expression owns an indented suite.
- grammar.stmt.defer
deferis parsed wherever a suite statement is accepted; the semantic rules in Control Flow restrict it to executing cleanup scopes. - grammar.stmt.usingA statement that starts with the contextual word
usingand a name on the same line is ausing_statement. The semantic rules are in Using Bindings. - grammar.stmt.using.one-nameA
usingstatement binds exactly one name: no pattern and nomut.
fn twice(n: i32) -> i32: n * 2
fn demo() -> void:
_ := twice(21) # the value is discarded#Suite Statements
- grammar.stmt.suiteA
suite_statementis a statement whose outermost expression owns a suite. - grammar.stmt.suite.endIts final
DEDENT, or theSUITE_ENDof a same-line suite, terminates the statement; it does not require anotherNEWLINE. - grammar.stmt.suite.right-sideThis separate production is what permits
value := if ...,let callback = fn ..., and similar direct right-hand-side forms. - grammar.stmt.suite.trailing-blockEvery right-hand side that accepts a suite expression, after
:=,let ... =,=,_ :=,return, andbreak, also accepts a trailing block call. - grammar.stmt.chainA chain of bindings continues only with single names, as in
a := b := if c: 1 else: 2. - grammar.stmt.suite.in-delimitersA suite expression nested inside delimiters remains part of its enclosing expression, and the enclosing statement ends normally after the closing delimiter.
fn choose(flag: bool) -> i32:
y := if flag: +1 else: +2
y#Statements Ending At A Newline
- grammar.stmt.closedA statement that ends at
NEWLINEtakes aclosed_expression, which cannot end in a suite. Layout emits noNEWLINEafter a suite'sSUITE_ENDorDEDENT. - grammar.stmt.closed.suite-alternativesOnly the
suite_statementalternatives may end in a suite. - grammar.stmt.closed.examplesThus
y := if c: 1 else: 2is a statement, but_ := y := if c: 1 else: 2andreturn y := if c: 1 else: 2are syntax errors. Error:syntax-error. - grammar.stmt.closed.parenthesizedParenthesizing the inner binding makes them valid.
fn choose(flag: bool) -> i32:
_ := y := if flag: 1 else: 2 # error: syntax-error
return y := if flag: 1 else: 2 # error: syntax-error#Same-Line Suite Bodies
- grammar.inline.statementA same-line suite body is an
inline_statement. - grammar.inline.closed-by-layoutLayout closes a same-line suite at the end of its logical line and at any comma at the suite's own delimiter depth.
- grammar.inline.no-commaThe body therefore contains no comma at that depth and no indented suite.
- grammar.inline.no-ifIt also contains no same-line
ifat that depth:if a: if b: 1 else: 2 else: 3,fn f() -> i32: if c: 1 else: 2, anddefer: if flag: passare syntax errors. Error:syntax-error. - grammar.inline.nested-ifParentheses nest a conditional, as in
if a: (if b: 1 else: 2) else: 3, and an indented body may hold one. - grammar.inline.else-if
else ifcontinues the same conditional rather than nesting one. - grammar.inline.loopsSame-line
forandwhileloops may still appear directly in a same-line suite. - grammar.inline.let-patternA
letstatement may be a same-line suite body, because a pattern's commas stand inside brackets, as inif ok: let (a, b) = pairandif ok: let Point { x, y } = p. - grammar.inline.bare-commaThe bare comma forms still close the suite, so
if ok: a, b := pairandif ok: let a, b = pairare syntax errors. Error:syntax-error.
Note. The
late_feeexample starts itsifin an indented function body, not a same-line suite, so it is valid.
fn pair() -> (i32, i32): (1, 2)
fn pick(a: bool, b: bool) -> i32:
v := if a: if b: 1 else: 2 else: 3 # error: syntax-error
v
fn sign(x: i32) -> i32: if x < 0: -1 else: 1 # error: syntax-error
fn release(flag: bool) -> void:
defer: if flag: pass # error: syntax-error
if flag: a, b := pair() # error: syntax-error
if flag: let a, b = pair() # error: syntax-errorNote. A binding is scoped to its block, so a name that a same-line suite binds is never read after the suite ends. The
unused-local-bindingwarning reports it:
fn pair() -> (i32, i32): (1, 2)
fn split(ready: bool) -> void:
if ready: let (low, high) = pair() # warning: unused-local-binding#Expressions Followed By Another Token
- grammar.continued.positionsWhere another token follows an expression inside brackets, the grammar uses
continued_expression. - grammar.continued.position-listThose positions are the header of a control-flow expression written directly inside brackets, a comprehension clause, and a map key. They are also a spread before
...and a parameter decorator. - grammar.continued.no-same-line-suiteA
continued_expressioncannot end in a same-line suite, because layout would extend that suite over the following token. - grammar.continued.layoutLayout ends a same-line suite only at a line boundary outside brackets, at a comma or closing delimiter at its depth, or before
else. - grammar.continued.indented-suiteA
continued_expressionmay end in an indented suite, except a closure body. - grammar.continued.no-closureAfter an indented closure body inside brackets, the next line must start with
,or a closing delimiter, soindented_suite_expressionhas no closure alternative. - grammar.closed.outside-bracketsOutside brackets, an expression followed by another token cannot end in any suite.
- grammar.closed.header-positionsA control-flow header in a statement and a match guard therefore take a
closed_expression. - grammar.closed.bracketed-suiteA suite may still appear inside brackets within the header, as in
if check(fn(x): ...):. - grammar.closed.indented-headerBut a statement
if fn() -> bool:, followed by the closure's indented body and then a line beginning: 1 else: 2, is a syntax error. Its header ends in an indented suite. Error:syntax-error. - grammar.closed.nested-statementsThe statements of a suite nested inside brackets follow the same rule, because they are statements too.
fn pick() -> i32:
if fn() -> bool: # error: syntax-error
true
: 1 else: 2See also: Physical And Logical Lines.
#Semantic Statement Rules
- grammar.stmt.semanticWhether a statement may appear in a particular value-producing block is a semantic rule.
- grammar.stmt.breakIn particular,
breakis valid only inside a loop, andbreakwith a value is valid only in a loop with anelsesuite. - grammar.stmt.assign-placeThe left side of an assignment must resolve to a reassignable local, a field or element of a writable place, or a call place. Other postfix expressions are errors. Error:
invalid-assignment-target. - grammar.stmt.compound-assignA compound assignment
place op= valuetakes the same left side as an assignment, and its right side follows the same forms as=. Compound Assignment defines it. - grammar.stmt.no-copy-assignmentA statement
place ...= valueis a syntax error. Error:syntax-error.
fn demo() -> void:
let x = 1
x = x + 1 # x is a reassignable localfn invalid() -> void:
let value = 1
value ...= 2 # error: syntax-errorSee also: Data Embedding.
#Declarations
A declaration is an optionally public named declaration or an implementation:
declaration = [ "pub" ], ( function_decl
| data_decl
| enum_decl
| trait_decl
| type_decl )
| impl_decl
;
- grammar.decl.impl-no-pub
pubis not accepted before animpldeclaration.
Why. Implementations are not independently named module members.
#Functions
function_decl = "fn", callable_name, [ function_generic_params ], parameter_clause,
[ "->", function_result_type ], [ requirement_clause ], ":",
suite_body ;
function_result_type = result_type ;
opaque_result_type = "impl", bound_trait_type ;
callable_name = identifier, [ "!" ] ;
suite_body = inline_suite_body
| NEWLINE, INDENT, statement, { statement }, DEDENT
;
inline_suite_body = inline_statement, SUITE_END ;
parameter_clause = "(", [ parameter_list ], ")" ;
parameter_list = parameter, { ",", parameter }, [ "," ] ;
parameter = receiver_parameter
| { parameter_decorator }, value_parameter
;
parameter_decorator = "@", continued_expression ;
value_parameter = [ "mut" ], identifier, ":", type, [ "=", expression ]
| [ "mut" ], identifier, "...", ":", type
;
receiver_parameter = "self" | "mut", "self" | "&", "self" | "&", "mut", "self"
| "*", "self" | "*", "mut", "self" ;
- grammar.fn.receiverThe receiver forms are valid only for methods.
- grammar.fn.receiver-ref
&selfand&mut selfare receivers that take a reference; Reference Receivers states their meaning. - grammar.fn.receiver-counted
*selfand*mut selfare receivers that take a counted reference, for a method that keepsself. Reference Receivers states their meaning. - grammar.fn.param-mut
mutbefore a parameter name marks a parameter whose value changes. Parameters states its meaning. - grammar.fn.decorator-param-targetsAmong function parameters, parameter decorators are valid only on value parameters of module-level named functions and of methods. Variant payload parameters also accept them, as Enums states.
- grammar.fn.decorator.linesWithin a multiline parameter clause, each decorator may occupy its own prefix line; delimiter line breaks do not terminate the parameter.
- grammar.fn.vararg-nameA vararg parameter writes
...after its name, as invalues...: List[i32]; it takes no default and must be the final positional parameter. - grammar.fn.semanticDefault-argument ordering and the requirement-free rule are semantic constraints defined in Functions.
- grammar.fn.opaque-result-nested
implmay occur in a named-function result annotation, including nested ordinary type arguments, tuples and optional types. - grammar.fn.opaque-result.positions
implis invalid in parameters, fields, bindings, closure annotations, function types and requirement rows. Error:syntax-error. - grammar.fn.opaque-bound.no-nestingAn
impltype must not appear inside anotherimplbound. Error:syntax-error.
fn scaled(values...: List[i32], factor: i32) -> i32: factor # error: nonfinal-vararg#Data Types
data_decl = "struct", identifier, [ type_params ], ":", data_suite ;
data_suite = "pass", SUITE_END
| NEWLINE, INDENT,
( "pass", NEWLINE
| data_member, { data_member } ), DEDENT
;
data_member = { decorator_line }, ( data_field | embedded_field ), NEWLINE
;
data_field = [ "pub" ], identifier, ":", type, [ "=", closed_expression ] ;
embedded_field = named_type ;
- grammar.data.no-mut-modifier
mutis not a struct-member modifier:mut name: stringandmut Baseare errors. Error:mutable-field-modifier. - grammar.data.embeddedAn embedded field must denote a struct type and must not include
mut. - grammar.data.embedded.no-pubAn embedded field takes no
pubmarker, sopub Basein a struct body is an error. Error:syntax-error. - grammar.data.defaultField default expressions have the requirement-free constraint specified in Field Defaults.
- grammar.data.struct-onlyThis declaration is written with
structand declares a struct (Struct Declarations).datais a reserved word that starts no declaration, sodata Point:is an error. Error:syntax-error.
pub struct Base:
id: string
pub struct Post:
pub Base # error: syntax-error
pub title: string
data Point: # error: syntax-error
x: i32Why. An embedded field is always public.
See also: Struct Declarations.
#Enums
enum_decl = "enum", identifier, [ type_params ],
[ enum_parameter_clause ], ":",
NEWLINE, INDENT, enum_variant, { enum_variant }, DEDENT ;
enum_variant = { decorator_line }, identifier, [ variant_parameter_clause ],
[ "->", variant_shared_data ], NEWLINE ;
enum_parameter_clause = "(", [ enum_parameter_list ], ")" ;
enum_parameter_list = enum_parameter, { ",", enum_parameter }, [ "," ] ;
enum_parameter = [ identifier, ":" ], type, [ "=", expression ] ;
variant_parameter_clause = "(", [ data_parameter_list ], ")" ;
data_parameter_list = data_parameter, { ",", data_parameter }, [ "," ] ;
data_parameter = { parameter_decorator }, [ identifier, ":" ], type ;
variant_shared_data = identifier, argument_clause ;
- grammar.enum.variant-resultThe optional variant result initializes constructor data shared by every variant, as in
NotFound -> StatusCode(404). - grammar.enum.no-result-typeAfter
->, a variant names its enum and gives an argument clause. A result type there, as inIntLit(value: i64) -> Expr[i64], is an error. Error:variant-result-type-removed. - grammar.enum.no-type-paramsType parameters after a variant's name, as in
If[T](cond: Expr[bool]), are an error. Error:variant-result-type-removed. - grammar.enum.defaultsOnly shared enum constructor parameters may declare defaults.
- grammar.enum.defaults.rulesTheir ordering and requirement-free constraints follow function-parameter defaults.
- grammar.enum.payload-decoratorA variant payload parameter may carry parameter decorators, as in
Moved(to: string, @rename("why") reason: string). They attach facts to that payload member.
Why. A variant result type and variant type parameters were the GADT forms, which hd removed. A typed request and its response pair through a trait with an associated type instead.
#Traits And Implementations
trait_decl = "trait", identifier, [ type_params ],
[ "<", supertrait_bounds ],
( NEWLINE
| ":", NEWLINE, INDENT,
trait_member, { trait_member }, DEDENT )
;
supertrait_bounds = bound_trait_type, { "&", bound_trait_type } ;
trait_member = associated_type_decl
| { decorator_line }, "fn", callable_name, [ function_generic_params ],
parameter_clause,
"->", function_result_type, [ requirement_clause ],
( NEWLINE | ":", suite_body )
;
impl_decl = "impl", [ generic_params ], impl_header_types,
[ "by", identifier ],
( NEWLINE
| ":", NEWLINE, INDENT,
impl_member, { impl_member }, DEDENT )
;
impl_header_types = trait_type, "for", type
| type
;
impl_member = associated_type_decl
| { decorator_line }, method_decl
| intrinsic_method
| derivation_line
;
intrinsic_method = "@", "intrinsic", NEWLINE, "fn", callable_name,
[ function_generic_params ], parameter_clause,
"->", function_result_type, NEWLINE ;
derivation_line = ( identifier | "Self" ), ( "=" | "+=" ), closed_expression,
NEWLINE ;
method_decl = [ "pub" ], "fn", callable_name, [ function_generic_params ],
parameter_clause, [ "->", function_result_type ],
[ requirement_clause ], ":", suite_body ;
associated_type_decl = "type", identifier, [ "<", supertrait_bounds ],
[ "=", type ], NEWLINE ;
- grammar.impl.inherent
impl T:is an inherent implementation. - grammar.impl.trait
impl Trait for T:is a trait implementation. - grammar.impl.delegation-fieldExcept when
EisStructure,impl Trait for T by Edelegates the trait to the embedded fieldEofTand may omit its body. - grammar.trait.markerA trait declaration without a body is a marker trait.
- grammar.impl.bodylessA trait implementation may omit its body when the trait is a marker or when every trait method has a default.
- grammar.impl.pub-methodA
pubmethod is permitted only in an inherent implementation; trait method visibility follows the trait. Apubtrait method or trait implementation method is an error. Error:trait-method-visibility. - grammar.trait.method-endA bodyless trait method ends at
NEWLINE; a default method has:followed by a suite. - grammar.trait.supertrait
trait Child < Parent:declaresParentas a supertrait and opens the body with:. - grammar.decl.bound-vs-colonIn declarations,
<introduces a bound (a supertrait or a generic parameter bound), while:means "has type" or opens a suite. - grammar.trait.method-receiverA function member whose first parameter is a receiver (
self,mut self,&self, or&mut self) is a method; a receiverless member is an associated function. - grammar.trait.associated-typeAssociated type declarations omit
=in a trait requirement and provide= typein an implementation. - grammar.trait.associated-type.boundOnly a trait's associated type declaration may name bounds after
<. Bounds in an implementation's declaration are an error. Error:syntax-error. - grammar.impl.inline-boundsGeneric implementations state every bound inline in their generic parameter list; the language has no separate bound clause.
- grammar.impl.by-structure
impl Trait for T by Structuredeclares a derivation template or a derivation block, as Typed Derivation defines. - grammar.impl.by-no-trait
impl T by Structure, without a trait, declares a derivation block with no trait, as Derivation Blocks With No Trait defines. - grammar.impl.by-no-trait.otherIn a header without a trait,
byfollowed by any other name is a semantic error, not a grammar error, astrait.by.no-traitdefines. - grammar.impl.derivation-lineA
derivation_lineis a member line. Its placement and meaning are defined in Member Lines. - grammar.impl.derivation-line.formsWhich right sides a member line accepts is a semantic rule of Member Lines, not a grammar rule.
- grammar.impl.intrinsic-methodAn
intrinsic_methodis an implementation method without a body. Intrinsic Methods defines where one is valid.
pub trait Display:
pub fn to_string(self) -> string # error: trait-method-visibility
trait Named:
fn name(self) -> string
trait Greeter: Named # error: syntax-errorSee also: Trait Delegation.
#Type Declarations
type_decl = "type", identifier, [ type_params ],
( "=", ( type | row_alias_target ) | "(", type, ")" ), NEWLINE ;
row_alias_target = "$", requirement_row ;
- grammar.type-decl.aliasThe
=form declares a transparent alias. - grammar.type-decl.newtypeThe parenthesized form declares a nominal single-field newtype.
- grammar.type-decl.row-alias.dollarAn
=form whose right side is a row after$, as intype AppRow = $ Db + Cacheortype NoRow = $(), declares a row alias. - grammar.type-decl.row-alias.bareA right side that joins keys with
+but has no$, as intype AppRow = Db + Cache, parses, andreq.row.alias.dollar.missingrejects it. - grammar.type-decl.alias.bare-trait.reference-valuesA right side of one trait without
$, as intype Store = Db, parses as a type, andtypes.trait.value.dyn-required.reference-valuesrejects it. - grammar.type-decl.row-alias.no-and
&joins bounds only, sotype Both = $ Db & Cacheis an error. Error:syntax-error.
trait Db
trait Cache
type AppRow = $ Db + Cache
type Both = $ Db & Cache # error: syntax-errorSee also: Row Aliases.
#Generic Parameters And Bounds
type_params = "[", type_parameter, { ",", type_parameter }, [ "," ], "]" ;
generic_params = "[", generic_parameter, { ",", generic_parameter }, [ "," ], "]" ;
function_generic_params = "[", function_generic_parameter,
{ ",", function_generic_parameter }, [ "," ], "]" ;
type_parameter = [ variance ], identifier, [ "<", trait_bounds ],
[ type_default ]
| row_parameter_decl, [ type_default ] ;
generic_parameter = identifier, [ "<", trait_bounds ]
| row_parameter_decl ;
row_parameter_decl = "$", identifier ;
function_generic_parameter = generic_parameter, [ type_default ] ;
type_default = "=", type_argument ;
variance = "+" | "-" ;
trait_bounds = bound_trait_type, { "&", bound_trait_type } ;
bound_trait_type = qualified_name, [ bound_type_arguments ] ;
bound_type_arguments = "[", bound_type_argument_list, [ "," ], "]" ;
bound_type_argument_list = type_argument, { ",", type_argument },
{ ",", associated_type_binding }
| associated_type_binding,
{ ",", associated_type_binding }
;
associated_type_binding = identifier, "=", type ;
trait_type = qualified_name, [ type_arguments ] ;
#Multiple Bounds
- grammar.generic.bound.andSeveral trait bounds on one type are joined with
&, as inT < Eq & Hash, and the type implements every one. - grammar.generic.bound.and.positions
&joins bounds in every bound position: a generic parameter, a supertrait list, and an implementation's generic parameters. - grammar.generic.bound.and.examplesFor example,
trait Ord < Eq & PartialOrdandimpl[T < Eq & Hash] Hash for Bag[T]join bounds with&. - grammar.generic.bound.and.types-onlyBounds appear only in type positions, so a bound's
&never conflicts with the bitwise&of an expression.
trait Named:
fn name(self) -> string
trait Tagged:
fn tag(self) -> string
fn label[T < Named & Tagged](value: T) -> string:
value.name() + value.tag() # T implements both traitsWhy.
&means both at once, as in Java<T extends A & B>, TypeScript and Scala 3A & B, and SwiftP & Q. It leaves+to requirement rows, so each operator has one meaning.
#Associated Type Bindings In Bounds
- grammar.generic.bindingA trait in a generic parameter bound may end its bracketed arguments with associated type bindings. So
I < Supplier[Item = T]requiresIto implementSupplierwithI::Itemequal toT. - grammar.generic.binding.orderBindings follow every positional type argument.
- grammar.generic.binding.positions-key.reference-valuesBindings are valid in
trait_bounds, and insupertrait_boundsas intrait Summable < Add[Out = Self]. They are also valid in adyn_type, as in the trait value type&dyn Supplier[Item = i32], and in arequirement_key, as in$ Store[Item = User]. - grammar.generic.binding.trait-type-onlyThe trait of an implementation header is a
trait_type, and the qualifier of a trait-qualified call or a method reference is anexpression_trait_type. Neither takes a binding in its arguments. A binding there is an error. Error:syntax-error. - grammar.generic.binding.named-typeA binding in a
named_typehas no meaning; Binding Positions rejects it, and a trait value type writes its bindings afterdyn.
trait Supplier:
type Item
fn get(self) -> Self::Item
struct Constant:
value: string
impl Supplier[Item = string] for Constant: # error: syntax-error
fn get(self) -> string: self.valueSee also: Associated Type Bindings.
#Type-Argument Default Syntax
A generic parameter may end in = and a default type argument, after its
bound:
trait Supplier:
type Item
fn get(self) -> Self::Item
struct Constant: pass
impl Supplier for Constant:
type Item = string
fn get(self) -> string: "constant"
fn pick[T, I < Supplier[Item = T] = Constant](source: I) -> T:
source.get()- grammar.generic.defaultA
type_defaultmay end a generic parameter of a struct, enum, trait,typedeclaration, function, or method, after any bound. - grammar.generic.default.bindingA binding sits inside a bound trait's brackets, while a default follows them at the level of the parameter list.
I < Supplier[Item = T] = Constanthas both. - grammar.generic.default.implThe generic parameters of an implementation use
generic_params, which has no default. A default there is an error. Error:syntax-error. - grammar.generic.default.semanticDefault order, the names a default may use, and when it applies are semantic rules of Type-Argument Defaults.
struct Box[T]:
value: T
impl[T = i32] Box[T]: # error: syntax-error
fn get(self) -> T: self.value#Generic Parameter Modifiers
- grammar.generic.varianceVariance markers are valid on generic type declarations, not function generic parameters.
- grammar.generic.row-markerA
$before a generic parameter's name, as infn map[T, U, $R], declares a row parameter, as Row Parameters specifies. - grammar.generic.row-marker.no-boundA row parameter takes no bound, so
$R < Logis an error. Error:syntax-error. - grammar.generic.row-marker.defaultA row parameter's default is a row type argument, as in
$R = $(). - grammar.generic.bound.no-mutA bound takes no
mutprefix, soT < mut Namedis an error. Error:syntax-error.
trait Log
fn run[$R < Log](job: fn() -> void $ R) -> void $ R: # error: syntax-error
job()See also: Keywords And Reserved Words.
#Types
type = reference_access_type, { "?" }
| function_type
;
reference_access_type = [ "mut" ], reference_type
| ref_type
;
ref_type = "&", [ "mut" ], reference_type
| "*", [ "mut" ], reference_type
;
reference_type = opaque_result_type
| named_type
| dyn_type
| "Self"
| tuple_type
| grouped_type
| associated_type_projection
| context_type
;
named_type = qualified_name, [ bound_type_arguments ] ;
dyn_type = "dyn", bound_trait_type ;
type_arguments = "[", type_argument, { ",", type_argument }, [ "," ], "]" ;
type_argument = type
| row_type_argument
;
row_type_argument = "$", requirement_row ;
tuple_type = "(", ")"
| "(", type, ",", [ type_list ], ")"
| "(", rest_type, ",", ")"
;
rest_type = type, "..." ;
grouped_type = "(", type, ")" ;
function_type = "fn", [ "!" ], "(", [ type_list ], ")",
"->", type, [ requirement_clause ] ;
type_list = type, { ",", type }, [ ",", rest_type ], [ "," ]
| rest_type, [ "," ]
;
result_type = reference_access_type, { "?" }
| result_function_type
;
result_function_type = "fn", [ "!" ], "(", [ type_list ], ")",
"->", result_type ;
associated_type_projection = ( qualified_name | "Self" ), "::", identifier ;
qualified_name = identifier, { ".", identifier } ;
requirement_clause = "$", requirement_row ;
requirement_row = requirement_list
| "(", ")"
;
requirement_list = requirement_key, { "+", requirement_key } ;
requirement_key = bound_trait_type ;
#Requirement Clauses
- grammar.type.row.plus-keysA requirement row lists separate requirement keys joined by
+, as in$ Db + Cache. - grammar.type.row.singleA single key is written alone, as in
$ Console. - grammar.type.row.empty
$()is the empty row. - grammar.type.row.one-formEvery position writes a row the same way: a header, a bodyless trait method, a function type, a row type argument, and
$.Context[...]. - grammar.type.row.one-form.examplesThe header
fn load(id: UserId) -> User $ Db + Cache:and the typefn(UserId) -> User $ Db + Cachewrite one row. - grammar.type.row.no-mut-keyA requirement key has no
mutprefix, so$ R + mut Loggeris an error. Error:syntax-error. - grammar.type.row.no-parenthesesParentheses never surround a nonempty row, so the former
$(A + B)is an error. Error:syntax-error. - grammar.type.row.in-type.commaInside a type, a comma after a key ends the row, so
fn f(cb: fn() -> i32 $ A, B) -> i32:is an error. Error:syntax-error. - grammar.type.row.key-bindingA requirement key may end its bracketed arguments with associated type bindings, as a bound does:
$ Store[Item = User]. - grammar.type.row.key-binding.everywhereEvery place that names a key takes the same form, including
$.use,$.with,$.context, and$.Context[...]. In a provider entry the binding's=sits inside the brackets, as in$.with(Store[Item = User]=store).
trait Clock
trait Logger
fn grouped() -> void $(Clock + Logger): pass # error: syntax-error
fn run(callback: fn() -> void $ Clock, Logger) -> void: pass # error: syntax-error#Row Operators
- grammar.type.row.plus-only
+is the only row operator: it joins keys into one row. - grammar.type.row.plus-rows-only
+has no bound meaning; several bounds on one type are joined with&, as inT < A & B. - grammar.type.row.no-subtractionA
-between requirement keys, as in the former$ R - K, is an error. Error:syntax-error. - grammar.type.row.extensionRemoving a key from a callback row is written by extension instead, as Requirement Polymorphism specifies.
trait Logger
fn drop_logger[R](callback: fn() -> void $ R) -> void $ R - Logger: callback() # error: syntax-error#Row Type Arguments
- grammar.type.row-argumentFor a row-kinded generic parameter, a type argument may be a row after
$, as inFn[(), void, $ Logger + Clock], or$()for the empty row. - grammar.type.row-argument.dollarA row argument always starts with
$, even for one key or a row alias, as inFn[(), void, $ Db]. - grammar.type.row-argument.bareA bare name for a row-kinded parameter parses as a type, which
req.row.slot.barerejects. - grammar.type.row-argument.kindUsing a row argument for a type-kinded parameter, or a type for a row-kinded one, is an error. Error:
generic-kind-mismatch.
struct Box[T]:
value: T
fn invalid(value: Box[$()]) -> void: pass # error: generic-kind-mismatch#Modifiers, Optionality, And Grouping
- grammar.type.mut
mutis a type modifier. - grammar.type.mut.suspend-onlySemantic rules accept the modifier only in
mut Suspend[T]and reject it on every other type. - grammar.type.mut.no-function
mutnever directly precedesfn:mut fn() -> i32as a type, ormut fn() -> i32:as a closure header, is an error. Error:syntax-error. - grammar.type.optionalOptionality applies to the complete reference access type and may be nested.
- grammar.type.optional.functionIn
fn() -> T?,?belongs to the innermost result type; an optional function type must be grouped, as in(fn() -> T)?. - grammar.type.groupParentheses group types; unlike a one-element tuple type, grouping has no trailing comma.
- grammar.type.dyn.reference-valuesA
dyn_typewritesdynand a trait name with its arguments and bindings, as in the referent of&dyn Supplier[Item = i32]. A bare descriptor is rejected as a value type. - grammar.type.dyn.mutA
mutmodifier precedesdyn, sodyn mut Counteris an error. Error:syntax-error. - grammar.type.dyn.optional.reference-values
?applies to the whole trait value reference:*dyn Display?is an optional reference. - grammar.type.row-ownerInside a type, a requirement clause following nested function types likewise belongs to the innermost ungrouped function type. That holds in a parameter type, a field type, and a type argument.
- grammar.type.row-owner.groupedParentheses select an outer owner.
- grammar.type.ref
&Tand&mut Twrite local reference types. Reference Types states their syntax rules. - grammar.type.counted-ref
*Tand*mut Twrite counted reference types, with the same syntax rules. A*in a type is never multiplication or a dereference.
fn readers() -> (fn() -> i32)?:
.None # the group makes the function itself optional#Tuple Rest Types
A rest_type is a type followed by .... It may end a tuple type or a
function type's parameter list:
fn total(pair: (i32, List[i32]...), callback: fn(i32, List[i32]...) -> i32) -> i32:
callback(pair...)
fn only(values: (List[i32]...,)) -> usize: values._0.len()- grammar.type.restA rest element may be the last element of a tuple type or of a function type's parameter list. Alone in a tuple type, it keeps the one-element trailing comma, as in
(List[i32]...,). - grammar.type.rest.elsewhere
...anywhere else in a type is an error. This coversvalues: i32...,List[i32...],(List[i32]...), and a rest element before another element. Error:syntax-error.
fn early(values: (List[i32]..., i32)) -> i32: 0 # error: syntax-error
fn sum(values: i32...) -> i32: 0 # error: syntax-error#Header Requirement Clauses
A header owns the requirement clause at its end, so a function-typed result with its own row is parenthesized:
fn make() -> fn() -> i32 $ Console + Log: # make requires both keys
_ := $.use(Console, Log)
fn() -> i32: 1
fn wrap() -> (fn() -> i32 $ Log + Trace) $ Console: # the result requires both
_ := $.use(Console)
fn() -> i32 $ Log + Trace:
_ := $.use(Log, Trace)
2- grammar.type.header.ownerA declaration or closure header owns the requirement clause directly before its
:. - grammar.type.header.trait-methodA bodyless trait method owns the clause directly before its line end.
- grammar.type.header.resultIts result is a
result_type, whose function types, however nested, carry no requirement clause, so the clause cannot attach to the result. - grammar.type.header.parenthesized-resultA function-typed result with its own row is parenthesized.
- grammar.type.header.appliesThe rule applies to named functions, methods, trait methods, and closures: in
fn() -> fn() -> i32 $ Console:, the closure requiresConsole. - grammar.type.header.two-clausesWriting
fn() -> i32 $ Log $ Consoleas a declaration result is an error. Error:syntax-error.
trait Log
fn make() -> fn() -> i32 $ Log $ Console: # error: syntax-error
fn() -> i32 $ Log: 1See also: Requirements and Suspension.
#Use Declarations
A use declaration imports a path or a group of names:
use_decl = "use", use_path, [ "as", identifier ], NEWLINE
| [ "pub" ], "use", use_path, ".", use_group, NEWLINE
;
use_path = use_root, { ".", identifier } ;
use_root = "pkg"
| "std"
| "dep", ".", identifier
| "self"
| "super", { ".", "super" }
;
use_group = "{", use_item, { ",", use_item }, [ "," ], "}" ;
use_item = identifier, [ "as", identifier ] ;
- grammar.use.no-import-export
importandexportare not declaration keywords. - grammar.use.contextual-words
pkg,std,dep, andsuperare contextual use-root words, andasis contextual before an alias. - grammar.use.needs-root
usebegins a use declaration only when a use root follows it. - grammar.use.self-rootThe reserved word
selfalso acts as a relative use root.
See also: Keywords And Reserved Words.
#Expressions
The expression grammar is ordered from lowest to highest precedence.
expression = binding_expression ;
binding_expression = identifier, ":=", binding_expression
| conditional_expression
;
conditional_expression = if_expression
| for_expression
| while_expression
| match_expression
| closure_expression
| context_scope
| range_expression
;
suite_expression = statement_if_expression
| statement_for_expression
| statement_while_expression
| statement_match_expression
| closure_expression
| context_scope
;
closed_expression = identifier, ":=", closed_expression
| range_expression
;
inline_expression = identifier, ":=", inline_expression
| inline_suite_expression
| range_expression
;
continued_expression = identifier, ":=", continued_expression
| continued_conditional_expression
;
continued_conditional_expression = indented_suite_expression
| range_expression
;
indented_suite_expression = indented_if_expression
| indented_for_expression
| indented_while_expression
| match_expression
| "$", ".", "with", "(", context_entries, ")",
":", indented_suite_body
;
inline_suite_expression = inline_for_expression
| inline_while_expression
| inline_closure_expression
| inline_context_scope
;
range_expression = logical_or_expression,
[ "..", [ logical_or_expression ]
| "..=", logical_or_expression ]
| "..", [ logical_or_expression ]
| "..=", logical_or_expression
;
logical_or_expression = logical_and_expression,
{ "||", logical_and_expression } ;
logical_and_expression = comparison_expression,
{ "&&", comparison_expression } ;
comparison_expression = pipe_expression,
[ comparison_operator, pipe_expression ] ;
comparison_operator = "==" | "!=" | "<" | "<=" | ">" | ">=" | "is" ;
pipe_expression = bitwise_or_expression,
{ "|>", bitwise_or_expression } ;
bitwise_or_expression = bitwise_xor_expression,
{ "|", bitwise_xor_expression } ;
bitwise_xor_expression = bitwise_and_expression,
{ "^", bitwise_and_expression } ;
bitwise_and_expression = shift_expression,
{ "&", shift_expression } ;
shift_expression = additive_expression,
{ ( "<<" | ">>" ), additive_expression } ;
additive_expression = multiplicative_expression,
{ ( "+" | "-" ), multiplicative_expression } ;
multiplicative_expression = unary_expression,
{ ( "*" | "/" | "%" ), unary_expression } ;
unary_expression = prefix_expression
| power_expression
;
prefix_expression = ( "+" | "-" | "~" | "!" ), unary_expression
| "&", [ "mut" ], unary_expression
;
power_expression = ( postfix_expression | dereference_expression ),
[ "**", unary_expression ] ;
dereference_expression = "*", ( dereference_expression
| postfix_expression
| prefix_expression ) ;
postfix_expression = primary_expression, { postfix_suffix } ;
postfix_suffix = ".", identifier, [ "::", function_type_arguments ]
| "[", expression, "]"
| argument_clause
| suspension_call_suffix
| "?"
;
suspension_call_suffix = "!", [ "::", function_type_arguments ],
argument_clause ;
#Precedence And Associativity
- grammar.expr.binding
:=is right-associative and has the lowest precedence. - grammar.expr.no-comparison-chainComparisons do not chain, so
a < b < cis an error. Error:comparison-chaining. - grammar.expr.powerExponentiation is right-associative.
- grammar.expr.power.unaryThe right operand of
**may therefore begin with a unary operator. - grammar.expr.pipe
|>is left-associative and binds more tightly than comparison and more loosely than|. - grammar.expr.pipe.stepEach pipe step is a
bitwise_or_expression, so a step with a comparison,&&,||, or a control-flow expression needs parentheses. - grammar.expr.prefix-operandA
&or&mutat the start of an operand is a prefix operator ofunary_expression, and a*there starts adereference_expression, which**takes whole as its base. Reference Expressions states their rules.
inside := 0 < value < 10 # error: comparison-chaining#Range Syntax
A range expression joins two bounds with .. or ..=. A .. may leave
out either bound or both, and a ..= may leave out its start:
fn parts(text: string, n: i32) -> (string, string, string, string, string, i32):
let total = 0
for i in 0..n + 1:
total = total + i
(text[1..], text[..2], text[0..=1], text[..=1], text[..], total)- grammar.expr.range.precedence
..and..=bind more loosely than||and more tightly than control-flow expressions and:=, as in Rust. So0..n + 1is0..(n + 1). - grammar.expr.range.non-assocA range expression is non-associative: a range bound is never itself an unparenthesized range, so
a..b..cis an error. Error:syntax-error. - grammar.expr.range.open-endAfter
.., an end bound is present exactly when the next token can begin an operand. Sotext[1..],for i in 0..:, andf(1.., x)have no end bound. - grammar.expr.range.open-startA
..at the start of an operand begins a range with no start bound, as intext[..2]. - grammar.expr.range.fullA
..with neither bound, as intext[..], is a range expression, the full range. - grammar.expr.range.inclusive-toA
..=at the start of an operand begins a range with no start bound, as intext[..=2]. - grammar.expr.range.inclusive-needs-endA
..=must have an end bound. A..=with no end bound, as in1..=or a bare..=, is an error. Error:syntax-error. - grammar.expr.range.pipe-stepA pipe step is a
bitwise_or_expression, so a range in a pipe step needs parentheses.
fn invalid() -> void:
a := 0..1..2 # error: syntax-error
b := 1..= # error: syntax-errorWhy. A range sits below
||, as in Rust, so arithmetic and comparisons in a bound need no parentheses. Leaving out a bound always means "from the start" or "to the end". Sotext[1..]reads the same in any position, andtext[..]is the whole string.
#Multi-Name Bindings
A binding expression binds one name, so a list or a tuple may hold one as its last element:
fn pair() -> (i32, i32): (1, 2)
fn items(a: (i32, i32)) -> List[(i32, i32)]:
[a, b := pair()]- grammar.expr.multi-binding.elementA list item or a tuple element may be a binding expression. So
[a, b := value]and(a, b := value)each holdaand the bindingb := value, as[a, (b := value)]does. - grammar.expr.multi-binding.let-onlyA name list before
:=inside an expression, as in((a, b) := value)or[(a, b) := value], is an error. Its fix-it writeslet (a, b) = valueas a statement before the expression. Error:syntax-error.
fn pair() -> (i32, i32): (1, 2)
wrapped := [(a, b) := pair()] # error: syntax-error
whole := ((low, high) := pair()) # error: syntax-errorWhy. Destructuring is a
letstatement, on its own line. Python's:=also takes one name, and Go's:=is a statement. The single-name binding, as inif (n := f()) > 0:, stays an expression.
#Bang And Dot Tokens
- grammar.expr.bang-suffix
!(or!::[after a completed operand begins a suspension call suffix at ordinary call precedence. - grammar.expr.prefix-notA
!at the start of an operand is the prefix logical-not operator ofunary_expression, so!fetch!(id)negates a suspending call's result. - grammar.expr.not-equal
!=is a single token by longest match:f!=gis the comparisonf != g. - grammar.expr.member-identifierA member suffix takes an identifier after
., so the tuple selectiont._0._1is two member suffixes. - grammar.expr.no-numeric-memberAn integer or floating-point literal after
.forms no suffix, sot.0andt.0.1are errors. Error:syntax-error.
first := pair.0 # error: syntax-error
second := nested.0.1 # error: syntax-error#Type Arguments In Expressions
In an expression, an explicit type-argument list follows ::, the
type-argument marker. A type keeps its plain brackets:
struct Box[T]:
value: T
fn first[T](items: List[T]) -> T: items[0]
fn demo(names: List[string]) -> Box[string]:
head := first::[string](names)
Box::[string] { value: head }- grammar.expr.type-arguments.markerIn an expression, an explicit type-argument list follows
::: a generic function takesfirst::[string](names), a methodparser.parse::[User](text), and a referenceJson::decode::[User]. - grammar.expr.type-arguments.type-nameA type name in an expression also takes its type arguments after
::. This holds in a data expression such asBox::[i32] { value: 1 }and in a qualified call such asAdd::[i32]::add(price, 5). - grammar.expr.type-arguments.in-typesIn a type, type arguments follow the name directly, as in
List[i32], with no::. - grammar.expr.index-only
[directly after a completed operand always begins an indexing suffix, never type arguments, sohandlers[i](event)indexeshandlersand calls the element. - grammar.expr.type-arguments.unmarkedA data expression or qualified call whose type name takes brackets without
::, as inBox[i32] { value: 1 }orAdd[i32]::add(price, 5), is an error. Error:syntax-error.
struct Box[T]:
value: T
fn boxed() -> Box[i32]:
Box[i32] { value: 1 } # error: syntax-errorWhy. Each form has one reading that a parser can see. Brackets after an expression always index, and
::[always gives type arguments. So neither a reader nor a tool needs name resolution to tell them apart. Rust marks expression type arguments the same way, as inparse::<i32>().
#Method Type Arguments
- grammar.expr.method-type-arguments.validAn explicit method type-argument list is valid only when the selected member is generic and the expression proceeds to an ordinary call.
- grammar.expr.method-type-arguments.bangA bang call writes the
!on the name and the list after it, as the declarationfn fetchdoes. The calls arefetch!::[User](key),parser.load!::[User](text), andStore::load!::[User](key).
struct Identity: pass
impl Identity:
fn echo -> Value:
value
fn run!() -> i32:
Identity::echo::[i32]!(42) # error: syntax-error#Primary Expressions
primary_expression = literal
| "self"
| string_expression
| generic_function_reference
| qualified_name
| contextual_variant_expression
| trait_qualified_call
| method_reference
| context_use
| context_create
| tuple_or_group_expression
| list_expression
| map_expression
| data_expression
| pipe_placeholder
| "pass"
;
pipe_placeholder = "_" ;
generic_function_reference = qualified_name, "::", function_type_arguments ;
function_type_arguments = "[", function_type_argument,
{ ",", function_type_argument }, [ "," ], "]" ;
function_type_argument = type_argument | "_" ;
contextual_variant_expression = ".", identifier ;
trait_qualified_call = expression_trait_type, "::", identifier,
( [ "::", function_type_arguments ], argument_clause
| suspension_call_suffix ) ;
method_reference = expression_trait_type, "::", identifier,
[ "::", function_type_arguments ] ;
expression_trait_type = qualified_name, [ "::", type_arguments ] ;
expression_named_type = qualified_name, [ "::", bound_type_arguments ] ;
literal = boolean_literal
| suffixed_literal
| float_literal
| integer_literal
| char_literal
;
string_expression = interpreted_string_expression
| interpreted_multiline_string_expression
| prefixed_string_expression
;
interpreted_string_expression = '"', { string_segment }, '"' ;
interpreted_multiline_string_expression = '"""',
{ multiline_string_segment },
'"""' ;
string_segment = string_text
| escape_sequence
| "$", identifier
| "$", "self"
| "${", expression, "}"
;
multiline_string_segment = multiline_string_text
| escape_sequence
| "$", identifier
| "$", "self"
| "${", expression, "}"
;
prefixed_string_expression = string_prefix, '"',
{ prefixed_string_segment }, '"'
| string_prefix, '"""',
{ prefixed_multiline_segment }, '"""' ;
prefixed_string_segment = prefixed_string_character
| "$", identifier
| "$", "self"
| "${", expression, "}"
;
prefixed_multiline_segment = prefixed_multiline_character
| "$", identifier
| "$", "self"
| "${", expression, "}"
;
tuple_or_group_expression = "(", ")"
| "(", expression, ")"
| "(", tuple_element, ",", [ tuple_elements ], ")"
| "(", tuple_spread, ",", ")"
;
tuple_elements = tuple_element, { ",", tuple_element },
[ ",", tuple_spread ], [ "," ]
| tuple_spread, [ "," ]
;
tuple_element = expression ;
tuple_spread = continued_expression, "..." ;
list_expression = "[", [ list_items ], "]"
| list_comprehension
;
list_items = list_item, { ",", list_item }, [ "," ] ;
list_item = expression
| continued_expression, "..."
;
map_expression = "{", [ map_items ], "}"
| map_comprehension
;
map_items = map_item, { ",", map_item }, [ "," ] ;
map_item = continued_expression, ":", expression ;
data_expression = expression_named_type, "{", [ data_items ], "}" ;
data_items = [ "...", expression, "," ],
data_field_item, { ",", data_field_item }, [ "," ]
| "...", expression, [ "," ]
;
data_field_item = identifier, ":", expression
| identifier
;
#Pipe Placeholder
- grammar.primary.pipe-placeholder
_in expression position is the pipe placeholder. - grammar.primary.pipe-placeholder.semanticThe grammar accepts it as any primary expression; Pipe Expressions limits it to pipe steps.
fn wrap(text: string, mark: string) -> string:
"$mark$text$mark"
fn demo(text: string) -> string:
text |> wrap(_, "*") # _ marks where the piped value goes#Copies In Data Expressions
- grammar.primary.update-spreadA prefix
...in a data expression begins a copy-update spread, which copies the named members of its operand.
struct Point:
x: i32
y: i32
fn move_right(p: Point) -> Point:
Point { ...p, x: p.x + 1 }See also: Data Embedding.
#Field Shorthand In Data Expressions
- grammar.primary.field-shorthandA
data_field_itemthat is a bareidentifieris field shorthand:xmeansx: x, as inPoint { x, y }. - grammar.primary.field-shorthand.mixedShorthand and labeled items may be mixed in one data expression, as in
Point { x, y: 0 }, and may follow a copy-update spread.
struct Point:
x: i32
y: i32
fn build(x: i32, y: i32) -> Point:
Point { x, y }Why. It mirrors the bare
fieldof a data pattern, so building and destructuring a value read alike.
See also: Data Literals.
#Reflection
- grammar.primary.no-reflection-syntaxDeclaration reflection has no dedicated syntax.
@derive(Debug)
struct Point:
x: i32
fn label(p: Point) -> string:
debug(p) # "Point { x: 1 }": the name reaches text through a derivation#Suffixed Literals
- grammar.primary.literal-callA
suffixed_literaltoken and aprefixed_string_expressionare primary expressions, each standing for a call as Literal Suffixes specifies. - grammar.pattern.no-literal-call
literal_patternadmits neither a suffixed literal nor a prefixed string, so5sorr"a"in a pattern is an error. Error:syntax-error.
fn describe(count: i32) -> string:
match count:
5px => "five" # error: syntax-error
_ => "other"See also: Literal Suffixes.
#Prefixed Strings
The rules of Suffixed Literals cover a prefixed string too:
fn describe(text: string) -> string:
match text:
r"a" => "letter" # error: syntax-error
_ => "other"- grammar.primary.prefix-after-dotA prefixed string directly after
.is an error. This holds whether a module path stands before the dot, as inmarks.tag"item", or a value, as invalue.tag"item". Error:qualified-string-prefix.
use pkg.marks
fn label() -> string:
marks.tag"item" # error: qualified-string-prefix
fn quote(value: string) -> string:
value.tag"item" # error: qualified-string-prefixNote. The
qualified-string-prefixmessage says that only a bare name in scope can be a string prefix, and suggests importing it. Fortext.r"\d+"it reads: "only a bare name in scope can be a string prefix; writeuse std.text.r, thenr"..."".
Why. A bare prefix keeps one rule for every name a literal calls, and
usealready names where it comes from. A path before a prefix would need its own resolution, beside module paths, methods, and fields.
See also: Prefixed Strings.
#Forms Resolved By Name
- grammar.primary.resolutionName resolution distinguishes a data expression from a map expression and an enum variant selection from ordinary field access.
- grammar.primary.function-type-argumentEach function type argument is a type or the inference placeholder
_. - grammar.primary.placeholderThe placeholder is not part of ordinary
type_argumentsand therefore cannot occur in a type such asList[_]. - grammar.primary.qualified-type-argumentsIn a qualified call such as
Type::name::[T](...),Trait::name::[T](...), orType::name!::[T](...), type arguments of the qualifying type or trait stay before the member's::. One example isAdd::[Money]::add. - grammar.primary.member-type-argumentsMethod-level type arguments follow the member name, as in the dot call
parser.parse::[User](text). - grammar.primary.member-type-arguments.rulesThat list is valid only when the selected member is generic, and it follows the explicit-list rules of Generic Functions.
struct Point:
x: i32
y: i32
fn demo() -> i32:
p := Point { x: 1, y: 2 } # a data expression: Point names a struct type
m := {"x": +1} # a map expression
p.x + m.get("x").unwrap_or(+0)#Prefix And Suffix ...
- grammar.primary.list-spreadA list element ending in
...is a spread that expands a list's elements in place. - grammar.primary.ellipsis-positionsThe two positions of
...never overlap. - grammar.primary.prefix-copyA prefix
...always copies: its one position is the copy-update spread of a data expression. - grammar.primary.suffix-spreadsA suffix
...always spreads: it expands the elements or entries of its operand in arguments, list elements, and provider-context entries, as in$.with(ctx...). - grammar.primary.tuple-spreadThe last element of a tuple expression may also be a suffix spread, as in
(1, 2, [3, 4]...). A spread alone keeps the one-element trailing comma,(xs...,), so(xs...)and a spread before another element are errors. Error:syntax-error. - grammar.primary.prefix-elsewhereA prefix
...anywhere else, including before a provider-context entry, is an error. Error:syntax-error.
trait Tag:
fn name(self) -> string
fn widen(base: $.Context[$ Tag]) -> $.Context[$ Tag]:
$.context(...base) # error: syntax-errorSee also: List And Map Expressions.
#Member References
- grammar.primary.method-referenceA
::member without an argument clause, such asUser::domain,Json::decode::[User], oruser::domain, is amethod_reference. - grammar.primary.method-reference.meaningMethod References gives its meaning; with an argument clause, the same form is a call.
- grammar.primary.method-reference.no-bangA reference with type arguments directly followed by
!(is not a bang call of that reference. SoIdentity::echo::[i32]!(42)is an error, and the call isIdentity::echo!::[i32](42). Error:syntax-error.
struct User:
domain: string
impl User:
fn label(self) -> string: self.domain
fn demo(user: User) -> string:
f := user::label
f()#Calls And Arguments
argument_clause = "(", [ argument_list ], ")" ;
argument_list = positional_argument, { ",", positional_argument },
[ ",", named_argument, { ",", named_argument } ], [ "," ]
| named_argument, { ",", named_argument }, [ "," ]
;
positional_argument = expression
| continued_expression, "..."
;
named_argument = identifier, "=", expression ;
- grammar.call.positional-firstPositional arguments, including positional spreads, must precede named arguments. Error:
argument-order.
resize(width=640, 480) # error: argument-orderSee also: Positional Spreads, for what a spread fills.
#Trailing Blocks
A call whose final parameter is a zero-argument function may use an indented
trailing block. The call may stand as a complete statement, or as the complete
right-hand side of :=, let ... =, =, _ :=, return, or break:
trailing_block_call = postfix_expression, ":", indented_suite_body ;
indented_suite_body = NEWLINE, INDENT, statement, { statement }, DEDENT ;
- grammar.call.trailing-blockA call whose final parameter is a zero-argument function may use an indented trailing block. The call may stand as a complete statement, or as the complete right-hand side of
:=,let ... =,=,_ :=,return, orbreak. - grammar.call.trailing-block.no-argumentsWhen there are no ordinary arguments, the call omits
(), as intransaction:. - grammar.call.trailing-block.acceptedThis production is accepted only at delimiter depth zero when the call is the complete statement or one of those complete right-hand sides.
- grammar.call.trailing-block.eligibleIt is also accepted only when name and type resolution identify a callable with an eligible final parameter. A trailing block on any other call is an error. Error:
trailing-block-ineligible. - grammar.call.trailing-block.next-lineIts body must begin on the next logical line. A body on the same line is an error. Error:
syntax-error. - grammar.call.trailing-block.not-headerIt is not accepted in an
if,while,for, ormatchheader or inside brackets. A trailing block there is an error. Error:trailing-block-position.
fn run(callback: fn() -> i32) -> i32: callback()
values := [run: # error: trailing-block-position
1]The next-line rule also rules out a trailing block call as a same-line
suite body. Written on the call's line, the body breaks the next-line rule.
Written on the next line, it is an indented suite, which a same-line suite
never holds (grammar.inline.no-comma):
fn trailing(body: fn() -> void) -> void: body()
fn close_all(close: bool) -> void:
if close: trailing(): pass # error: syntax-errorfn trailing(body: fn() -> void) -> void: body()
fn close_all(close: bool) -> void:
if close: trailing: # error: syntax-error
passThe valid forms indent the if body, or pass the closure as an argument:
fn trailing(body: fn() -> void) -> void: body()
fn close_all(close: bool) -> void:
if close:
trailing:
pass
if close: trailing(fn(): pass)#Closures
closure_expression = closure_header, suite_body ;
inline_closure_expression = closure_header, inline_suite_body ;
closure_header = "fn", [ "!" ], closure_parameter_clause,
[ "->", result_type ], [ requirement_clause ], ":" ;
closure_parameter_clause = "(", [ closure_parameter_list ], ")" ;
closure_parameter_list = closure_parameter,
{ ",", closure_parameter }, [ "," ] ;
closure_parameter = [ "mut" ], identifier, [ ":", type ] ;
- grammar.closure.parameter-typesOmitted closure parameter types require an expected function type.
- grammar.closure.annotationsA standalone or otherwise ambiguous closure must provide enough annotations to determine its complete function type.
- grammar.closure.param-mutA closure parameter may begin with
mut, as a function parameter may.
See also: Closure Annotations, which says when a closure may infer its result type.
#Control-Flow Expressions
if_expression = "if", continued_expression, ":", suite_body,
{ "else", "if", continued_expression, ":", suite_body },
[ "else", ":", suite_body ]
;
for_expression = "for", pattern, "in", continued_expression, ":",
suite_body, [ "else", ":", suite_body ]
;
while_expression = "while", continued_expression, ":", suite_body,
[ "else", ":", suite_body ]
;
indented_if_expression = "if", continued_expression, ":", indented_suite_body
| "if", continued_expression, ":", suite_body,
{ "else", "if", continued_expression, ":",
suite_body },
"else", [ "if", continued_expression ], ":",
indented_suite_body
;
indented_for_expression = "for", pattern, "in", continued_expression,
":", ( indented_suite_body
| suite_body, "else", ":",
indented_suite_body )
;
indented_while_expression = "while", continued_expression, ":",
( indented_suite_body
| suite_body, "else", ":", indented_suite_body )
;
inline_for_expression = "for", pattern, "in", closed_expression, ":",
inline_suite_body, [ "else", ":", inline_suite_body ]
;
inline_while_expression = "while", closed_expression, ":", inline_suite_body,
[ "else", ":", inline_suite_body ]
;
match_expression = "match", continued_expression, ":", NEWLINE, INDENT,
match_arm, { match_arm }, DEDENT
;
statement_if_expression = "if", closed_expression, ":", suite_body,
{ "else", "if", closed_expression, ":",
suite_body },
[ "else", ":", suite_body ]
;
statement_for_expression = "for", pattern, "in", closed_expression,
":", suite_body, [ "else", ":", suite_body ]
;
statement_while_expression = "while", closed_expression, ":", suite_body,
[ "else", ":", suite_body ]
;
statement_match_expression = "match", closed_expression, ":", NEWLINE,
INDENT, match_arm, { match_arm }, DEDENT
;
match_arm = pattern, [ "if", closed_expression ], "=>", arm_body ;
arm_body = suite_expression
| simple_statement, NEWLINE
| NEWLINE, INDENT, statement, { statement }, DEDENT
;
- grammar.flow.for-in-bracketsA
forloop is an expression, so it may also appear inside brackets, as in[for x in xs: body]or(for (k, v) in m: body). - grammar.flow.if-elseAn
ifused where a value is required must have anelse; statement-positionifmay omit it. - grammar.flow.semanticThe
ifand loop rules above are semantic rules, not separate grammar productions. - grammar.flow.for-patternA
forloop or a comprehensionforclause takes a pattern beforein, asletdoes. Sofor (key, value) in entriesuses a tuple pattern, andfor Point { x, y } in pointsa data pattern. - grammar.flow.for-list.bareA bare list, as in
for key, value in entries, is an error whose fix-it adds the parentheses. Error:syntax-error.
fn names(scores: Map[string, i32]) -> List[string]:
for (name, score) in scores: # valid
pass
for name, score in scores: # error: syntax-error
pass
[for (name, score) in scores => name]Why. A
forloop and alettake the same patterns, so one shape reads the same in both. The commas of a pattern stand inside brackets, so a same-line suite may hold the loop.
See also: Let Statements, For Loops for which patterns a loop accepts, and Loop Values for a loop's type.
#Patterns
pattern = "_"
| literal_pattern
| binding_pattern_atom
| variant_pattern
| data_pattern
| tuple_pattern
| unit_pattern
| range_pattern
;
literal_pattern = boolean_literal
| [ "-" ], ( integer_literal | float_literal )
| string_literal
| char_literal
;
binding_pattern_atom = [ "mut" ], identifier ;
variant_pattern = qualified_variant_name, [ pattern_argument_clause ]
| ".", identifier, [ pattern_argument_clause ]
| identifier, pattern_argument_clause
;
qualified_variant_name = identifier, ".", identifier,
{ ".", identifier } ;
pattern_argument_clause = "(", [ pattern_argument_list ], ")" ;
pattern_argument_list = positional_pattern,
{ ",", positional_pattern },
[ ",", named_pattern, { ",", named_pattern } ], [ "," ]
| named_pattern, { ",", named_pattern }, [ "," ]
;
positional_pattern = pattern ;
named_pattern = identifier, "=", pattern ;
data_pattern = qualified_name, "{", [ data_pattern_fields ], "}" ;
data_pattern_fields = data_pattern_field,
{ ",", data_pattern_field }, [ "," ] ;
data_pattern_field = [ "mut" ], identifier
| identifier, ":", pattern
;
tuple_pattern = "(", pattern, ",", [ tuple_pattern_elements ], ")"
| "(", spread_pattern, ",", ")"
;
tuple_pattern_elements = pattern, { ",", pattern },
[ ",", spread_pattern ], [ "," ]
| spread_pattern, [ "," ]
;
spread_pattern = ( "_" | binding_pattern_atom ), "..." ;
unit_pattern = "(", ")" ;
range_pattern = range_pattern_bound, ( "..=" | ".." ), range_pattern_bound
| range_pattern_bound, ".."
| ( "..=" | ".." ), range_pattern_bound
;
range_pattern_bound = [ "-" ], integer_literal ;
- grammar.pattern.variantVariant patterns may use a qualified enum variant name or
.Variantwhen the matched value's type supplies one enum. - grammar.pattern.bare-variantThe unqualified
identifier, pattern_argument_clauseform parses so that a checker can report it as an error. Error:bare-variant-pattern. - grammar.pattern.positional-namesPositional binding names need not match payload field names.
- grammar.pattern.namedIn a payload list, only
field=patternis a named pattern. - grammar.pattern.named-lastNo positional pattern may follow a named pattern. Error:
pattern-order. - grammar.pattern.mut-let-only
mutmay precede a binding name only in aletpattern. In amatcharm's pattern, as in.Some(mut user) => ..., it is an error. Error:syntax-error. - grammar.pattern.data-fieldIn a data pattern, bare
fieldbinds that field's value to a new name of the same spelling. - grammar.pattern.data-field.nested
field: patternmatches the field against a nested pattern, andfield: namebinds it toname. - grammar.pattern.data-unlistedUnlisted fields are ignored.
- grammar.pattern.tuple-spreadThe last element of a tuple pattern may be a spread pattern, a name or
_followed by..., as in(a, b, xs...). Alone, it keeps the one-element trailing comma,(xs...,), so(xs...)and a spread pattern before another element are errors. Error:syntax-error. - grammar.pattern.unit
()is the unit pattern. A tuple pattern needs a comma, so()is never a tuple pattern. - grammar.pattern.rangeA range pattern takes one of five forms:
a..=b,a..b,a..,..=b, or..b. - grammar.pattern.range.boundEach bound of a range pattern is an integer literal, which a
-may precede. A bound of any other kind, such as'a',1.5, or a name, is an error. Error:syntax-error.
enum Pair:
Values(left: i32, right: i32)
fn value_or_zero(value: i32?) -> i32:
match value:
Some(number) => number # error: bare-variant-pattern
.None => 0
fn invalid(value: Pair) -> i32:
match value:
Pair.Values(left=l, r) => l + r # error: pattern-order
fn rebind(value: Pair) -> i32:
match value:
Pair.Values(mut l, r) => l + r # error: syntax-errorfn bucket(n: i32) -> string:
match n:
..=-1 => "negative"
0 => "zero"
1..10 => "small"
10..=99 => "medium"
100.. => "large"
fn sign(n: i32) -> string:
match n:
..0 => "negative"
_ => "other"Why. The five forms are Rust's, as of Rust 1.80, so the same bounds read the same in both languages. Every range expression form but a bare
..is also a pattern.
See also: Match Expressions.
#Labels
- grammar.label.bracketLabels follow their brackets.
- grammar.label.bracesInside
Type { ... }, in data expressions and data patterns alike, a field label is followed by:. - grammar.label.parenthesesInside parentheses, in named arguments, variant payloads, and payload patterns, a label is followed by
=. - grammar.label.data-pattern-equalsA data pattern written with
field = patternis an error. Error:syntax-error.
struct Point:
x: i32
y: i32
fn first(p: Point) -> i32:
match p:
Point { x = 0, y } => y # error: syntax-error
Point { x, y: _ } => x#Comprehensions
list_comprehension = "[", comprehension_clauses, "=>", expression, "]" ;
map_comprehension = "{", comprehension_clauses, "=>",
continued_expression, ":", expression, "}" ;
comprehension_clauses = comprehension_for,
{ comprehension_for | comprehension_if } ;
comprehension_for = "for", pattern, "in", continued_expression ;
comprehension_if = "if", continued_expression ;
- grammar.comprehension.first-forThe first clause must be
for. - grammar.comprehension.orderLater
forandifclauses execute from left to right. - grammar.comprehension.no-letComprehensions do not have a
letclause;:=binding expressions may be used inside guards or result expressions.
#Requirements And Provider Contexts
context_use = "$", ".", "use", "(", requirement_key,
{ ",", requirement_key }, [ "," ], ")" ;
context_create = "$", ".", "context", "(", context_entries, ")" ;
context_type = "$", ".", "Context", "[", type_argument, "]" ;
context_scope = "$", ".", "with", "(", context_entries, ")",
":", suite_body ;
inline_context_scope = "$", ".", "with", "(", context_entries, ")",
":", inline_suite_body ;
context_entries = context_entry, { ",", context_entry }, [ "," ] ;
context_entry = requirement_key, "=", expression
| continued_expression, "..."
;
- grammar.row.setsRequirement rows denote unordered sets of keys after name resolution.
- grammar.row.parameter.markedA generic parameter declared
$Ris a row parameter, and a row writes it by name as a complete requirement key. - grammar.row.parameter.extensionListing a row parameter beside other keys extends that row with them.
#Annotations
decorated_decl = decorator_line, { decorator_line },
( [ "pub" ], ( data_decl | enum_decl | function_decl
| trait_decl | newtype_decl )
| impl_decl ) ;
decorator_line = "@", ( derive_decorator | closed_expression ), NEWLINE ;
newtype_decl = "type", identifier, [ type_params ], "(", type, ")", NEWLINE ;
derive_decorator = "derive", "(", qualified_name,
{ ",", qualified_name }, [ "," ], ")" ;
- grammar.annot.item-targetsDecorator lines may precede a struct, enum, function, trait, newtype, or implementation declaration.
- grammar.annot.member-targetsDecorator lines may also precede a struct field, an enum variant, and a method of a trait or implementation. The
data_member,enum_variant,trait_member, andimpl_memberproductions show this. - grammar.annot.alias-no-decoratorAny decorator before a transparent alias is an error. Error:
syntax-error. - grammar.annot.derive-traitsThe names in a
derive_decoratorare traits. Opting In defines which traits it accepts.
Member metadata written away from a declaration uses a derivation block
with no trait, an impl_decl, as grammar.impl.by-no-trait
states.
See also: Typed Derivation.