← Catalog

RFC-001AcceptedLanguage

Language Specification

GitHub

Depends: RFC-000

Blocks: RFC-002RFC-003RFC-004RFC-006RFC-007RFC-009RFC-010

1. Abstract

This RFC defines the surface syntax and core language semantics of Aura: lexical structure, grammar sketch, declarations (classes, interfaces, functions, packages), expressions and statements, modules/visibility, and the user-visible shape of nullability, errors, async, and attributes.

Deep type rules live in RFC-002; memory, tasks, and races in RFC-003; attributes/metadata detail in RFC-009; macros in RFC-010. Syntax here is pseudo-Aura until a frozen grammar file lands in-repo.

2. Motivation

2.1 Problem statement

Without a single surface specification, compiler, formatter, and docs diverge. Aura needs a statement-oriented, Java-like surface that still supports modern expression forms, null-safe types, Result, exceptions, and task-based async—without requiring ownership annotations.

2.2 Why now

Wave-1 foundation: every later RFC quotes keywords, declaration forms, and module rules from here.

2.3 Success metrics

  • Unambiguous programs can be written and type-checked (with RFC-002).
  • Grammar parseable by a standard approach (hand-written recursive descent first; tree-sitter grammar as companion).
  • A short “language tour” compiles once the compiler MVP exists.

3. Goals

  • Formal-ish specification: lexical rules + grammar sketch + semantics prose.
  • Deterministic evaluation order where it matters (argument order, field init).
  • Stable surface for compiler, formatter, and macros.
  • Align with RFC-000 locked decisions (classes, T?, exceptions + Result, tasks).

4. Non-goals

  • Full formal operational semantics (optional later appendix).
  • Stdlib API (→ RFC-007).
  • Detailed type inference algorithm (→ RFC-002).
  • Memory/concurrency operational model (→ RFC-003).
  • Complete EBNF frozen file in this draft (tracked as deliverable).

5. Prior art & alternatives

ApproachNotesInfluence
Java / KotlinClasses, methods, packages, nullability (Kotlin)Primary surface
C#async, attributesSecondary
Gopackage model simplicityPackages/tasks spirit only
TypeScriptDX ergonomicsNot structural typing default
RustExpression-oriented blocksSelective (if/match as expr)

6. Design

6.0 MVP surface (compiler C0–C1)

This subsection freezes the subset that the first compiler milestones must implement. Full v1 surface remains in later subsections; anything not listed here was out of scope for C0/C1 unless an RFC amend expands this table.

Toolchain progress (2026-07-29): the Rust compiler has shipped through C22 plus S2 production-toolchain work (see roadmap): classes with inheritance/virtual dispatch, interfaces and Array<Interface>, generics+bounds, struct/enum/match, exceptions, packages/import/aura.lock, Array/for/?./?:, scoped nonowning ref T with lexical escape checks, borrow-safe Array field returns, GC mark/sweep, std.io (console/file/argv/stdin/exit and Result wrappers), assert, generic collections, deterministic snapshot and epoch-invalidated live collection iterators, first-class funs/lambdas with value captures and mutable var class/Array/Fun capture MVPs, String tools, direct-origin dependency consumption, formatter/diagnostic/test-report tooling, and the bounded async/task surface with task frames, deterministic execution, channels, and covered await/control-flow lowering. HashMap.entry(key) supplies key-based handles, while liveEntry(key) supplies invalidation-checked live entry views. General async lowering, richer captures/outcomes, macros, mutable/nullable/nested borrows remain deferred. §6.0 remains the historical C0/C1 freeze; later milestones are tracked in the roadmap, not by rewriting this freeze.

Milestones (aligned with RFC-004 §11 and this RFC §11):

MilestoneCompiler goalLanguage surface
C0aura check — lex, parse, basic name checks§6.0.1–6.0.3
C1aura build — native hello (interim C backend + cc; LLVM later)C0 + print/runtime hooks
C1bSimple classes + methods+ §6.0.4 (implemented)
Post-C1 (C2–C22+)Generics, packages, Array, GC, lambdas, process I/O, …Shipped slices in roadmap; bounded async is partial and macros remain deferred

6.0.1 Lexical (C0)

ItemMVP rule
EncodingUTF-8
Comments// line; /* */ non-nesting block
IdentifiersASCII [A-Za-z_][A-Za-z0-9_]* (Unicode XID later)
Keywords (hard)package, import, as, class, fun, val, var, if, else, while, return, true, false, null, pub
Soft / deferred (C0 freeze)Originally deferred: match, async, await, spawn, interface, enum, struct, for, in, … — many are now implemented post-C1 (see roadmap); general async lowering and macros remain partial/deferred
Literalsdecimal Int, true/false, "..." strings (no interpolation in C0), null
Operators+ - * / %, == != < <= > >=, && || !, =, ., ( ) { } , : ?
Semicolonsoptional; newline/brace terminated

6.0.2 Grammar (C0)

ebnf
File        = "package" Path Decl*
Path        = Ident ("." Ident)*
Decl        = FunDecl
FunDecl     = "fun" Ident "(" Params? ")" (":" Type)? Block
Params      = Param ("," Param)*
Param       = Ident ":" Type
Type        = Ident "?"?                 (* nominal + optional nullability *)
Block       = "{" Stmt* "}"
Stmt        = VarStmt | IfStmt | WhileStmt | ReturnStmt | ExprStmt
VarStmt     = ("val" | "var") Ident (":" Type)? "=" Expr
IfStmt      = "if" "(" Expr ")" Block ("else" Block)?
WhileStmt   = "while" "(" Expr ")" Block
ReturnStmt  = "return" Expr?
ExprStmt    = Expr
Expr        = ... Pratt: call, member, unary, binary, primary
Primary     = Ident | Literal | "(" Expr ")"
  • One package declaration per file; no multi-file packages in C0.
  • Top-level functions only (no class until C1b).
  • Entry: fun main() (optional : Unit / no return type).

6.0.3 Semantics (C0 check / C1 run)

TopicMVP
TypesInt, Bool, String, Unit; user types deferred; T? parse + local flow later
Name resolutionSingle file: funs + locals; calls to unknown names → error
Control flowif / while / return
Runtime (C1)Linked stub: println(String) (or intrinsic) + process exit
Concurrency / GCDeclared by RFC-000/003; not implemented in C0/C1 (single-threaded stub OK)

6.0.4 Classes (C1b only)

aura
class Greeter(val name: String) {
  fun greet(): String {
    return "Hello, " // + name in later string ops
  }
}
  • Final classes only; primary constructor fields; instance methods; this optional later.
  • No inheritance, interfaces, generics, or companion in C1b.

6.0.5 Explicitly deferred (post-C1)

Originally deferred at C0/C1 freeze (many now shipped — see roadmap C2–C10j):

ItemToolchain status (2026-07-20)
Generics, interfaces, struct/enum/matchImplemented (C2–C3)
Exceptions / throw/try/catch/finallyImplemented (C3c/C3g)
Multi-file packages, import, path deps, aura.lockImplemented (C3e–C3p, C4j); lock schema v0 (C8k)
for ranges / for-in (Array, String bytes), break/continueImplemented (C3h–C3l, C3w)
Builtin Array<T>, null ?: / ?., if exprImplemented (C3j+, C4m/C4s/C4t)
String + / interpolation ${} (idents), type/const, isImplemented (C9d–C9i)
Lambdas / fun types (T) -> UImplemented (C10c–j + C12k–m + C20c–e captures); escaping live Array ownership still deferred
async/await/spawnC22 surface and checks landed; lowering is partial — bounded await CFGs, typed outcomes, channels, and covered captures ship; arbitrary shapes remain deferred
Attributes/macros, full Unicode identifiersStill deferred
Iterable protocolImplemented (C8d); generic class implements C9a
Locked direct-origin dependency consumptionImplemented (Git/HTTPS/SSH origins, immutable revision/checksum pins, semver tag selection, cache extraction; proxy serving remains deferred)

Corpus: programs under corpus/ exercise the implemented surface above; see corpus/README.md.

6.1 Overview

Aura is a multi-paradigm language with a class-based OOP core:

AxisChoice
CompilationAhead-of-time to native (LLVM)
ExecutionLinked runtime: GC + task scheduler
OrientationStatements primary; blocks / if / match may yield values
Nominal typesClasses, interfaces, enums; value struct (distinct from class)
ModulesPackage-based; files belong to a package
Entryfun main(...) in a designated root (convention: package main or [[bin]])
code
Source file
  → package decl
  → imports
  → top-level decls (class, interface, enum, struct, fun, const, type alias)

6.2 Lexical structure

Character set & encoding: UTF-8 source. Identifiers: Unicode XID rules (exact table TBD); ASCII letters/digits/_ required for keywords.

Keywords (reserved):
package, import, as, class, interface, enum, struct, fun, val, var, const, if, else, match, case, while, for, in, break, continue, return, throw, try, catch, finally, async, await, spawn, is, as (cast form disambiguated), null, true, false, this, super, new (optional sugar—prefer constructor call Type(...)), pub, protected, private, static, abstract, override, open, final, where, type, unsafe.

Soft keywords (contextual): get, set, field, init, from — reserved only in specific positions.

Literals:

KindExamples
Int0, 42, 0xFF, 1_000
Float3.14, 1e-3
Booltrue, false
String"hello", interpolation "hi ${name}"
Raw string#"path\no-escape"# (delimiter style TBD)
Char'a'
Nullnull (only for T?)

Comments: // line, /* */ block (non-nesting v1). Doc comments: /// or /** */ attached to following decl.

Semicolons: Statements do not require trailing ; (newline/brace terminated). ; optional separator for multiple statements on one line. No significant indentation.

Operators: Arithmetic, comparison, logical, ?. safe call, ?: null-coalesce, !! unchecked non-null assert (discouraged; lintable), -> in function types, => in match/lambda if needed.

6.3 Grammar sketch

Source of truth will be grammar/aura.ebnf (future). Sketch:

ebnf
File        = PackageDecl Import* Decl*
PackageDecl = "package" Path
Import      = "import" Path ("as" Ident)?
Decl        = ClassDecl | InterfaceDecl | EnumDecl | StructDecl
            | FunDecl | AsyncFunDecl | ConstDecl | TypeAlias | Attribute* Decl

ClassDecl   = Attr* Modifiers "class" Ident TypeParams?
              (":" TypeList)? ClassBody
FunDecl     = Attr* Modifiers "fun" Ident TypeParams? "(" Params? ")"
              (":" Type)? BlockOrExprBody
AsyncFunDecl = Attr* Modifiers "async" "fun" Ident TypeParams? "(" Params? ")"
              (":" Type)? BlockOrExprBody
AsyncExpr   = AwaitExpr | SpawnExpr | JoinExpr | CancelExpr
AwaitExpr   = "await" Expr
SpawnExpr   = "spawn" (Block | Expr)
JoinExpr    = "join" "(" Expr ")"
CancelExpr  = "cancel" "(" Expr ")"

Parser strategy: hand-written recursive descent with Pratt parser for expressions (RFC-004).

6.3.1 C22 async/task syntax contract

C22 reserves async, await, spawn, join, and cancel as keywords. async may prefix only a function declaration; await may occur only inside an async fun; join and cancel take exactly one task-handle expression. spawn { ... } is the canonical form. A bare spawn expression is accepted only when its operand is a callable task body, as defined by RFC-003.

Valid examples:

aura
async fun load(id: Int): User {
  return await fetchUser(id)
}

fun main() {
  val task = spawn { load(42) }
  val user = join(task)
  cancel(task)
}

Invalid forms:

aura
fun main() { await load(42) }       // await outside async fun
async class Worker {}               // async cannot modify a class
fun main() { join() }               // missing handle
fun main() { cancel(a, b) }         // too many handles

Span behavior is part of the frontend contract. A valid node spans its whole operator and operand, or the complete async fun declaration. Invalid placement is anchored to the operator keyword; an invalid async modifier is anchored to async. Missing operands or delimiters use the zero-width insertion point where the token was expected. Extra operands are anchored to the first unexpected token, and nested operand errors retain their inner span. Recovery must not widen an operator span to the enclosing block.

6.4 Types (surface syntax)

Semantic detail → RFC-002. Surface only:

CategorySyntax examples
PrimitivesInt, Long, Float, Double, Bool, Char, Byte, String, Unit, Nothing
Class / interfaceUser, List<String>
NullableString?, User?
ArraysArray<T> (not T[])
Tuples(Int, String)
Function types(Int, String) -> Bool
ResultResult<T, E> (stdlib / prelude)
Type aliastype UserId = Long
Genericsclass Box<T>, constraints via where / bound syntax

No raw nullable without ?. null has type Nothing? / bottom-nullable and only assigns to T?.

6.5 Declarations

Variables

FormMeaning
val x: T = ...Immutable binding
var x: T = ...Mutable binding
const X: T = ...Compile-time constant (top-level / class)

Type annotation optional when inferable (RFC-002).

Functions

aura
fun add(a: Int, b: Int): Int {
  return a + b
}

// Expression body
fun double(x: Int): Int = x * 2
  • Named functions; default args yes (resolved at call site).
  • Rest/vararg: yes as vararg xs: T.
  • Overloads: yes, resolved per RFC-002.
  • Generics on functions: yes.

Classes

aura
open class Animal(val name: String) {
  open fun speak(): String { return "..." }
}

class Dog(name: String, val breed: String) : Animal(name) {
  override fun speak(): String { return "woof" }
}
  • Primary constructor in header (Kotlin-like) plus optional secondary constructor blocks.
  • Single class inheritance; multiple interfaces.
  • open / final / abstract: classes are final by default; mark open to allow subclassing.
  • Nested companion object for type-level / “static” members (primary model; no free-floating Java static as the main surface).

Struct (value type — included in v1)

aura
struct Point(val x: Int, val y: Int)

Semantics (copy vs ref) → RFC-002/003. Surface: no identity, no subclassing.

Enums

aura
enum Color { Red, Green, Blue }

enum Shape {
  case Circle(radius: Double)
  case Rect(w: Double, h: Double)
}

Interfaces

aura
interface Drawable {
  fun draw()
  fun bounds(): Rect { /* default method optional */ }
}

Arrays of interfaces (C20h)

Array<I> is supported using Aura's tagged-interface representation. Every element has a stable runtime representation and carries the ownership and GC metadata needed for copying, dropping, dispatch, and marking. The following historical alternatives were considered before the tagged representation was implemented:

  • A fat element stores an interface data pointer plus a method-table (or type) pointer inline. This gives direct dispatch and avoids one allocation per element, but makes Array copies, GC scanning, and element drops depend on interface-specific metadata. It also requires a stable ABI for nullable and value-backed implementations.
  • A boxed element stores one GC-managed pointer per element. The box owns the concrete payload and carries its interface dispatch metadata. Array layout stays pointer-sized and simple, but every insertion may allocate, iteration adds indirection, and drop/GC must coordinate box finalization without double-releasing a payload.

The shipped implementation uses neither alternative; see the corpus/iface/array_interface.aura dispatch and GC fixture. The spike remains useful as historical rationale for the ABI decision. See the C20h layout spike for the comparison and follow-up conditions.

Modules, packages, visibility

ModifierMeaning
(default)Package-private
pubPublic API
protectedClass + subclasses
privateEnclosing class / file (file-private for top-level private)

Imports

aura
import std.io
import std.collections.List as StdList

Re-exports: pub import ... (optional v1).

6.6 Expressions & statements

Statements: declarations, assignments, loops, return/break/continue/throw, expression statements.

Expression forms: literals, calls, field/method access, operators, lambdas, if/match expressions, blocks { ... } (value = last expression if used as expr).

Control flow:

aura
if (cond) { ... } else { ... }

val msg = if (ok) "yes" else "no"

match (shape) {
  case Circle(r) => ...
  case Rect(w, h) => ...
}

while (cond) { ... }
for (item in iterable) { ... }
for (i in 0..n) { ... }  // range syntax TBD

Pattern matching: destructure enums, sealed hierarchies, basics on tuples; exhaustiveness → RFC-002.

Casts:

  • Smart cast after is check (flow-sensitive).
  • Explicit: x as Type (checked), x as? TypeType?, x as! Type unsafe assert.

Error handling surface:

aura
try {
  risky()
} catch (e: IoError) {
  ...
} finally {
  ...
}

throw AppError("boom")

fun readConfig(): Result<Config, ConfigError> { ... }

match readConfig() {
  case Ok(c) => use(c)
  case Err(e) => log(e)
}
  • Exceptions: unchecked hierarchy rooted at Error / Throwable (names TBD).
  • Result: for expected failures; not forced by the type system on all functions.

Async surface:

aura
async fun fetch(url: String): Bytes { ... }

fun main() {
  spawn { worker() }
  val body = await fetch("https://example.com")
}

Semantics → RFC-003. Keywords only here.

Null surface:

aura
val s: String? = maybe()
val n = s?.length()
val t = s ?: "default"
val u = s!!   // assert non-null; panic/throw if null

6.7 Functions & calling convention (language level)

  • Parameters are references to GC objects for class types; by-value for primitives and structs (details RFC-003).
  • this receiver for instance methods; super for parent.
  • First-class functions and lambdas: (x: Int) => x + 1 or block body (x: Int) => { ... }.
  • Closures: capture val by value/shared immutability; capture var through shared mutable storage. A mutable Array<T> capture is an owned, reference-counted shared cell: the outer binding and every escaping closure retain the same cell, mutations are visible to all aliases, and the payload is released after the last cell owner. It is not a borrowed Array view, so owner movement is cell retain/release and there is no live-view invalidation. C20c–e cover class, Array, and nested Fun lowering; scoped ref T remains a separate non-escaping borrow feature. Exact lowering is tracked in RFC-004/toolchain notes.
  • Generators/iterators: Iterable / Iterator protocols in stdlib; yield not required v1.

6.8 Modules & compilation units

  • Package = namespace + visibility boundary. Directory layout convention: src/<package/path>/File.aura (exact layout RFC-008).
  • Compilation unit = package graph of a target (bin/lib).
  • Cyclic packages: forbidden across packages; cycles within a package allowed with restrictions (forward refs).
  • Root manifest: aura.toml (RFC-005) lists packages/targets.

6.9 Attributes / decorators / annotations

aura
@test
@derive(Debug, Equals)
@inline
class Foo { }

Retention and meaning → RFC-009. Macro attributes → RFC-010.

6.10 Unsafe / low-level surface

aura
unsafe {
  // raw pointers, unchecked casts, FFI helpers
}
  • unsafe blocks/functions required for raw memory and most FFI.
  • Safe Aura must not observe undefined behavior from other safe Aura (GC + type system); FFI breaks the seal (RFC-003/006).

6.11 Examples

aura
package hello

import std.io.println

class Greeter(val name: String) {
  fun greet(whom: String?): String {
    val target = whom ?: "world"
    return "Hello, ${target}, from ${this.name}"
  }
}

fun main() {
  val g = Greeter("Aura")
  println(g.greet(null))
}
aura
package demo.http

interface Handler {
  fun handle(req: Request): Result<Response, HttpError>
}

class EchoHandler : Handler {
  override fun handle(req: Request): Result<Response, HttpError> {
    return Result.ok(Response(200, req.body))
  }
}

6.12 Error model / edge cases

TopicRule
Ambiguous parsesGrammar prefers longest match; reserved keywords never identifiers
Soft keywordsAllowed as identifiers outside their context
Evaluation orderLeft-to-right for arguments and operands unless specified
OverflowFixed-width ints: checked in dev (trap/throw on overflow); explicit wrapping operators (&+ style or wrappingAdd) always available; release may omit checks for speed when using wrapping ops—default arithmetic policy documented with toolchain profiles
String interpOnly expressions; no statement injection

6.13 Compatibility & migration

  • Language version / edition field in aura.toml.
  • Deprecation: @deprecated attribute + compiler warnings.
  • No promise of source compatibility with Java/Kotlin; “Java-like” is conceptual.

7. Open questions

#QuestionOptionsOwnerStatus
1Classes final by default?yesLangResolved — final by default
2Companion vs static keywordcompanionLangResolved — companion
3Array syntax Array<T> vs T[]Array<T>LangResolved
4Lambda syntax(…) =>LangResolved
5Checked exceptionsnoLangResolved — unchecked only
6Integer overflow policychecked dev + wrapping opsLangResolved (release elision details open with profiles)
7Exact keyword set / soft-keyword list§6.0.1 hard keywords v0LangResolved for C0 — expand as surface grows
8Range syntax 0..ninclusive/exclusiveLangResolved — exclusive a..b (C3h), inclusive a..=b (C3l)

8. Rationale & trade-offs

Java-like classes maximize familiarity for service engineers. Kotlin-inspired nullability and primary constructors reduce boilerplate without adopting full Kotlin semantics. Statement orientation keeps control flow obvious; expression-if/match covers the useful 20% of expression-oriented style. Rejecting ownership keeps the surface aligned with GC (RFC-000). Result + exceptions avoid the false dichotomy of “all errors are exceptions” vs “all errors are values.”

9. Unresolved / future work

  • Full formal grammar file in repo
  • Tree-sitter grammar
  • Spec test suite (syntax corpus)
  • Exact prelude names (Unit, Nothing, Result)

10. Security & safety considerations

  • No eval of source strings in language core.
  • String interpolation does not execute code beyond expression evaluation.
  • unsafe and !! are lint targets; security-sensitive codebases can forbid them.
  • Macro expansion hygiene (RFC-010) prevents accidental identifier capture.

11. Implementation plan (optional)

PhaseScopeExit criteria
G0Lexer + subset grammarParse hello
G1Core decls + control flowSpec examples parse
G2Full surface v1Grammar frozen for MVP

12. References

  • RFC-000, RFC-002, RFC-003, RFC-009, RFC-010
  • Kotlin language reference (nullability, primary constructors)
  • Java Language Specification (classes, overloading concepts)
  • Go language spec (packages—contrast only)

Changelog

DateAuthorChange
2026-07-16Status → Accepted — Review: MVP surface frozen, open Qs resolved, C0–C4t implement against it
2026-07-16Sync §6.0 notes with C4t toolchain; resolve range .. / ..= (Q8)
2026-07-15Add §6.0 MVP surface for compiler C0–C1; resolve keywords v0
2026-07-15Initial skeleton
2026-07-15Solid draft: Java-like surface, nullability, Result, tasks keywords
2026-07-15Lock lean surface decisions (final, companion, Array, lambda, …)