Grammar Specification
This document provides a formal grammar specification for the Kit programming language in Extended Backus-Naur Form (EBNF).
Notation
| Symbol | Meaning |
|---|---|
| | Alternation |
[ ] | Optional |
{ } | Zero or more repetitions |
( ) | Grouping |
" " | Terminal string |
UPPER_CASE | Token/terminal |
lower_case | Non-terminal |
Program Structure
program = { declaration } EOF ;
declaration = type_def
| import_stmt
| export_stmt
| module_decl
| test_block
| extern_decl
| trait_def
| trait_impl
| binding ;
Type Definitions
type_def = "type" TYPE_NAME { TYPE_VAR } "=" type_body ;
type_body = constructor { "|" constructor }
| "|" constructor { "|" constructor } ;
constructor = CONSTRUCTOR_NAME [ constructor_args ] ;
constructor_args = "(" field_list ")"
| type { type } ;
field_list = field { "," field } ;
field = IDENT { attribute } ;
Attributes
Attributes provide metadata annotations. Kit recognizes a small set of compiler-known and linter-known attributes, and preserves module, type, and constructor-field attributes for package tooling and external tools.
attribute = "@" attr_name [ "(" attr_args ")" ] ;
attr_name = IDENT { "-" IDENT } ; (* supports hyphenated names *)
attr_args = attr_arg { "," attr_arg } ;
attr_arg = STRING | INT | FLOAT | "true" | "false" | IDENT ;
Recognized attributes include:
- Compiler-known attributes:
@pre,@post,@defer-required, and@skip - Linter-known attributes:
@deprecated - Package/tool metadata attributes such as module-level
@check(...)and@feature(...) - Unknown module, type, and constructor-field attributes with kebab-case names, which are preserved for downstream tools
Unknown binding and test attributes are not accepted as executable annotations. Attributes must not hide authority; capabilities and effects remain explicit values.
Import/Export
import_stmt = "import" import_path [ "as" IDENT ]
| "import" import_path "." "{" import_list "}" ;
import_path = STRING | module_path ;
module_path = IDENT { "." IDENT } ;
import_list = import_item { "," import_item } ;
import_item = IDENT [ "as" IDENT ] ;
export_stmt = "export" ( binding | type_def ) ;
module_decl = "module" module_path ;
Test Blocks
test_block = [ "@skip" [ STRING ] ] "test" [ test_name ] "=>" indented_block ;
test_name = STRING | KEYWORD_LITERAL ;
The @skip attribute marks a test to be skipped during execution, with an optional reason string.
Match Macros
Kit provides three concise pattern matching macros for common operations:
is_expr = expression "is" pattern ;
as_expr = expression "as" pattern "->" expression "else" expression ;
guard_stmt = "guard" pattern "=" expression "else" expression ;
is- Check if a value matches a pattern, returnsBoolas- Extract value if pattern matches, return alternative otherwiseguard- Bind pattern variables if match succeeds, return early otherwise
These macros provide concise alternatives to verbose match expressions.
Compile-Time Macros
Kit supports user-defined compile-time macros for code generation:
macro_def = "macro" IDENT { IDENT } "=" quasiquote ;
quasiquote = "`" "(" expression ")" ;
unquote = "$" IDENT ;
Macros are expanded at compile time. The quasiquote syntax (`) creates a code
template, and $param unquotes (substitutes) parameter values into the template.
# Define a macro
macro double x = `($x + $x)
# Usage - expands to (5 + 5) at compile time
result = double(5) # 10
Extern Declarations
extern_decl = extern_c_decl | extern_zig_decl ;
extern_c_decl = "extern-c" IDENT "(" [ param_list ] ")" "->" type
"from" STRING "link" STRING ;
extern_zig_decl = "extern-zig" IDENT "(" [ param_list ] ")" "->" type
"from" STRING "link" STRING ;
param_list = param { "," param } ;
param = IDENT ":" type ;
extern-cdeclares a C function bindingextern-zigdeclares a Zig function binding
Traits
trait_def = "trait" IDENT TYPE_VAR [ "requires" constraint_list ]
trait_body ;
trait_body = { trait_method } ;
trait_method = IDENT ":" type
| IDENT "=" expression ;
trait_impl = "extend" type "with" IDENT [ "where" constraint_list ]
impl_body ;
impl_body = { method_def } ;
method_def = IDENT "=" expression ;
constraint_list = constraint { "," constraint } ;
constraint = IDENT TYPE_VAR ;
Bindings
binding = { contract } pattern [ ":" type ] "=" expression
| qualified_binding ;
qualified_binding = TYPE_NAME "." IDENT "=" expression ;
contract = "@pre" "(" expression [ "," STRING ] ")"
| "@post" "(" expression [ "," STRING ] ")" ;
Contracts (@pre and @post) provide precondition and postcondition
assertions for functions. The optional string argument provides a custom error message.
Destructuring in Bindings
Bindings support pattern matching with tuple and record patterns, allowing you to extract values directly:
# Tuple destructuring
(x, y) = (10, 20)
(a, b, c) = get-triple()
# Record destructuring with shorthand (field name becomes binding name)
{name, age} = person
# Record destructuring with explicit rename
{name: user-name, age: user-age} = person
# Partial record destructuring (extract only some fields)
{x, z} = {x: 1, y: 2, z: 3}
# Nested destructuring
{outer: {inner}} = nested-record
((a, b), (c, d)) = nested-tuples
# Wildcards to ignore values
{keep, ignore: _} = data
(_, important, _) = triple
Expressions
Precedence (lowest to highest)
expression = using_expr ;
using_expr = "using" IDENT { "," IDENT } "=>" using_body
| defer_expr ;
using_body = expression
| indented_block ;
defer_expr = "defer" expression | null_coalesce ;
null_coalesce = pipe_expr { "??" pipe_expr } ;
pipe_expr = send_expr { ( "|>" | "|>>" ) send_expr } ;
send_expr = or_expr [ "<-" or_expr ] ;
or_expr = and_expr { ( "||" | "or" ) and_expr } ;
and_expr = is_as_expr { ( "&&" | "and" ) is_as_expr } ;
is_as_expr = equality [ "is" pattern ]
| equality "as" pattern "->" expression "else" expression
| equality ;
equality = comparison { ( "==" | "!=" ) comparison } ;
comparison = concat { ( "<" | "<=" | ">" | ">=" ) concat } ;
concat = cons { "++" cons } ;
cons = term { ( "::" | "@" ) term } ;
term = factor { ( "+" | "-" ) factor } ;
factor = unary { ( "*" | "/" | "%" ) unary } ;
unary = ( "-" | "!" ) unary | call_expr ;
call_expr = primary { call_suffix } ;
call_suffix = "(" [ arg_list ] ")" (* C-style call *)
| "." IDENT (* field access *)
| "?!" (* error propagation *)
| prefix_arg ; (* Haskell-style *)
prefix_arg = primary ; (* excluding operators *)
arg_list = expression { "," expression } ;
Scoped Evidence
using evidence => body introduces direct identifier evidence for
guarded operations in body. It is compile-time-only capability evidence:
it does not pass hidden arguments, create ambient authority, import names, or run cleanup.
auth: EntropyAuth = entropy-auth RootAuth
value = using auth =>
Math.random
value2 = using root, auth =>
Random.float auth
Evidence must be one or more direct identifiers whose types are known capability
types. Evidence is borrowed, so a using scope does not count as
consumption for @linear or @relevant values.
Primary Expressions
primary = INT | FLOAT | STRING
| "true" | "false"
| "()" (* unit *)
| KEYWORD_LITERAL (* :symbol *)
| IDENT
| field_accessor
| lambda | if_expr | match_expr | for_expr | sql_expr
| group_or_tuple | list_literal | set_literal
| record_literal | interpolated_string | heredoc ;
field_accessor = "." IDENT ; (* shorthand for fn(x) => x.IDENT *)
lambda = "fn" [ "(" [ param_patterns ] ")" ] "=>" lambda_body ;
param_patterns = param_pattern { "," param_pattern } ;
param_pattern = [ "~" ] pattern ; (* ~ marks lazy-accepting parameter *)
lambda_body = expression | indented_block ;
if_expr = "if" expression "then" if_branch "else" if_branch ;
if_branch = expression | indented_block ;
match_expr = "match" expression match_arms ;
match_arms = { "|" match_arm } ;
match_arm = or_pattern [ guard ] "->" expression
| or_pattern [ guard ] "=>" expression ;
or_pattern = pattern { "|" pattern } ;
guard = "when" expression | "if" expression ;
for_expr = "for" pattern "in" expression "=>" for_body ;
for_body = expression | indented_block ;
sql_expr = "sql" [ expression [ "as" type_name ] ] "{" sql_content "}" ;
sql_content = { SQL_CHAR | "${" expression [ ":" FORMAT_SPEC ] "}" } ;
group_or_tuple = "(" expression [ "," expression { "," expression } ] ")" ;
list_literal = "[" [ list_elements ] "]" ;
list_elements = expression { "," expression } [ "|" expression ] ;
set_literal = "#{" [ expression { "," expression } ] "}" ;
record_literal = "{" [ record_fields ] "}" ;
record_fields = record_field { "," record_field } ;
record_field = "..." expression (* spread *)
| IDENT ":" expression
| IDENT ; (* shorthand *)
interpolated_string = STRING_START { string_part } STRING_END ;
string_part = STRING_CONTENT
| "${" expression [ ":" FORMAT_SPEC ] "}" ;
heredoc = "<<~" IDENT NEWLINE heredoc_content IDENT ;
heredoc_content = { HEREDOC_CHAR | "${" expression [ ":" FORMAT_SPEC ] "}" } ;
Note: Zero-arity functions can omit parentheses:
fn => expr is equivalent to fn() => expr.
Heredocs use squiggly syntax (<<~) which strips common leading indentation.
They support the same string interpolation as regular strings.
Range construction currently uses range or List.range with half-open
semantics. Range literal syntax such as 0..<10, inclusive syntax such as
0..=9, and list comprehensions such as [f x for x in xs] are reserved
for a future syntax pass and are not part of the grammar today.
Patterns
pattern = "_" (* wildcard *)
| INT | FLOAT | STRING
| "true" | "false"
| KEYWORD_LITERAL
| IDENT (* binding or constructor *)
| constructor_pattern
| tuple_pattern | list_pattern | record_pattern ;
constructor_pattern = CONSTRUCTOR_NAME [ constructor_payload ] ;
constructor_payload = "(" pattern { "," pattern } ")"
| "{" record_pat_fields [ ".." ] "}"
| pattern { pattern } ; (* space-separated *)
tuple_pattern = "(" pattern "," pattern { "," pattern } ")" ;
list_pattern = "[" [ list_pat_elements ] "]" ;
list_pat_elements = ".." pattern (* rest at start *)
| pattern { "," pattern } [ ( "|" | ".." ) pattern ] ;
record_pattern = "{" [ record_pat_fields ] "}" ;
record_pat_fields = record_pat_field { "," record_pat_field } ;
record_pat_field = IDENT [ ":" pattern ] ;
Types
type = simple_type [ "->" type ] ; (* function type *)
simple_type = "Int" | "Float" | "String" | "Bool" | "Unit"
| TYPE_NAME [ type_args ]
| tuple_type | record_type | refinement_type
| "(" type ")" ;
type_args = simple_type { simple_type } ;
tuple_type = "(" type "," type { "," type } ")" ;
record_type = "{" [ record_type_fields ] [ "..." ] "}" ;
record_type_fields = record_type_field { "," record_type_field } ;
record_type_field = IDENT ":" type ;
refinement_type = "{" IDENT ":" type "|" expression "}" ;
Refinement Types
Refinement types constrain values beyond what the base type expresses.
The syntax follows Liquid Haskell: {binding: BaseType | predicate}.
Refinement Construction
refinement_construct = TYPE_NAME "!" "(" expression ")" (* assert *)
| TYPE_NAME "?" "(" expression ")" (* Option *)
| TYPE_NAME "?!" "(" expression ")" ; (* Result *)
T!(expr)- Assert construction; panics if predicate failsT?(expr)- Safe construction; returnsOption TT?!(expr)- Safe construction; returnsResult T String
Row Polymorphism
Kit supports opt-in row polymorphism for record types using ... syntax.
By default, record types are closed (require exact fields). Adding ...
makes them open (accept extra fields).
- Closed record (default):
{name: String}- requires exactly the specified fields - Open record (opt-in):
{name: String, ...}- requires specified fields, allows extras - Empty open record:
{...}- accepts any record
Indented Blocks
indented_block = NEWLINE INDENT { block_item } DEDENT ;
block_item = binding | expression ;
Indentation is significant. An indented block begins when the indentation level increases and ends when it returns to the previous level.
Lexical Elements
Identifiers
IDENT = LOWER_IDENT | UPPER_IDENT ;
LOWER_IDENT = ( LETTER_LOWER | "_" ) { LETTER | DIGIT | "_" | "-" } ;
UPPER_IDENT = LETTER_UPPER { LETTER | DIGIT | "_" | "-" } ;
LETTER = LETTER_LOWER | LETTER_UPPER ;
LETTER_LOWER = "a" ... "z" ;
LETTER_UPPER = "A" ... "Z" ;
DIGIT = "0" ... "9" ;
Numeric Literals
INT = DECIMAL_INT | HEX_INT | BINARY_INT | OCTAL_INT ;
DECIMAL_INT = [ "-" ] DIGIT { DIGIT | "_" } [ INT_SUFFIX ] ;
HEX_INT = [ "-" ] "0" ( "x" | "X" ) HEX_DIGIT { HEX_DIGIT | "_" } ;
BINARY_INT = [ "-" ] "0" ( "b" | "B" ) BINARY_DIGIT { BINARY_DIGIT | "_" } ;
OCTAL_INT = [ "-" ] "0" ( "o" | "O" ) OCTAL_DIGIT { OCTAL_DIGIT | "_" } ;
FLOAT = [ "-" ] DIGIT { DIGIT } "." DIGIT { DIGIT } [ FLOAT_SUFFIX ] ;
HEX_DIGIT = DIGIT | "a" ... "f" | "A" ... "F" ;
BINARY_DIGIT = "0" | "1" ;
OCTAL_DIGIT = "0" ... "7" ;
INT_SUFFIX = "L" (* int64 *)
| "u" | "ul" (* uint32 *)
| "I" ; (* bigint *)
FLOAT_SUFFIX = "f" | "F" (* float32 *)
| "m" | "M" ; (* decimal *)
Examples: 255, 0xFF, 0b1111_1111, 0o377, 1_000_000
String Literals
STRING = '"' { STRING_CHAR | ESCAPE_SEQ } '"' ;
STRING_CHAR = any character except '"', '\', or '${' ;
ESCAPE_SEQ = "\" ( "n" | "r" | "t" | "\" | '"' | "$" ) ;
Keyword Literals
KEYWORD_LITERAL = ":" IDENT ;
Comments
COMMENT = "#" { any character except newline } NEWLINE ;
DOC_COMMENT = "##" { any character except newline } NEWLINE ;
Doc comments (##) are associated with the following declaration
and can be used for documentation generation.
Operator Precedence Table
| Precedence | Operators | Associativity | Description |
|---|---|---|---|
| 1 (lowest) | using defer | prefix | Evidence/deferred execution |
| 2 | ?? | right | Null coalesce |
| 3 | |> |>> | left | Pipe operators |
| 4 | <- | - | Actor send |
| 5 | || or | left | Logical OR |
| 6 | && and | left | Logical AND |
| 7 | is as | - | Pattern test/extract |
| 8 | == != | left | Equality |
| 9 | < <= > >= | left | Comparison |
| 10 | ++ | left | String concatenation |
| 11 | :: @ | left | List cons/append |
| 12 | + - | left | Addition/subtraction |
| 13 | * / % | left | Multiplication/etc |
| 14 | - ! | right | Unary negation/not |
| 15 | calls, ., ?! | left | Application/access |
| 16 (high) | literals | - | Primary expressions |
Unicode Operator Aliases
Kit supports Unicode characters as aliases for common operators:
| Unicode | ASCII | Description |
|---|---|---|
× | * | Multiplication |
÷ | / | Division |
⇒ | => | Fat arrow |
→ | -> | Right arrow |
− | - | Minus sign |
≠ | != | Not equal |
≤ | <= | Less than or equal |
≥ | >= | Greater than or equal |
… | ... | Spread operator |
𝑓 | fn | Lambda keyword |
Reserved Words
as defer else export extend extern-c extern-zig
false fn for from guard if import
in is link macro match module requires
sql test then trait true type using when
where with
Pre-registered Constructors
The following constructors are pre-registered and can be used in patterns without a type definition:
# Result and Option types
Ok Err Some None
# Backoff strategies
NoBackoff Constant Linear Exponential
# Jitter strategies
NoJitter FullJitter EqualJitter ProportionalJitter
Destructuring
Kit supports destructuring in bindings, allowing you to extract values from tuples and records directly into variables.
Tuple Destructuring
# Basic tuple destructuring
point = (10, 20)
(x, y) = point
println "x = ${x}, y = ${y}"
# Multiple values
(a, b, c) = (1, 2, 3)
# Nested tuple destructuring
((x1, y1), (x2, y2)) = ((0, 0), (10, 10))
# Ignore elements with wildcard
(first, _, third) = (1, 2, 3)
Record Destructuring
# Shorthand syntax - field name becomes variable name
person = {name: "Alice", age: 30}
{name, age} = person
println "Name: ${name}, Age: ${age}"
# Explicit rename - bind to different variable names
{name: user-name, age: user-age} = person
println "User: ${user-name}"
# Partial destructuring - only extract needed fields
config = {host: "localhost", port: 8080, debug: true}
{port} = config
# Mixed shorthand and rename
{name, age: years-old} = person
# Nested record destructuring
user = {info: {name: "Bob", email: "bob@example.com"}}
{info: {name, email}} = user
# Ignore fields with wildcard
{keep, ignore: _} = {keep: 42, ignore: 999}
Destructuring in Functions
# Tuple destructuring in function parameters
add-points = fn((x1, y1), (x2, y2)) =>
(x1 + x2, y1 + y2)
# Record destructuring from function return
make-point = fn(x, y) => {x: x, y: y}
{x, y} = make-point 5 10
# In for loops
for (key, value) in Map.entries(m) =>
println "${key}: ${value}"
Examples
Type Definition
type Option a = Some a | None
type Result a e =
| Ok a
| Err e
type Point = Point(x, y)
Type Definition with Attributes
# Single-line with attributes
type User = User(id @primary-key @auto-increment, name, email @unique)
# Multi-line with attributes
type Product = Product(
id @primary-key,
price @default(0.0),
category_id @fkey(Category, id),
tags @json("tags", omitempty)
)
Lambda Functions
add = fn(a, b) => a + b
# Zero-arity function (parentheses optional)
get-time = fn => now()
process = fn(items) =>
result = items |> map square
result
# Lazy-accepting parameter (~ sigil)
maybe-compute = fn(~value, should-force?) =>
if should-force? then force(value) else 0
# Linearity annotation on a parameter
consume-once = fn(resource @linear) => close resource
# Trailing lambda as the final call argument
r = twice fn(x) => x + 1
# Zero-arg trailing lambda with indented body
listen app 4000 fn =>
println "started"
# Multi-line parenthesized lambda argument
s = twice (fn(x) =>
doubled = x * 2
doubled)
For Expressions
# Simple for loop
for x in [1, 2, 3] => print(x)
# With tuple destructuring
for (k, v) in map-entries(m) => print("${k}: ${v}")
# Multi-line body
for item in items =>
processed = transform(item)
save(processed)
SQL Expressions
# Simple SQL
sql { SELECT * FROM users }
# With connection
sql db { SELECT * FROM users WHERE id = ${user_id} }
# With type casting
sql db as User { SELECT * FROM users WHERE id = 1 }
Heredocs
html = <<~HTML
<div>
<h1>${title}</h1>
<p>${content}</p>
</div>
HTML
Contracts
@pre(n >= 0, "n must be non-negative")
@post(result >= 0)
factorial = fn(n) =>
if n <= 1 then 1 else n * factorial(n - 1)
Test Blocks
test "addition works" =>
assert-eq!(1 + 1, 2)
@skip "not implemented yet"
test "future feature" =>
todo()
Pattern Matching
match value
| Some x -> x
| None -> default
match point
| Point(0, 0) -> "origin"
| Point(x, 0) -> "on x-axis"
| Point(0, y) -> "on y-axis"
| Point(x, y) -> "at (${x}, ${y})"
List Patterns
# Head and tail
match list
| [head | tail] -> process(head, tail)
| [] -> empty()
# Rest pattern
match args
| [first, second, ..rest] -> handle(first, second, rest)
| [..all] -> handle-all(all)
Numeric Literals
decimal = 1_000_000
hex = 0xFF
binary = 0b1010_1010
octal = 0o755
int64 = 100L
bigint = 999999999999999999I
float32 = 3.14f
Pipe Operators
data |> transform |> output # thread-first
list |>> map double |>> filter # thread-last
Traits
trait Eq a
eq: (a, a) -> Bool
ne = fn(a, b) => !(eq a b)
extend Int with Eq
eq = fn(a, b) => a == b
Match Macros
# is: Check if value matches pattern (returns Bool)
opt = Some 42
if opt is Some _ then "has value" else "empty"
# as: Extract value with default
name = user as Some u -> u.name else "anonymous"
# guard: Bind or return early
process = fn(opt) =>
guard Some value = opt else Err "was None"
guard true = value > 0 else Err "not positive"
Ok (value * 2)
Compile-Time Macros
# Define macros with quasiquote syntax
macro double x = `($x + $x)
macro square x = `($x * $x)
macro unless cond then-val else-val =
`(if not($cond) then $then-val else $else-val)
# Use macros (expanded at compile time)
result = double(5) # expands to (5 + 5)
squared = square(4) # expands to (4 * 4)
msg = unless((x > 0), "negative", "positive")
Row Polymorphism
# Closed record (default) - requires exact fields
greet-closed = fn(person: {name: String}) =>
"Hello, " ++ person.name
greet-closed({name: "Kit"}) # OK
# greet-closed({name: "Kit", age: 1}) # Type error: extra field
# Open record (opt-in with ...) - accepts extra fields
greet-open = fn(person: {name: String, ...}) =>
"Hello, " ++ person.name
greet-open({name: "Kit"}) # OK
greet-open({name: "Kit", age: 1}) # OK - extra field allowed
# Empty open record - accepts any record
log-record = fn(r: {...}) => println(r)
Field Accessor Shorthand
# .field is shorthand for fn(x) => x.field
users = [{name: "Alice", age: 30}, {name: "Bob", age: 25}]
# These are equivalent:
names1 = users |> map(fn(u) => u.name)
names2 = users |> map .name
# Useful in pipelines
users |> filter(fn(u) => u.age > 21) |> map .name
Notes
- Indentation: Kit uses significant whitespace for multi-line constructs. Indented blocks are used in lambda bodies, if/else branches, match arms, and test blocks.
- Kebab-case: Identifiers can contain hyphens (
my-function), which is idiomatic for function names. - Constructor Recognition: Uppercase identifiers are treated as constructors in patterns.
- Negative Numbers in Application: When using Haskell-style function
application, negative numbers must be parenthesized:
func (-1)notfunc -1. - Attributes: Attributes provide metadata using the
@prefix. Compiler-known attributes include@pre,@post,@defer-required, and@skip. Unknown module, type, and constructor-field attributes are preserved for package tooling, while unknown binding and test attributes are rejected. - For Expressions: For loops (
for x in list => body) desugar toeach. They support pattern destructuring in the binding. - SQL Expressions: SQL blocks support string interpolation with
${expr}. The optional connection expression allows specifying a database connection. - Heredocs: Squiggly heredocs (
<<~DELIM) strip common leading indentation from the content, making embedded code and markup easier to read. - Contracts:
@preand@postattributes on bindings define preconditions and postconditions that are checked at runtime. - Zero-arity Functions: Lambda expressions without parameters can omit
the parentheses:
fn => expris equivalent tofn() => expr. - Trailing Lambdas: A bare
fnlambda starting on the same line as a call becomes the final application argument:twice fn(x) => x + 1. The lambda body is greedy (it may continue as an indented block on the following lines). Parenthesized lambda arguments may also span multiple lines, closing on the last body line or on their own line. - Lazy-accepting Parameters: The
~sigil before a parameter indicates it accepts lazy values without auto-forcing. Regular parameters auto-force anyLazyorMemovalues passed to them. Example:fn(~value) => .... - Match Macros: The
is,as, andguardmacros provide concise pattern matching.isreturns a boolean,asextracts with a default, andguardbinds or returns early. These expand to equivalent match expressions. - Compile-Time Macros: User-defined macros use quasiquote syntax:
`(expr)creates a code template, and$paramsubstitutes parameters. Macros are expanded at compile time before type checking. - Row Polymorphism: Record types are closed by default (require exact fields).
Add
...to make them open:{name: String, ...}accepts records with at least anamefield. Use{...}to accept any record. - Linearity Annotations: Bindings, patterns, and function parameters can use
@linear,@affine,@relevant,@unrestricted, or borrowed callback annotations such asInt @borrow. - Field Accessor Shorthand: The syntax
.fieldis shorthand forfn(x) => x.field, useful in pipelines:users |> map .name. - Error Propagation: The postfix
?!operator unwrapsOk/Somevalues or returns early from the enclosing function withErr/None. It is parsed at call-expression level (same precedence as.and function application). - Actor Send: The
<-operator sends a message to an actor:actor <- messagedesugars toActor.send actor message. Its precedence is between pipe operators and logical OR. - Keyword Aliases:
orcan be used in place of||, andandcan be used in place of&&.