# Aura full documentation > Build-derived from the maintained Aura README, website, Manual, Learn track, and tutorials. ## Source: README.md # Aura Aura is a compiled, statically typed programming language designed for reliable software. It combines Python-inspired readability with deterministic ownership, structured concurrency, typed failure, native executables, and no garbage collector. Install the Aura 0.3 technical preview on Linux x64, macOS x64, or macOS arm64: ```bash curl -fsSL https://johnolafenwa.github.io/Aura/install.sh | sh ``` Already have Aura installed? Upgrade the compiler and bundled runtime with: ```bash aura upgrade ``` Detailed setup is available for [macOS](https://johnolafenwa.github.io/Aura/install/macos), [Linux](https://johnolafenwa.github.io/Aura/install/linux), and [Windows through WSL 2](https://johnolafenwa.github.io/Aura/install/windows-wsl). The [VS Code guide](https://johnolafenwa.github.io/Aura/install/vscode) covers Marketplace, Open VSX, VSIX, and WSL remote installation. Aura 0.3 focuses on applications for agents, ML infrastructure, and the control planes around models. The compiler checks types, access, mutation, ownership transfer, resource cleanup, and task boundaries before execution. Aura's long-term goal is to become a general-purpose systems language capable of building every type of software: applications, services, databases, language runtimes, embedded software, operating systems, and device drivers. Future releases will expand Aura with the freestanding targets, low-level memory facilities, hardware interfaces, and platform controls required for that scope. Read [Why Aura](docs/positioning.md) for the project direction. The canonical implemented contract begins with the normative [Language Specification](docs/manual/language-specification.md), [complete grammar](docs/manual/grammar.md), and Manual. Current measurements and the optimization roadmap live in the [Performance chapter](docs/manual/performance.md). Supported hosts and pinned tools are listed in [SUPPORTED_PLATFORMS.md](SUPPORTED_PLATFORMS.md). ## Monorepo layout This repository is intended to evolve as a monorepo for the Aura language and its associated tools. - `crates/` - Rust compiler, runtime, and CLI tooling - `tools/` - editor integrations and other developer tools - `package.json` - npm workspace manifest for repo-managed tools - `examples/` - categorized sample Aura programs - `tutorials/` - Markdown tutorials covering the implemented language subset - `docs/` - VitePress book, language proposal, and supporting documentation - `architecture_docs/` - implementation-focused architecture and component deep dives for the current Aura system - `work/` - persistent task board and implementation notes Compiler build and direct binary usage are documented in [crates/aura/README.md](crates/aura/README.md). Compiler library testing notes live in [crates/aura-compiler/README.md](crates/aura-compiler/README.md). The categorized example library is documented in [examples/README.md](examples/README.md). The tutorial track lives in [tutorials/README.md](tutorials/README.md). The VitePress book lives in [docs/index.md](docs/index.md) and includes the guided Learn track plus the normative language and API reference. The repo testing strategy is documented in [docs/testing_strategy.md](docs/testing_strategy.md). The forward-looking ML systems roadmap lives in [docs/ml_systems_support_plan.md](docs/ml_systems_support_plan.md). The implementation architecture guide lives in [architecture_docs/README.md](architecture_docs/README.md). Current editor tooling: - `tools/vscode-aura` - VS Code extension for Aura syntax highlighting and LSP client integration - `tools/aura-language-server` - Aura Language Server Protocol implementation Current compiler workflow: - `cargo run -p aura -- check examples/classes/point_distance.au` - parse and type check a program - `cargo run -p aura -- run examples/control_flow/while_break_continue.au` - execute the MIR runtime - `cargo run -p aura -- run examples/classes/methods.au` - execute user-defined instance and associated methods - `cargo run -p aura -- run examples/control_flow/match_literals.au` - execute statement-form `match` over literal `bool`, integer, and `str` cases - `cargo run -p aura -- run examples/control_flow/conditional_expressions.au` - execute lazy Python-style conditional expressions with one unified result type - `cargo run -p aura -- run examples/enums/result_match.au` - execute enum construction plus exhaustive `match` - `cargo run -p aura -- run examples/enums/result_option.au` - execute built-in `Result[T, E]` and `Option[T]` values with exhaustive `match` - `cargo run -p aura -- run examples/error_handling/try_result.au` - execute `try expr` over `Result[T, E]` - `cargo run -p aura -- run examples/generics/box_and_wrapper.au` - execute user-defined generic classes, enums, and functions - `cargo run -p aura -- run examples/basics/default_arguments.au` - execute default parameter values on ordinary functions - `cargo run -p aura -- run examples/basics/closures.au` - execute contextually typed expression closures with by-value captures - `cargo run -p aura -- run examples/basics/len_and_str.au` - execute `int64` member lengths, `len(value) == value.len()`, Unicode-scalar str length versus UTF-8 byte length, and `str(value)` - `cargo run -p aura -- run examples/collections/list_basics.au` - execute list literals, `list[T]` methods, and indexed element access - `cargo run -p aura -- run examples/collections/list_polish.au` - execute negative list indexing, cast-free length-driven indexing, non-copy cloned reads, mutable list iteration, `insert(...)`, `swap(...)`, `reverse()`, `clear()`, richer list methods, and list equality - `cargo run -p aura -- run examples/collections/list_algorithms.au` - execute stable natural/key sorting plus eager, source-retaining `list.map(...)` and `list.filter(...)` - `cargo run -p aura -- run examples/collections/comprehensions.au` - execute eager owned list, set, and dictionary comprehensions with filters and nested outer-major clauses - `cargo run -p aura -- run examples/collections/slices.au` - execute owned list and Unicode-scalar str slices, omitted and negative endpoints, and source/result independence - `cargo run -p aura -- run examples/collections/dict_basics.au` - execute `dict[K, V]` literals, tuple-valued `items()`, `update(...)`, and the maintained dictionary method surface - `cargo run -p aura -- run examples/collections/set_basics.au` - execute `set[T]` literals, shared set iteration, membership, and the maintained set method surface - `cargo run -p aura -- run examples/basics/pass_keyword.au` - execute the `pass` no-op statement in intentionally empty blocks - `cargo run -p aura -- run examples/basics/assertions.au` - execute introspectable comparisons and membership with lazy messages and source-located failures - `cargo run -p aura -- run examples/basics/multiline_expressions.au` - continue calls, signatures, grouping, indexes, and collection literals across physical lines while a source delimiter remains open - `cargo run -p aura -- run examples/basics/tuples.au` - execute fixed-arity tuple values, recursive unpacking, tuple-pattern matching, copy-only constant indexing, and same-type recursive `==` and `!=` that retain both operands; tuple ordering remains rejected - `cargo run -p aura -- run examples/modules/simple_import.au` - execute local file modules with `import`, `from ... import ...`, and `public` module boundaries - `cargo run -p aura -- run examples/packages/local_path_dependencies/app/src/main.au` - execute a manifest-rooted package with `src/`, a sibling path dependency, and package-local helpers - `cargo run -p aura -- run examples/packages/workspace/app/src/main.au` - execute a workspace member package with a workspace-root `Aura.toml` - `cargo run -p aura -- run --backend mir examples/packages/ffi_getpid/src/main.au` - execute an explicitly authorized FFI v0 package that calls the process-global C `getpid` symbol on a Unix-family host; use `--backend direct` for the maintained backend-parity path - `cargo run -p aura -- run examples/traits/greeter.au` - execute trait declarations, `impl Trait for Type`, and bounded generic calls - `cargo run -p aura -- run examples/traits/generic_trait_impl.au` - execute generic trait declarations and generic impl headers - `cargo run -p aura -- run examples/traits/generic_trait_bounds.au` - execute specialized generic trait bounds such as `T: Mapper[int32]` - `cargo run -p aura -- run examples/traits/operator_traits.au` - execute operator traits through `+` and unary `-` - `cargo run -p aura -- run examples/traits/ordering_traits.au` - execute ordering traits through `<`, `<=`, `>`, and `>=` - `cargo run -p aura -- run examples/traits/specialized_trait_dispatch.au` - execute bounded dispatch across specialized generic trait impls - `cargo run -p aura -- run examples/basics/numbers.au` - execute floor division, divisor-sign remainder, and rounded integer `.to_float()` conversion - `cargo run -p aura -- run examples/concurrency/duration_arithmetic.au` - execute signed Duration constructors, runtime scaling, floor division, comparison, and floating unit conversion - `cargo run -p aura -- run examples/randomness/deterministic_rng.au` - execute the stable seeded random stream, unbiased integer mapping, and deterministic in-place shuffle - `cargo run -p aura -- run examples/json/dynamic_values.au` - parse recursive JSON into typed variants, inspect an exact accessor, and emit deterministic compact and pretty JSON - `cargo run -p aura -- run examples/bytes/codecs_and_hashing.au` - convert strict UTF-8 text, encode canonical hex/base64, and compute raw SHA-256 bytes without consuming the inputs - `cargo run -p aura -- run examples/numbers/numeric_casts.au` - execute explicit numeric casts with `expr as Type` - `cargo run -p aura -- run examples/numbers/numeric_builtins.au` - execute the maintained builtin numeric helper surface `abs(...)`, `min(...)`, `max(...)`, `sqrt(...)`, and `float64.sqrt()` - `cargo run -p aura -- run examples/numbers/numeric_arrays.au` - execute contiguous row-major `Array[T]` construction, multidimensional indexing, mutation, first-axis owned slicing, mapping, reductions, exact-shape/scalar kernels, and explicit wrapping/saturating integer modes - `cargo run -p aura -- run examples/strings/string_methods.au` - execute single-quoted strings, `int64` Unicode-scalar `len()`, `int64` UTF-8 `byte_len()`, and the maintained `str` method surface including `split`, `replace`, case conversion, and prefix/suffix stripping - `cargo run -p aura -- run examples/strings/string_parsing_and_formatting.au` - execute parsing builtins, scalar/boolean `.to_string()`, and `str.join(...)` - `cargo run -p aura -- run examples/strings/literal_forms_and_formatting.au` - execute exact multiline/raw strings and statically checked Unicode-aware f-string formatting - `cargo run -p aura -- run examples/io/read_text_file.au` - execute the maintained builtin file I/O surface through `fs.exists(...)`, `fs.read_to_string(...)`, and `io.write(...)` - `cargo run -p aura -- run examples/io/bytes_file_io.au` - execute binary file helpers plus `fs.File.read_bytes()` / `write_bytes(...)` - `cargo run -p aura -- run examples/io/process_run.au` - execute shell-free subprocess helpers through `process.run(..., group=true)`, UTF-8/raw captured stdio, and `process.Completed.check()` - `cargo run -p aura -- run examples/io/process_pipes.au` - execute `process.start(..., group=true)`, interactive `process.Pipe` I/O, and `process.Child.wait_ok(...)` - `cargo run -p aura -- run examples/io/process_supervisor.au` - execute `process.supervisor()`, named child restart policies, backoff, and group-aware supervised shutdown - `cargo run -p aura -- run examples/io/tcp_echo.au` - execute the maintained builtin TCP networking surface through `net.listen(...)`, `net.connect(...)`, and `TcpStream` / `TcpListener` - `cargo run -p aura -- run examples/io/tcp_bytes.au` - execute timeout-aware TCP byte I/O through `connect_timeout(...)`, `read_exact(...)`, `read_bytes(...)`, and `write_bytes(...)` - `cargo run -p aura -- run examples/io/udp_echo.au` - execute UDP binding, datagram receive/send, and `net.UdpDatagram` - `cargo run -p aura -- run examples/io/http_roundtrip.au` - execute the maintained HTTP listener/request helpers on the shared evented runtime scheduler - `cargo run -p aura -- run examples/io/websocket_roundtrip.au` - execute timeout-aware WebSocket listener/connect helpers on the nonblocking socket runtime - `cargo run -p aura -- run examples/io/unix_tls_roundtrip.au` - execute the Unix-socket and TLS surface on Unix hosts using bundled PEM assets - `cargo run -p aura -- run examples/agents/control_plane_foundations.au` - execute typed JSON/TOML metadata, path helpers, counters, and structured log/trace events - `cargo run -p aura -- run examples/agents/retry_with_backoff.au` - execute `control.retry(...)` through eventual success and exact last-error exhaustion with a zero-delay backoff - `cargo run -p aura -- run examples/agents/retrying_network_worker.au` - execute an application-level HTTP retry worker that retries only `503`, uses deterministic seed-42 jitter with exponential `Duration` backoff, applies explicit deadlines, and closes its task/listener/response resources through structured scopes; the maintained product regression pins the same seven-request trace on the MIR and forced-direct backends - `cargo run -p aura -- run app.au -- --model small` - pass program arguments exposed through `sys.args()` - `cargo run -p aura -- new agent-app` - create a manifest-rooted project without overwriting existing files - `cargo run -p aura -- fmt --check agent-app` - verify Aura source normalization - `cargo run -p aura -- test agent-app/tests` - run package-aware Aura test programs - `cargo run -p aura -- run examples/resources/with_resource.au` - execute deterministic scoped cleanup with `with` - `cargo run -p aura -- run examples/concurrency/task_group_start.au` - execute the maintained queue/task concurrency surface - `cargo run -p aura -- run examples/concurrency/bounded_queue.au` - execute bounded queues with `Queue[T](capacity=...)` across the pinned-worker scheduler; task captures, task results, and Queue payloads use compiler-derived structural `Transfer`, while non-repeatable task results have one consuming observation right - `cargo run -p aura -- run examples/concurrency/sleep_builtin.au` - execute `sleep(duration)` delays in the MIR-backed runtime path - `cargo run -p aura -- run examples/concurrency/yield_now.au` - execute bounded CPU-work chunks with explicit cooperative scheduling points; ordinary loop backedges also receive automatic checks - `cargo run -p aura -- run examples/concurrency/typed_select.au` - execute typed heterogeneous Queue, Task, and relative-deadline selection with deterministic source indexes on both maintained backends - `cargo run -p aura -- build -o ./target/aura-point examples/point.au` - compile a standalone native binary through the default auto backend - `cargo run -p aura -- build --backend direct -o ./target/aura-direct ./examples/basic_addition.au` - force the direct native backend for the full currently implemented Aura language surface - `cargo run -p aura -- ast examples/classes/point_distance.au` - print the parsed syntax tree - `cargo run -p aura -- ast-json examples/classes/point_distance.au` - print the parsed syntax tree as machine-readable JSON - `cargo run -p aura -- mir examples/control_flow/while_break_continue.au` - print the lowered MIR for the checked program - `cargo run -p aura -- analyze examples/classes/point_distance.au` - print machine-readable compiler analysis for diagnostics, symbols, hover, and definition - `cargo run -p aura -- check --format json examples/classes/point_distance.au` - emit the stable, schema-versioned compiler diagnostic document used by CLI tooling; `run` and `build` accept the same format, including typed `call_frames` and `task_ancestry` arrays for runtime failures - `cargo run -p aura -- help` - print CLI usage and exit successfully - `cargo run -p aura -- --version` - print the preview channel and the 12-hex-digit source commit, so preview builds cannot be confused with a future final release - `cat examples/modules/simple_import.au | cargo run -p aura -- analyze --stdin "$(pwd)/examples/modules/simple_import.au"` - analyze an editor-style buffer while still resolving local imports relative to the supplied path - `cargo run -p aura -- complete --line 5 --character 11 --trigger . examples/point.au` - print machine-readable completion items at a source position - `--line` and `--character` are zero-based - member completion expects the cursor positioned just after `.` - the CLI tolerates the common incomplete-editor state where the current buffer contains one or more dangling member accesses such as `counter.` or `helpers.math.`, including when they appear at EOF - stdin-backed completion resolves local imported modules relative to the supplied file path, including imported trait methods - `cat examples/modules/simple_import.au | cargo run -p aura -- run --stdin "$(pwd)/examples/modules/simple_import.au"` - execute an editor-style buffer while still resolving local imports relative to the supplied path - `cargo run -p aura -- check examples/packages/local_path_dependencies/app/src/main.au` - type-check a package entrypoint using `Aura.toml`, `src/`, local path dependencies, and git dependencies - `cargo run -p aura -- deps update` - refresh all branch/tag/default-main git dependencies for the current package or workspace and rewrite `Aura.lock` - `cargo run -p aura -- deps update util` - refresh only the `util` git dependency for the current package or workspace - `cat examples/modules/simple_import.au | cargo run -p aura -- check --stdin "$(pwd)/examples/modules/simple_import.au"` - type-check an editor-style buffer while still resolving local imports relative to the supplied path - `npm run coverage:compiler` - measure current Rust compiler coverage with `cargo-llvm-cov`, using the full Rust workspace test surface while reporting compiler production files - `npm run coverage:compiler:check` - enforce the current compiler coverage floor - `npm run test:rust` - run the Rust test suite at Cargo/libtest's default parallelism with a larger test stack so deep parser-limit regressions do not overflow the host test harness - `npm run coverage:lsp:check` - enforce the current LSP coverage floor - `npm run check:format` - verify Rust formatting - `npm run check:clippy` - run the Rust lint gate with warnings treated as errors - `npm run check:audit` - run npm and RustSec vulnerability gates - `npm run check:hygiene` - reject whitespace errors, tracked generated executables, editor metadata, and scratch evaluation corpora - `npm run docs:dev` - start the VitePress Aura book locally - `npm run docs:build` - build the VitePress Aura book - `npm run check:reference` - verify that the normative language-reference pages, navigation, and core conformance statements stay present - `npm run ci` - run the current repo-quality gate locally, including formatting, the default-parallel main Rust suite, the separately serialized backend-parity gate, Node tests, coverage floors, docs build, audit, Clippy warnings-as-errors, and diff hygiene; the instrumented compiler-coverage wrapper also retains its own narrow single-threaded test setting for stable coverage collection GitHub Actions: - `.github/workflows/ci.yml` - runs the repo gate on Linux and macOS - `.github/workflows/docs.yml` - builds the VitePress book and deploys it to GitHub Pages from `main` - `.github/workflows/release.yml` - builds Linux and macOS CLI archives, packages the VS Code extension and docs, and publishes them for pushed `v*` tags; manual runs are build-only by default and require an explicit publish opt-in Current `build` status: - `aura build` accepts `--backend auto|direct` - `aura build` defaults to `auto` - `auto` first tries the direct native backend and may fall back to a standalone embedded-MIR launcher when direct emission is unavailable - `direct` performs true low-level native code generation for the full currently implemented Aura language surface - a built binary runs without reparsing source or compiling a generated Rust runner - a built binary runs without the original `.au` source files - built binaries render runtime failures with file, line, caret, typed Aura call-chain, and child-task ancestry context from embedded source - release archives include the Aura native runtime and do not require Cargo or a source checkout; `aura build` still requires a host C compiler - manifest-aware commands resolve local path dependencies, git dependencies, and workspace members when the entry file lives under a package with `Aura.toml` - git dependencies support `git = "..."` with `rev`, `tag`, or `branch`, and default to `branch = "main"` when no selector is provided - the current package-system milestone writes a local `Aura.lock` at the package root or workspace root, pinning resolved git revisions and recording relative paths for local path dependencies - both maintained execution paths cover the builtin `io`, `fs`, `net`, and `process` module surface for scheduler-aware text/binary file I/O, reactor-driven TCP/UDP/WebSocket/Unix/TLS socket I/O, higher-level HTTP helpers, shell-free subprocess execution with captured pipes, and supervised child processes with restart policy support Current `run` status: - `aura run` defaults to the MIR runtime for the current implemented Aura surface; `--backend direct` requires native execution and `--backend auto` prefers it with visible fallback - queues, task groups, wait helpers, `try`, `with`, scheduler-aware file I/O, the maintained reactor-driven socket networking surface, and the shell-free `process` module run through the same MIR-backed public execution path - task bodies use pinned scheduler workers: the default worker count is the available parallelism reported by the host, and the provisional `AURA_WORKERS=` override selects an explicit count; each child keeps its spawn-time worker for its lifetime, with no stack migration or work stealing - scheduler waits use persistent descriptor registrations, a timer heap, and direct Queue, task-completion, and blocking-pool notifications, including cross-worker wakes; an idle worker blocks until local work, a notification, an event, or a deadline without a periodic tick - blocking host operations use a separate lazy process-wide pool: `AURA_BLOCKING_WORKERS=` selects an exact worker count without clamping, while the absent default derives `2..=8` workers from host parallelism with fallback `4`; `AURA_BLOCKING_QUEUE_CAPACITY=` optionally bounds accepted pending jobs only, with FIFO scheduler-aware admission and an unbounded compatibility default - invalid blocking-pool settings fail with `AU4006` before user code under MIR, direct, and standalone execution; the first runtime preflight records one immutable process-lifetime configuration, but starts no blocking-pool worker threads - first blocking submission creates the complete worker set, which production reuses until process exit without an Aura shutdown/join surface; pre-acceptance timeout/cancellation prevents submission, and accepted host work remains non-retractable, so a queue bound cannot guarantee progress for unrelated blocking I/O while every worker is stuck - `select(source, ...)` provides typed heterogeneous Queue, Task, and relative-deadline waiting with cancellation-first/lowest-index arbitration, one winner, and loser cleanup; it is an ordinary builtin, not statement syntax - `yield_now()` yields only to runnable work on the current task's worker; task scheduling, cross-worker completion, and program-output order are deliberately unspecified - Queue and Task handles are the maintained cross-worker communication surface; compiler-derived `Transfer` keeps all other task captures and results share-nothing, and cancellation and diagnostic context remain isolated per task - MIR execution and direct native execution use the same pinned-worker contract and execute Aura tasks across multiple cores; preemption, work stealing, detached tasks, and worker introspection are unavailable, while parallel speedup depends on the workload - every loop backedge includes a compiler-inserted cooperative scheduling check; native concurrent code amortizes it with function-local fuel, while sequential native code elides checks when no sibling task can exist - the maintained execution architecture uses the MIR runtime for `run` and native direct codegen for `build` ## VS Code install The extension has two server pieces: - the JavaScript LSP transport bundled inside the VSIX - the compiler-owned semantic service started as `aura lsp` Build both pieces before installing from this checkout. In particular, do not reuse an existing `tools/vscode-aura/aura-language.vsix` after the language server changes; that ignored local artifact may contain an older server bundle. Install the current server and extension: 1. Run `npm ci` from the repo root. 2. Build the repo-local compiler service with `cargo build -p aura`. To install the actual `aura lsp` server binary on your `PATH` for every Aura workspace, also run: ```bash cargo install --path crates/aura --locked --force ``` This installs the `aura` executable (normally under `~/.cargo/bin`); the extension starts its `aura lsp` subcommand automatically. There is no second semantic-server executable to install. 3. Build and package the current JavaScript LSP transport with `npm run package:extension`. 4. Install that newly generated package: ```bash code --install-extension tools/vscode-aura/aura-language.vsix --force ``` If the `code` shell command is unavailable, use **Extensions → … → Install from VSIX…** and select the same file. 5. Run **Developer: Reload Window** in VS Code, then reopen an `.au` file. The language server keeps one persistent `aura lsp` compiler service for diagnostics, document symbols, hover, go-to-definition, and completions. In this repository it discovers `target/debug/aura` or `target/release/aura`. For an Aura workspace elsewhere, put `aura` on `PATH` or launch VS Code with `AURA_LSP_AURA_PATH` set to the absolute compiler path: ```bash AURA_LSP_AURA_PATH="/absolute/path/to/aura" code /path/to/aura-project ``` The LSP bridge preserves the compiler's stable `AU####` codes, related spans, notes, help, edits, typed call frames, and task ancestry. If the compiler process is unavailable, a small lexical recovery layer provides basic declarations and top-level completions. Full extension install and packaging steps are documented in [tools/vscode-aura/INSTALL.md](tools/vscode-aura/INSTALL.md). ## Source: docs/index.md ## Why Aura Aura brings familiar source code to a compiled, statically typed language. Its indentation-based syntax is easy to read, while compiler checks cover types, ownership, mutation, exhaustive matching, failure handling, and task boundaries. Programs build as native executables with deterministic cleanup and no garbage collector. The current preview is designed for reliable applications, agent runtimes, ML infrastructure, evaluation workers, and control-plane services. Aura is a technical preview. The language and APIs may still change before a stable release. ## At A Glance | | Python | Rust | Aura | | --- | --- | --- | --- | | Syntax | Indentation-based and concise | Explicit and low-level | **Python-inspired and indentation-based** | | Types | Dynamic, with optional hints | Static | **Static, with inference** | | Execution | Interpreter and virtual machine | Native executables | **Native executables** | | Memory | Reference counting and garbage collection | Ownership | **Ownership, no garbage collector** | | Failure | Exceptions | `Result`, `Option`, panics | **Typed `Result`, `Option`, outcome enums** | | Concurrency | Threads and async functions | Threads and async ecosystem | **Structured task groups across multiple cores** | | Current focus | General-purpose applications and scripting | Systems and application software | **Reliable applications, agents, and ML infrastructure** | ## A First Program ```aura def scale(values: mut list[int64], factor: int64): for value in mut values: value *= factor def total(values: list[int64]) -> int64: mut sum = 0 for value in values: sum += value return sum mut scores = [10, 20, 30] scale(scores, 3) print(f"scores: {scores}") print(f"total: {total(scores)}") ``` The syntax is familiar and every operation remains statically checked. Each signature states what it does to its arguments: `scale` asks for `mut` access and changes the list in place, while `total` only reads it. The compiler enforces both contracts. ## Built For Agents And ML Infrastructure Serving models and running agents involves sockets, subprocesses, queues, deadlines, and retries. Aura's rules make the failure modes visible: - **Values have owners.** Bare parameters share, `mut` mutates, `own` transfers. Cleanup follows the owning scope. - **Failure has a type.** Recoverable failures return `Result`, `Option`, or an outcome enum, handled where they happen. - **Concurrency has a scope.** A `TaskGroup` owns its children: leaving the scope joins them, cancels stragglers, and loses nothing. - **The standard library speaks infrastructure.** Files, processes, TCP, HTTP, WebSockets, TLS, queues, retries, and supervisors follow the same ownership and failure rules as everything else. ## Long-Term Direction Aura's long-term goal is to become a general-purpose systems language capable of building every type of software. The intended scope spans applications, services, databases, language runtimes, embedded software, operating systems, and device drivers. Aura 0.3 establishes the foundation through static typing, deterministic ownership, native compilation, structured concurrency, typed failure, packages, and integrated tooling. Later releases will extend that foundation with freestanding compilation, low-level memory access, hardware interfaces, portable layout controls, cross-compilation, and specialized runtime profiles. ## Start Building Install Aura, then run a file or build a native executable: ```bash aura run program.au aura build -o ./program program.au ``` [Learn Aura](/learn/) starts with runnable scripts and works up to tasks, typed failures, and I/O. [The Manual](/manual/) is the normative reference: grammar, ownership rules, execution model, APIs, diagnostics, and limits. ## Source: docs/positioning.md # Why Aura Aura 0.3.2 is a technical preview of a compiled, statically typed programming language for reliable software. It combines Python-inspired syntax, deterministic ownership, structured concurrency, typed failure, and native executables. The current language focuses on agents, ML infrastructure, evaluation workers, network services, and the control-plane software around models. These workloads benefit from readable source code, explicit resource lifetimes, structured tasks, and failures represented directly in the type system. Three commitments shape the language: - **Deterministic ownership.** Bare access is shared, `mut` is exclusive mutation, `own` transfers a value, and the owning scope defines cleanup. - **Structured concurrency.** A `TaskGroup` owns every child started in its scope, and scope exit accounts for all of them. - **Typed failure.** Files, subprocesses, sockets, HTTP, retries, and supervisors surface recoverable failure through `Result`, `Option`, and focused outcome enums. Ownership governs access, transfer, and cleanup — not scheduling. Concurrent completion, cross-worker scheduling, and output order stay unspecified. The [Ownership](/manual/ownership-and-borrowing), [Concurrency](/manual/concurrency), and [Control-Plane Modules](/manual/control-plane) chapters define the exact contracts. ## Built For Agents And ML Infrastructure Modern ML products extend far beyond model code. They include inference gateways, queue workers, evaluation pipelines, tool executors, subprocess supervisors, network clients, storage paths, timeouts, and retries. Agent runtimes add long-lived task trees and repeated interaction with unreliable external systems. Aura gives this work one coherent contract. Static types describe the data. Ownership describes resource lifetime. Structured concurrency accounts for child tasks. Typed outcomes keep operational failure visible. Native compilation produces deployable executables with no garbage collector. The result is a focused language for the reliable control plane around models: - model-serving and inference coordination; - agent runtimes and tool execution; - concurrent data and evaluation workers; - process, queue, and network supervision; and - infrastructure where cleanup and failure handling are correctness requirements. ## Long-Term Direction Aura's long-term goal is to become a general-purpose systems language capable of building every type of software. The intended scope spans applications, services, databases, language runtimes, embedded software, operating systems, and device drivers. Aura 0.3 establishes the foundation through static typing, deterministic ownership, native compilation, structured concurrency, typed failure, packages, and integrated tooling. Later releases will extend that foundation with freestanding compilation, low-level memory access, hardware interfaces, portable layout controls, cross-compilation, and specialized runtime profiles. ## Familiar Source, Strong Guarantees Python demonstrated the value of readable, low-friction source code. Rust demonstrated that ownership can prevent broad classes of memory and concurrency errors before execution. Aura combines those lessons in an indentation-based language with a smaller control-plane focus. The familiar surface lowers the cost of reading and writing compiled software. The compiler requires exact types at public boundaries, validates ownership and task transfer, checks exhaustive matches, and carries source context into runtime diagnostics. Familiar syntax preserves the complete language contract. ## Adjacent Languages These projects overlap with parts of Aura's motivation. The distinctions below describe focus and language contracts. Primary sources were checked on 31 July 2026. ### Mojo Mojo is a close neighbor in Python-shaped compiled syntax and compiler-tracked ownership. Its roadmap centers [high-performance kernels on CPUs, GPUs, and ASICs, with Python interoperability](https://mojolang.org/docs/roadmap/). Its ownership documentation gives each value one owner and defines [default immutable, `mut`, and `var` argument conventions](https://mojolang.org/docs/manual/values/ownership/). Aura 0.3 centers the application control plane around models and agents: scoped child tasks, transferable messages, typed I/O and process failures, timeouts, retries, and supervision. GPU programming, heterogeneous hardware, and Python-library interoperability remain future surface areas. ### Nim Nim is a broad, established systems language. The Nim project describes it as a [statically typed compiled language combining ideas from Python, Ada, and Modula](https://nim-lang.org/), with native executables and deterministic, customizable memory management. Its documentation recommends [ORC for newly written code](https://nim-lang.org/2.2.6/mm.html), and its [typed-threads documentation](https://nim-lang.org/docs/typedthreads.html) covers shared-heap and explicit thread facilities. Aura's distinction is its smaller integrated contract around call-boundary capabilities, structurally transferable task values, `TaskGroup` scope, and typed control-plane APIs. Nim provides greater metaprogramming, backend, ecosystem, and portability breadth today. ### Go Go is a production reference point for simple concurrent service software. Its documentation defines lightweight [goroutines and channel communication](https://go.dev/doc/effective_go#concurrency), treats [errors as values](https://go.dev/blog/errors-are-values), and explains that the standard toolchain ships a [tracing garbage collector](https://go.dev/doc/gc-guide). Aura uses a different lifetime contract. Non-copy task captures and messages must satisfy structural `Transfer`, resources have owners, and a `TaskGroup` accounts for the children it starts. This provides scoped task lifetime and deterministic resource cleanup without a garbage collector. ### Free-threaded Python 3.13+ CPython has supported an optional free-threaded build since Python 3.13. The official guide says that this build can run threads in parallel with the GIL disabled, while some extension modules may [re-enable the GIL](https://docs.python.org/3/howto/free-threading-python.html). The language retains its shared-object, dynamically typed programming model. Aura checks ownership and task-transfer boundaries before execution and gives common control-plane failures concrete result types. Python offers much greater runtime flexibility and ecosystem compatibility. Aura offers a compiled, ownership-based contract for teams that want those decisions checked. ## Performance And Technical Scope The [Performance](/manual/performance) chapter records current measurements, known gaps, reproduction evidence, and the optimization direction for later releases. Aura 0.3 is an executable technical preview. Its current surface includes the language, native compiler, ownership model, structured task runtime, numeric arrays, control-plane modules, package tooling, Manual, and editor extension. The [Current Limits](/manual/current-limits) chapter lists the precise boundaries of that surface. ## Source: docs/downloads.md # Downloads Aura 0.3.2 is a technical preview. The compiler, command-line tools, editor extension, reference manual, and source are distributed from the [Aura GitHub repository](https://github.com/johnolafenwa/Aura). ## Aura CLI Install the current preview with one command on a supported macOS or Linux host, including x86-64 Ubuntu 24.04 inside Windows WSL 2: ```bash curl -fsSL https://johnolafenwa.github.io/Aura/install.sh | sh ``` The installer downloads the matching release archive, verifies it against the published `SHA256SUMS`, and installs Aura under `~/.local` by default. Set `AURA_INSTALL_PREFIX` to select another prefix. Choose a detailed platform guide: - [Installation overview](/install/) - [macOS: Apple silicon and Intel](/install/macos) - [Linux: Ubuntu 24.04 and compatible x86-64 glibc hosts](/install/linux) - [Windows 11 through Ubuntu on WSL 2](/install/windows-wsl) Download the archive for your platform from the [v0.3.2-preview release](https://github.com/johnolafenwa/Aura/releases/tag/v0.3.2-preview). Each release includes Linux x64, macOS x64, and macOS arm64 archives together with a `SHA256SUMS` manifest. After extracting an archive, put its `bin` directory on `PATH` and verify the installation: ```bash aura --version ``` ## VS Code Extension Install **Aura Programming Language** from either public extension registry: - [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=JohnOlafenwa.vscode-aura-lang) - [Open VSX](https://open-vsx.org/extension/JohnOlafenwa/vscode-aura-lang) The registry packages are identical and carry the plain extension version `0.3.3`. The extension needs the `aura` executable on `PATH` because semantic editor features run through the compiler-owned `aura lsp` server. Install from a terminal with: ```bash code --install-extension JohnOlafenwa.vscode-aura-lang ``` For a manual installation, download [`aura-language.vsix`](https://github.com/johnolafenwa/Aura/releases/download/v0.3.2-preview/aura-language.vsix) from the GitHub Release, then choose **Extensions: Install from VSIX...** in VS Code. The [complete VS Code guide](/install/vscode) covers Marketplace, Open VSX, manual VSIX, custom compiler paths, verification, and installation into a WSL remote extension host. ## Documentation And Source The release also includes the static Aura documentation archive. The current book is available on [GitHub Pages](https://johnolafenwa.github.io/Aura/), and the complete source is available from the [Aura repository](https://github.com/johnolafenwa/Aura). ## Source: docs/install/index.md # Install Aura Aura 0.3 is distributed as a self-contained command-line tool with its private native runtime. Choose the guide for the operating system where `aura` will run: | Platform | Release archive | Guide | | --- | --- | --- | | macOS 15, Apple silicon | `aarch64-apple-darwin` | [Install on macOS](/install/macos) | | macOS 15, Intel | `x86_64-apple-darwin` | [Install on macOS](/install/macos) | | Ubuntu 24.04 or compatible glibc Linux, x86-64 | `x86_64-unknown-linux-gnu` | [Install on Linux](/install/linux) | | Windows 11, x86-64 | Linux archive inside WSL 2 | [Install on Windows with WSL](/install/windows-wsl) | The installer detects the supported archive automatically, verifies its SHA-256 checksum, and installs under `~/.local`: ```bash curl -fsSL https://johnolafenwa.github.io/Aura/install.sh | sh ``` Verify the result in the same terminal after adding `~/.local/bin` to `PATH`: ```bash aura --version ``` The expected release identity begins with `aura 0.3.2-preview`. The remaining text is the source commit used to build the binary. ## What Gets Installed The default layout is: ```text ~/.local/ ├── bin/aura ├── lib/aura/ │ ├── libaura_compiler.a │ └── native-link-args.json └── share/aura/ ├── examples/ ├── README.md └── LICENSE ``` Set `AURA_INSTALL_PREFIX` when another prefix is required: ```bash AURA_INSTALL_PREFIX="$HOME/tools/aura" \ sh -c "$(curl -fsSL https://johnolafenwa.github.io/Aura/install.sh)" ``` Add the selected prefix's `bin` directory to `PATH` after installation. ## Editor Setup Install the [Aura Programming Language extension](/install/vscode) after the CLI works. The extension supplies the editor client, syntax grammar, and snippets. Compiler-backed diagnostics, completion, hover, definitions, and symbols use the installed `aura lsp` server. ## Native Builds `aura run` and `aura check` work after installing the archive. Direct native execution and `aura build` also require a host C toolchain: - macOS: Xcode command-line tools - Ubuntu and WSL: `build-essential` The platform guides include the exact commands. ## Next Step Continue with [Getting Aura Running](/learn/install-and-run) to create a source file, run it, check it, and build a native executable. ## Source: docs/install/linux.md # Install Aura On Linux The Aura 0.3 preview supports x86-64 Ubuntu 24.04 and compatible glibc-based Linux distributions. The release does not currently include Linux ARM64 or musl archives. ## 1. Confirm The Host ```bash uname -s uname -m ``` The supported release path reports `Linux` and `x86_64` or `amd64`. ## 2. Install Download And Verification Tools On Ubuntu 24.04: ```bash sudo apt update sudo apt install -y curl ca-certificates tar coreutils ``` `coreutils` supplies `sha256sum`, which the installer uses to verify the downloaded archive. ## 3. Install Aura ```bash curl -fsSL https://johnolafenwa.github.io/Aura/install.sh | sh ``` The installer selects `x86_64-unknown-linux-gnu`, downloads the archive and `SHA256SUMS`, rejects a checksum mismatch, and installs under `~/.local`. ## 4. Add Aura To Bash Add `~/.local/bin` to the login environment once: ```bash grep -qxF 'export PATH="$HOME/.local/bin:$PATH"' "$HOME/.profile" || \ printf '%s\n' 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.profile" export PATH="$HOME/.local/bin:$PATH" ``` New login shells will read `~/.profile`. The final command updates the current terminal immediately. ## 5. Verify The Installation ```bash command -v aura aura --version ``` The version should begin with `aura 0.3.2-preview`. ## 6. Run A Program Create `hello.au`: ```aura def main(): print("hello from Aura on Linux") ``` Then run it: ```bash aura run hello.au ``` ## 7. Enable Native Builds Install the C compiler and linker required by direct native execution: ```bash sudo apt install -y build-essential ``` Verify the toolchain and build the example: ```bash cc --version aura build -o hello hello.au ./hello ``` ## Upgrade Aura ```bash aura upgrade aura --version ``` `aura upgrade` downloads the current installer, verifies the published release checksums, and replaces the compiler and bundled runtime in the same install prefix. Set `AURA_INSTALL_PREFIX` when upgrading an installation in a custom location. ## Troubleshooting ### `aura: command not found` ```bash ls -l "$HOME/.local/bin/aura" export PATH="$HOME/.local/bin:$PATH" ``` Persist the export in the startup file used by the current shell. ### `sha256sum` is missing Install `coreutils`, then rerun the installer: ```bash sudo apt install -y coreutils ``` ### The archive will not start on the distribution The published Linux binary targets x86-64 glibc systems. Confirm the architecture with `uname -m` and the C library with `ldd --version`. Alpine Linux and other musl systems are outside the current distribution matrix. Continue with the [VS Code extension guide](/install/vscode) or [Getting Aura Running](/learn/install-and-run). ## Source: docs/install/macos.md # Install Aura On macOS Aura publishes separate macOS 15 archives for Apple silicon and Intel. The installer uses `uname` to select the correct archive. ## 1. Confirm The Mac Architecture Open Terminal and run: ```bash uname -m ``` - `arm64` means Apple silicon. - `x86_64` means Intel. Both results are supported. Other macOS architectures do not have a release archive. ## 2. Install Aura macOS includes `curl`, `tar`, and `shasum`, which are the tools used by the verified installer: ```bash curl -fsSL https://johnolafenwa.github.io/Aura/install.sh | sh ``` The script downloads the matching `v0.3.2-preview` archive and checks it against the release's `SHA256SUMS` file before copying anything into the installation prefix. ## 3. Add Aura To zsh The default installation location is `~/.local/bin/aura`. Add that directory to the zsh login environment once: ```bash grep -qxF 'export PATH="$HOME/.local/bin:$PATH"' "$HOME/.zshrc" || \ printf '%s\n' 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.zshrc" source "$HOME/.zshrc" ``` If `aura --version` already works, the directory was already on `PATH` and this step is unnecessary. ## 4. Verify The Installation ```bash command -v aura aura --version ``` The command path should end in `.local/bin/aura`, and the version should begin with: ```text aura 0.3.2-preview ``` ## 5. Run A Program Create `hello.au`: ```aura def main(): print("hello from Aura on macOS") ``` Run it: ```bash aura run hello.au ``` ## 6. Enable Native Builds The default MIR execution path does not need Xcode. Direct native execution and `aura build` need Apple's linker and C toolchain. Install them with: ```bash xcode-select --install ``` After the installer completes, verify and build: ```bash xcode-select -p aura build -o hello hello.au ./hello ``` ## Upgrade Aura Upgrade the installed CLI and runtime to the current published preview: ```bash aura upgrade aura --version ``` The command preserves the active install prefix and uses the same verified installer as a fresh installation. ## Troubleshooting ### `aura: command not found` Confirm the file exists and reload the shell: ```bash ls -l "$HOME/.local/bin/aura" source "$HOME/.zshrc" ``` ### The installer reports an unsupported architecture Run `uname -m`. Aura currently publishes macOS archives only for `arm64` and `x86_64`. ### Native linking fails Run `xcode-select -p`. If it fails, install or repair the Xcode command-line tools before using `aura build` or `--backend direct`. Continue with the [VS Code extension guide](/install/vscode) or [Getting Aura Running](/learn/install-and-run). ## Source: docs/install/vscode.md # Install The VS Code Extension The **Aura Programming Language** extension provides `.au` syntax highlighting, indentation, snippets, diagnostics, completion, hover, go-to-definition, and document symbols. The extension includes its JavaScript editor client and language-server transport. Semantic analysis comes from the actual compiler server exposed by `aura lsp`, so install the [Aura CLI](/install/) first and verify: ```bash aura --version aura help ``` ## Install From Visual Studio Marketplace Open the Extensions view in VS Code, search for **Aura Programming Language** from publisher **JohnOlafenwa**, and select **Install**. - [Open the Visual Studio Marketplace listing](https://marketplace.visualstudio.com/items?itemName=JohnOlafenwa.vscode-aura-lang) The equivalent terminal command is: ```bash code --install-extension JohnOlafenwa.vscode-aura-lang ``` Reload VS Code after installation. ## Install From Open VSX Editors using Open VSX, including VSCodium, can install the same extension: - [Open the Open VSX listing](https://open-vsx.org/extension/JohnOlafenwa/vscode-aura-lang) In VSCodium, search for **Aura Programming Language** in Extensions or run: ```bash codium --install-extension JohnOlafenwa.vscode-aura-lang ``` ## Install The Release VSIX Manually Download `aura-language.vsix` from the [v0.3.2-preview release](https://github.com/johnolafenwa/Aura/releases/tag/v0.3.2-preview). Then open the Command Palette and choose **Extensions: Install from VSIX...**. The command-line form is: ```bash code --install-extension ./aura-language.vsix ``` VS Code does not automatically update extensions installed from a VSIX by default. Install the next release's VSIX manually when upgrading through this path. ## Install In WSL First complete [Install Aura On Windows With WSL](/install/windows-wsl). Open the project from the Ubuntu terminal with `code .` and confirm that the remote status bar shows **WSL: Ubuntu**. Open Extensions in that remote window, find **Aura Programming Language**, and select **Install in WSL: Ubuntu**. The Aura extension and the `aura` CLI must both run in the WSL environment. Installing the extension only in the local Windows extension host cannot reach the Linux compiler server reliably. Verify the remote environment in VS Code's integrated terminal: ```bash command -v aura aura --version ``` ## Use A Specific Aura Binary The extension normally launches `aura` from `PATH`. To use another binary, start VS Code with `AURA_LSP_AURA_PATH` set to its absolute path: ```bash AURA_LSP_AURA_PATH="$HOME/tools/aura/bin/aura" code /path/to/project ``` For WSL, run that command from the WSL terminal so the path is a Linux path. ## Verify Language Support Create or open a file ending in `.au`: ```aura def greet(name: str) -> str: return f"hello {name}" print(greet("Aura")) ``` Confirm all of the following: 1. The language mode in the lower-right corner reads **Aura**. 2. Keywords, strings, types, and interpolation receive Aura highlighting. 3. An incomplete or invalid expression produces an `AU####` diagnostic. 4. Completion appears after a binding or member-access prefix. 5. Hover shows compiler-owned type information. ## Troubleshooting ### The file opens as plain text Confirm the filename ends in `.au`. Select the language mode in the lower-right corner and choose **Aura**. ### Syntax colors work but semantic features do not Syntax highlighting is bundled with the extension, while semantic features need `aura lsp`. Open VS Code's integrated terminal and run: ```bash command -v aura aura --version ``` Restart VS Code after fixing `PATH`, or launch it with `AURA_LSP_AURA_PATH` as shown above. ### Inspect the language-server output Open **View → Output**, then select **Aura Language Server**. Startup and request failures appear there without requiring a separate server package. ## Source: docs/install/windows-wsl.md # Install Aura On Windows With WSL Aura does not publish a native Windows executable. On an x86-64 Windows 11 machine, install and run the Linux release inside Windows Subsystem for Linux 2 using Ubuntu 24.04. The CLI, compiler runtime, projects, and VS Code language server all run inside WSL 2. ## 1. Install WSL 2 And Ubuntu Open PowerShell as Administrator. Check the available distribution names: ```powershell wsl --list --online ``` Install Ubuntu 24.04: ```powershell wsl --install -d Ubuntu-24.04 ``` Restart Windows if requested. Launch **Ubuntu 24.04 LTS** from the Start menu and create the Linux username and password requested on first launch. Microsoft's [WSL installation guide](https://learn.microsoft.com/windows/wsl/install) documents recovery steps for older Windows builds and existing WSL setups. ## 2. Confirm WSL 2 In PowerShell: ```powershell wsl --list --verbose ``` The Ubuntu row must show version `2`. If it shows version `1`, use the exact distribution name displayed by the preceding command: ```powershell wsl --set-version Ubuntu-24.04 2 ``` All remaining shell commands in this guide run inside the Ubuntu terminal, not PowerShell. ## 3. Prepare Ubuntu ```bash sudo apt update sudo apt install -y curl ca-certificates tar coreutils build-essential ``` The first four packages install and verify Aura. `build-essential` enables direct native execution and `aura build`. ## 4. Install Aura Inside WSL ```bash curl -fsSL https://johnolafenwa.github.io/Aura/install.sh | sh ``` Add Aura to the Ubuntu login environment: ```bash grep -qxF 'export PATH="$HOME/.local/bin:$PATH"' "$HOME/.profile" || \ printf '%s\n' 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.profile" export PATH="$HOME/.local/bin:$PATH" ``` Verify both the CLI and native toolchain: ```bash aura --version cc --version ``` ## 5. Create A Project In The WSL Filesystem Keep active Aura projects under the Linux home directory. This gives Linux tools normal permissions and filesystem behavior: ```bash mkdir -p "$HOME/projects/aura-hello" cd "$HOME/projects/aura-hello" printf '%s\n' 'print("hello from Aura in WSL")' > hello.au aura run hello.au ``` Windows drives are available under paths such as `/mnt/c`, but the Linux home directory is the recommended location for WSL development projects. ## 6. Connect VS Code To WSL Install Visual Studio Code on Windows and select **Add to PATH** in its Windows installer. Install Microsoft's **WSL** extension in the local VS Code window. From the Ubuntu terminal, open the project: ```bash cd "$HOME/projects/aura-hello" code . ``` VS Code installs its server inside WSL and opens a remote window. The status bar must show **WSL: Ubuntu**. In that remote window, install **Aura Programming Language** into WSL. A locally installed copy is not enough because the extension must launch the `aura lsp` executable inside Ubuntu. Continue with the complete [VS Code extension guide](/install/vscode). ## Upgrade Aura Run the updater from the Ubuntu terminal: ```bash aura upgrade aura --version ``` The command upgrades the Linux compiler and runtime inside WSL. Run it in the Ubuntu terminal, not PowerShell. ## Troubleshooting ### `wsl --install` displays help WSL may already be present. Run `wsl --list --online`, then install the exact Ubuntu distribution name shown by that command. ### `code` is not found inside Ubuntu Install VS Code on Windows with its **Add to PATH** option, close the Ubuntu terminal, reopen it, and run `code .` again. ### VS Code cannot find `aura` Open a terminal in the **WSL: Ubuntu** window and run: ```bash command -v aura aura --version ``` If the commands fail, restore the PATH export from step 4 and restart the WSL VS Code window. Do not install a Windows copy of Aura; the current compiler distribution is the Linux binary running inside WSL. ### Windows on ARM The current Aura release has no Linux ARM64 archive. Windows-on-ARM WSL hosts are outside the supported distribution matrix for this preview. ## Source: docs/manual/index.md # Aura Language Reference The source version for this Manual is **Aura 0.3.2 (technical preview)**. The release stamp below records the rendered implementation baseline commit. Release builders set `AURA_DOCS_COMMIT`; GitHub builds use `GITHUB_SHA`; a clean local build falls back to the checkout's committed `HEAD`. A dirty or Git-free build says `local-uncommitted-checkout` instead of inventing a commit or writing a self-referential hash into this source page. This Manual is the normative reference for the implemented Aura language and runtime. It is written so a reader can reconstruct the language accurately: complete syntax, name and type rules, ownership behavior, execution, module APIs, diagnostics, limits, and tool contracts. The Learn track tells a story. A future book may build a longer learning sequence from this material. The reference defines the facts that those teaching materials must preserve. Start with [Language Specification](/manual/language-specification) for scope, terminology, authority, and conformance language. ## Language Reference - [Lexical Structure](/manual/lexical-structure): files, physical/logical lines and delimiter continuation, indentation, comments, identifiers, keywords, literals, f-strings, and duration literals. - [Complete Grammar](/manual/grammar): normative EBNF, precedence, associativity, contextual syntax, layout, and unsupported forms. - [Names And Scopes](/manual/names-and-scopes): modules, imports, visibility, bindings, block scope, no-shadowing, and member lookup. - [Types](/manual/types): primitive types, tuples, `None`, `Duration`, generic types, copy and move categories, and type annotations. - [Static Semantics](/manual/static-semantics): inference, type equality, assignment, calls, operators, constructors, matching, traits, resources, and entrypoints. - [Expressions](/manual/expressions): operators, calls, indexing, owned list/str slicing, member access, literals, conditional expressions, membership and comparison chains, `match` expressions, `try`, and f-strings. - [Statements](/manual/statements): bindings, assignment, control flow, loops, imports, `with`, `pass`, assertions, and top-level execution. - [Tuples](/manual/tuples): fixed structural values and types, recursive unpacking and patterns, whole-source ownership, constant indexing, and recursive equality. - [Assertions](/manual/assertions): exact boolean conditions, lazy messages, `AU4001` failures, cleanup precedence, and backend behavior. - [Functions](/manual/functions): signatures, bare/`own`/`mut` parameter modes, default arguments, named arguments, `main`, owned returns, and call binding. - [Closures](/manual/closures): contextual expression lambdas, by-value captures, repeated read-only calls, consuming single-use calls, and structural Transfer. - [Foreign Function Interface (FFI) v0](/manual/ffi): explicit package authorization, bodyless C declarations, fixed-width scalars, pointer-length views, opaque handles, and the native safety boundary. - [Classes](/manual/classes): fields, constructors, methods, receivers, associated methods, resources, and mutation. - [Enums And Pattern Matching](/manual/enums-and-match): variants, payloads, exhaustiveness, literal patterns, short-form variants, and match value flow. - [Generics And Traits](/manual/generics-and-traits): type parameters, trait declarations, impls, bounds, dispatch, and current restrictions. - [Ownership And Borrowing](/manual/ownership-and-borrowing): moves, copies, clones, shared borrows, mutable borrows, field moves, and task boundaries. - [Execution Model](/manual/execution-model): evaluation order, entry execution, backends, cleanup, runtime failures, scheduling, cancellation, and external effects. ## Runtime And Library Reference - [Collections](/manual/collections): `list[T]`, `dict[K, V]`, `set[T]`, literals, eager owned comprehensions and slices, iteration, mutation, and eager callable-powered list algorithms. - [Numeric Arrays](/manual/numeric-arrays): contiguous row-major `Array[T]`, four numeric dtypes, first-axis owned slices, reductions, native kernels, and explicit checked/wrapping/saturating integer arithmetic. - [Math Module](/manual/math): exact binary64 constants plus scalar rounding, power, exponential, logarithmic, and trigonometric functions with explicit domain and overflow behavior. - [Bytes, Text Codecs, And SHA-256](/manual/bytes): `list[uint8]`, strict UTF-8 conversion, canonical hex/base64, typed data errors, and raw SHA-256. - [JSON Module](/manual/json): recursive JSON values, typed parse errors, exact number classification, deterministic dumping, and resource limits. - [Randomness Module](/manual/randomness): deterministic seeded streams, exact sequence compatibility, unbiased ranges, in-place shuffle, and OS-secure integers and bytes. - [Concurrency](/manual/concurrency): `TaskGroup`, `Task[T]`, `Queue[T]`, cancellation, `yield_now`, typed heterogeneous `select`, `wait_any`, `wait_all`, and scheduler-aware waits. - [I/O Module](/manual/io): standard input/output and `io.Error`. - [Filesystem Module](/manual/filesystem): one-shot helpers, `fs.File`, scoped file cleanup, byte and text limits. - [Network Module](/manual/network): TCP, UDP, HTTP, WebSocket, Unix sockets, TLS, and HTTP client helpers. - [Process Module](/manual/process): subprocess spawning, pipes, completed processes, process groups, supervisors, and restart policy. - [Control-Plane Modules](/manual/control-plane): system/path helpers, JSON and TOML compatibility APIs, telemetry, and `control.retry`. - [Packages](/manual/packages): manifests, package roots, import resolution, lockfiles, and editor analysis behavior. - [CLI And Tooling](/manual/cli-and-tooling): `aura` commands, diagnostics, analysis JSON, completions, and build modes. - [API Index](/manual/api-index): every maintained builtin function, method, enum, and module type in one place. - [Diagnostics](/manual/diagnostics): compile-time/runtime categories, source rendering, machine-readable positions, and CLI exit status. - [Performance](/manual/performance): reproducible measurements, current gaps, evidence provenance, and the optimization direction for later releases. - [Current Limits](/manual/current-limits): intentional current boundaries and practical workarounds. - [Conformance](/manual/conformance): executable fixture/test mapping and the rules for changing the language safely. ## Conventions Used In This Manual Code blocks marked `python` contain Aura code using Python highlighting until the documentation theme ships a dedicated highlighter. The language grammar itself is defined by [Complete Grammar](/manual/grammar). Shell blocks contain repository commands. Signatures use `Duration = ...` for optional timeout parameters whose default is documented in the relevant API section. In general: - blocking APIs wait when a timeout is omitted - convenience helpers ending in `_or_none` or `_or` may use immediate non-blocking checks when documented that way - timeout results are explicit variants such as `TimedOut`, `None`, or `process.Error.TimedOut` - explicit timeout values must be non-negative and fit the host deadline; invalid values return the documented typed InvalidInput/process error or trap with `AU4001` when the API has no typed error carrier When a page says a value is returned "cloned", it means the caller receives a new owned value. When a page says a method "moves" an argument, the caller cannot use that argument after the call unless it is a copy type. ## Source: docs/manual/api-index.md # API Index This page indexes every maintained public builtin constant, function, method, module type, and builtin enum documented by the manual. It is intentionally dense. Use the linked manual pages for examples and longer discussion. `assert condition` and `assert condition, message` are statements rather than callable APIs. Their exact typing, lazy-message, `AU4001`, cleanup, and backend-parity contract is indexed separately in [Assertions](/manual/assertions). ## Top-Level Builtins | API | Signature | Contract | | --- | --- | --- | | `print` | `print(value) -> None` | Renders `value` and writes a newline. | | `range` | `range(stop: int64) -> Range`; `range(start: int64, stop: int64) -> Range` | End-exclusive integer range. | | `cancelled` | `cancelled() -> bool` | Returns the current task cancellation state. | | `yield_now` | `yield_now() -> None` | Voluntarily yields the current lightweight task to the scheduler. | | `sleep` | `sleep(duration: Duration) -> None` | Suspends the current task using the scheduler. | | `select` | `select(source, ...) -> SelectOutcome[Q, T]` | Waits on one or more positional Queue, Task, or relative-Duration sources; cancellation wins, otherwise the lowest ready source index wins. | | `wait_any` | `wait_any(tasks: list[Task[T]], timeout: Duration = ...) -> WaitAny[T]` | Waits for the first task outcome; consumes the list and abandons unchosen rights when `T` is non-repeatable. `wait_any([])` returns `TimedOut` immediately. | | `wait_all` | `wait_all(tasks: list[Task[T]], timeout: Duration = ...) -> WaitAll[T]` | Waits for all tasks, the first task error, timeout, or cancellation; consumes the list when `T` is non-repeatable. | | `abs` | `abs(value: number) -> number` | Absolute value for integers and floats. | | `min` | `min(left: number, right: number) -> number` | Smaller value of the same numeric type. | | `max` | `max(left: number, right: number) -> number` | Larger value of the same numeric type. | | `sqrt` | `sqrt(value: float32|float64) -> float32|float64` | Square root. | | `round` | `round(value: T) -> T` for integers; `round(value: float32\|float64) -> int64` | Integer identity or nearest-integer ties-to-even rounding. | | `divmod` | `divmod(left: T, right: T) -> (T, T)` for one exact integer or float type | Paired floor quotient and divisor-signed remainder. | | `parse_int32` | `parse_int32(text: str) -> Result[int32, str]` | Parses a signed 32-bit integer. | | `parse_int64` | `parse_int64(text: str) -> Result[int64, str]` | Parses a signed 64-bit integer. | | `parse_float64` | `parse_float64(text: str) -> Result[float64, str]` | Parses a 64-bit float. | | `len` | `len(value: str\|list[T]\|dict[K, V]\|set[T]\|Array[T]) -> int64` | Delegates to the value's own `len()` member with the same `int64` type and value. | | `str` | `str(value) -> str` | Renders `value` exactly as `print` and f-string interpolation render it. | ## Foreign Declarations FFI declarations are package-authorized direct calls, not builtins or first-class function values. Their names and exact signatures come from the binding package. | Surface | Signature | Contract | | --- | --- | --- | | C function | `extern "C" def name(...) -> R` | Bodyless synchronous call to the same process-global symbol name. | | Opaque handle | `extern "C" opaque class Handle` | Non-null, non-Copy, non-cloneable, non-Transfer foreign pointer wrapper. | | str view | `text: str` | Temporary const UTF-8 pointer plus byte length; empty is `(NULL, 0)` and no NUL terminator is promised. | | Byte view | `bytes: list[uint8]` | Temporary const pointer plus byte length; empty is `(NULL, 0)`. | | Mutable byte view | `bytes: mut list[uint8]` | Same-length scratch copy-in/out; writeback occurs after native return before result validation. | | Consuming handle | `handle: own Handle` | Moves the opaque handle into a foreign close/free-style call. | The complete scalar table, manifest report, safety boundary, diagnostics, and backend rules are in [FFI v0](/manual/ffi). ## Scalars And str | API | Signature | Contract | | --- | --- | --- | | `float64.sqrt` | `sqrt() -> float64` | Square root of the receiver. | | integer `.to_float` | `to_float() -> float64` | Converts any integer type with IEEE-754 round-to-nearest, ties-to-even; may round. | | integer wrapping methods | `wrapping_add(rhs)`, `wrapping_sub(rhs)`, `wrapping_mul(rhs)` | Same-type fixed-width two's-complement modular arithmetic. | | integer saturating methods | `saturating_add(rhs)`, `saturating_sub(rhs)`, `saturating_mul(rhs)` | Same-type arithmetic clamped to the declared width. | | integer wrapping shifts | `wrapping_shl(count)`, `wrapping_shr(count)` | Same-type count; left shift discards high bits and right shift matches `>>` after count validation. | | integer saturating shifts | `saturating_shl(count)`, `saturating_shr(count)` | Same-type count; left shift clamps and right shift matches `>>` after count validation. | | scalar `.to_string` | `to_string() -> str` | Supported on `bool`, integer types, `float32`, and `float64`. | | `Duration.ms` | `Duration.ms(value: int64) -> Duration` | Exact signed millisecond constructor. | | `Duration.seconds` | `Duration.seconds(value: int64) -> Duration` | Exact signed second constructor. | | `Duration.minutes` | `Duration.minutes(value: int64) -> Duration` | Exact signed minute constructor. | | `Duration.to_ms` | `to_ms() -> float64` | Converts exact nanoseconds to nearest-representable binary64 milliseconds, ties-to-even; may round; accepted under ADR-0019. | | `Duration.to_seconds` | `to_seconds() -> float64` | Converts exact nanoseconds to nearest-representable binary64 seconds, ties-to-even; may round; accepted under ADR-0019. | | `str.len` | `len() -> int64` | Counts Unicode scalar values in O(n). | | `str.byte_len` | `byte_len() -> int64` | Returns the UTF-8 byte count in O(1). | | `str.to_bytes` | `to_bytes() -> list[uint8]` | Returns a fresh list containing the receiver's exact UTF-8 bytes. | | `str.from_bytes` | `from_bytes(bytes: list[uint8]) -> Result[str, bytes.Error]` | Strictly validates UTF-8 and returns a fresh str or the first invalid byte offset. | | `str.contains` | `contains(text: str) -> bool` | `true` when the receiver contains `text`. | | `str.starts_with` | `starts_with(text: str) -> bool` | Prefix test. | | `str.ends_with` | `ends_with(text: str) -> bool` | Suffix test. | | `str.split` | `split(text: str) -> list[str]` | Splits on each occurrence of `text`. | | `str.replace` | `replace(from: str, to: str) -> str` | Returns a new string with replacements applied. | | `str.to_lower` | `to_lower() -> str` | Unicode lowercase conversion. | | `str.to_upper` | `to_upper() -> str` | Unicode uppercase conversion. | | `str.strip_prefix` | `strip_prefix(text: str) -> Option[str]` | Returns the remainder when the prefix matches. | | `str.strip_suffix` | `strip_suffix(text: str) -> Option[str]` | Returns the remainder when the suffix matches. | | `str.trim` | `trim() -> str` | Removes surrounding Unicode whitespace. | | `str.join` | `join(parts: list[str]) -> str` | Joins `parts` using the receiver as separator. | | `str.clone` | `clone() -> str` | Returns a new owned string. | Duration operators are `Duration + Duration`, `Duration - Duration`, `Duration * int64`, `int64 * Duration`, and `Duration // int64`, all returning `Duration`, plus equality and all four ordering comparisons between Duration values. Arithmetic is checked on signed i128 nanoseconds. ## Numeric Arrays `T` and `U` below are each exactly one of `int32`, `int64`, `float32`, or `float64`. See [Numeric Arrays](/manual/numeric-arrays) for shape, ownership, diagnostic, and backend contracts. | API | Signature | Contract | | --- | --- | --- | | `Array[T].zeros` | `zeros(shape: list[int64]) -> Array[T]` | Fresh rank-at-least-one row-major zero buffer. | | `Array[T].full` | `full(shape: list[int64], value: T) -> Array[T]` | Fresh buffer filled with `value`. | | `Array[T].from_list` | `from_list(values: list[T], shape: list[int64]) -> Array[T]` | Copies the shared list into exact row-major shape. | | `Array.shape` | `shape() -> list[int64]` | Owned shape snapshot. | | `Array.len` | `len() -> int64` | Total element count. | | `Array.clone` | `clone() -> Array[T]` | Explicit fresh full-buffer copy. | | `Array.get` | `get(index: list[int64]) -> Option[T]` | Optional coordinate read. | | `Array.set` | `set(index: list[int64], value: T) -> Option[T]` | Mutable replacement; returns old scalar or traps on invalid coordinate/rank. | | `Array.fill` | `fill(value: T) -> None` | Mutable row-major fill. | | `Array.map` | `map[U](f: def(T) -> U) -> Array[U]` | Eager row-major repeatable callback. | | `Array.sum` | `sum() -> T` | Deterministic dtype reduction; empty returns zero. | | `Array.min` | `min() -> T` | Minimum; empty is `AU4007`. | | `Array.max` | `max() -> T` | Maximum; empty is `AU4007`. | | `Array.mean` | `mean() -> float64` | `float64` accumulation/result; empty is `AU4007`. | | integer Array wrapping methods | `wrapping_add(rhs)`, `wrapping_sub(rhs)`, `wrapping_mul(rhs)` | `rhs` is same-shape Array or same-dtype scalar; fresh Array result. | | integer Array saturating methods | `saturating_add(rhs)`, `saturating_sub(rhs)`, `saturating_mul(rhs)` | `rhs` is same-shape Array or same-dtype scalar; fresh Array result. | ## Math See [Math Module](/manual/math) for the normative IEEE-754, domain, evaluation-order, diagnostic, and backend contracts. Every argument is exactly `float64`; the module performs no implicit numeric conversion. | API | Signature | Contract | | --- | --- | --- | | `math.pi` | `float64` constant | Nearest binary64 pi, bits `0x400921fb54442d18`. | | `math.e` | `float64` constant | Nearest binary64 Euler's number, bits `0x4005bf0a8b145769`. | | `math.inf` | `float64` constant | Positive infinity, bits `0x7ff0000000000000`. | | `math.nan` | `float64` constant | Canonical quiet NaN, bits `0x7ff8000000000000`. | | `math.floor` | `floor(value: float64) -> int64` | Greatest integer less than or equal to `value`, checked for `int64` range. | | `math.ceil` | `ceil(value: float64) -> int64` | Least integer greater than or equal to `value`, checked for `int64` range. | | `math.trunc` | `trunc(value: float64) -> int64` | Truncates toward zero, checked for `int64` range. | | `math.pow` | `pow(base: float64, exponent: float64) -> float64` | Binary64 exponentiation with specified identity, domain, and overflow behavior. | | `math.exp` | `exp(value: float64) -> float64` | Base-e exponential. | | `math.log` | `log(value: float64) -> float64` | Natural logarithm. | | `math.log2` | `log2(value: float64) -> float64` | Base-2 logarithm. | | `math.log10` | `log10(value: float64) -> float64` | Base-10 logarithm. | | `math.sin` | `sin(value: float64) -> float64` | Sine in radians. | | `math.cos` | `cos(value: float64) -> float64` | Cosine in radians. | | `math.tan` | `tan(value: float64) -> float64` | Tangent in radians. | ## Randomness See [Randomness Module](/manual/randomness) for the normative xoshiro256** algorithm, seed-42 vectors, ownership, secure-source boundary, and diagnostics. | API | Signature | Contract | | --- | --- | --- | | `random.Rng` | `Rng(seed: int64) -> random.Rng` | Creates a move-only deterministic stream from the seed's exact two's-complement bit pattern. | | `random.Rng.next_int` | `next_int(lo: int64, hi: int64) -> int64` | Uniform half-open `[lo, hi)` integer; mutable receiver. | | `random.Rng.next_float` | `next_float() -> float64` | Uniform 53-bit binary64 value in `[0.0, 1.0)`; mutable receiver. | | `random.Rng.shuffle` | `shuffle[T](values: mut list[T]) -> None` | Descending Fisher-Yates shuffle in place; mutable receiver and list. | | `random.secure_int` | `secure_int(lo: int64, hi: int64) -> int64` | OS-secure uniform half-open integer with no deterministic fallback. | | `random.secure_bytes` | `secure_bytes(n: int64) -> list[uint8]` | Exactly `n` OS-secure bytes for `0 <= n <= 2147483647`; zero skips entropy and larger counts trap with `AU4005` before allocation. | `random.Rng` has no public clone route. `AU3007` rejects the clone-producing collection and task APIs indexed below when their produced value contains, or may contain, an `Rng`, including through a user-defined wrapper. Cloning an allowed Task or Queue handle copies only the handle. Accepted ADR-0033 nevertheless rejects `random.Rng` as a task result or Queue payload with `AU3008`, and makes a Task with a non-repeatable result non-copyable. Moves, collection removals, and in-place shuffle within one owning task transfer or rearrange values without duplicating generator state. Generic clone-producing calls infer clone-safety obligations and discharge them after specialization; the obligation is retained through generic callers and module imports. ## Bytes, Text Codecs, And SHA-256 See [Bytes, Text Codecs, And SHA-256](/manual/bytes) for exact UTF-8 preservation, strict malformed-input policy, error offsets, ownership, output size preflights, and the cryptographic scope of SHA-256. | API | Signature | Contract | | --- | --- | --- | | `str.to_bytes` | `to_bytes() -> list[uint8]` | Exact UTF-8 bytes; shared receiver and fresh result. | | `str.from_bytes` | `from_bytes(bytes: list[uint8]) -> Result[str, bytes.Error]` | Strict UTF-8; no replacement decoding. | | `bytes.hex_encode` | `hex_encode(value: list[uint8]) -> str` | Two lowercase ASCII digits per byte. | | `bytes.hex_decode` | `hex_decode(text: str) -> Result[list[uint8], bytes.Error]` | Accepts mixed-case ASCII hex; rejects prefixes, separators, and whitespace. | | `bytes.base64_encode` | `base64_encode(value: list[uint8]) -> str` | RFC 4648 standard alphabet with canonical padding. | | `bytes.base64_decode` | `base64_decode(text: str) -> Result[list[uint8], bytes.Error]` | Strict canonical standard-alphabet decode. | | `bytes.sha256` | `sha256(value: list[uint8]) -> list[uint8]` | Fresh raw 32-byte FIPS 180-4 digest. | | `bytes.sha256_string` | `sha256_string(text: str) -> list[uint8]` | SHA-256 over the text's exact UTF-8 bytes. | All displayed inputs use shared access and remain reusable. An `encoding` argument is reserved but not implemented. Expanded output that cannot be represented or allocated traps with `AU4005`; malformed data returns `bytes.Error`. ## Collections See [Collections](/manual/collections) for ownership and iteration details. ### list[T] | API | Signature | Contract | | --- | --- | --- | | `list[T]()` | `list[T]()` | Empty list constructor. | | `list.len` | `len() -> int64` | Element count. | | `list.is_empty` | `is_empty() -> bool` | `true` when empty. | | `list.copy` | `copy() -> list[T]` | Returns independent owned storage; requires clone-safe `T`. | | `list.append` | `append(value: own T) -> None` | Transfers `value` to the end. | | `list.pop` | `pop(index: int64 = -1) -> T` | Removes and transfers the normalized position; invalid positions trap. | | `list.get` | `get(index: int64) -> Option[T]` | Cloned element after negative-index normalization, or `None` when out of bounds; requires clone-safe `T`. | | `list.set` | `set(index: int64, value: own T) -> T` | Replaces and transfers out the old element; invalid positions trap. | | `list.remove` | `remove(value: T) -> None` | Removes the first equal value; absence traps with `AU4008`. | | `list.index` | `index(value: T) -> int64` | Returns the first equal position; absence traps with `AU4008`. | | `list.count` | `count(value: T) -> int64` | Counts equal elements. | | `list.swap` | `swap(first: int64, second: int64) -> None` | Normalizes and swaps two positions; invalid positions trap. | | `list.extend` | `extend(other: own list[T]) -> None` | Moves elements from `other` into the receiver. | | `list.insert` | `insert(index: int64, value: own T) -> None` | Inserts before the Python-clamped position. | | `list.clear` | `clear() -> None` | Removes all elements. | | `list.reverse` | `reverse() -> None` | Reverses in place. | | `list.sort` | `sort(reverse: bool = false) -> None` | Stable in-place natural ordering for `T: Ord`. | | `list.sort` | `sort[K](key: def(T) -> K, reverse: bool = false) -> None` | Stable key ordering; evaluates each key once before mutation and requires `K: Ord`. | | `list.map` | `map[U](f: def(T) -> U) -> list[U]` | Eager shared traversal into a fresh owned result; retains the source. | | `list.filter` | `filter(f: def(T) -> bool) -> list[T]` | Eager shared traversal into a fresh owned result; retains the source and requires clone-safe `T`. | | `list.reserve` | `reserve(additional: int64) -> None` | Ensures capacity for at least `len() + additional`. | | `list[T].with_capacity` | `with_capacity(minimum: int64) -> list[T]` | Creates an empty list with at least the requested capacity. | ### dict[K, V] | API | Signature | Contract | | --- | --- | --- | | `dict[K, V]()` | `dict[K, V]()` | Empty dictionary constructor. | | `dict.len` | `len() -> int64` | Entry count. | | `dict.is_empty` | `is_empty() -> bool` | `true` when empty. | | `dict.copy` | `copy() -> dict[K, V]` | Returns independent owned storage; requires clone-safe `K` and `V`. | | `dict.get` | `get(key: K) -> Option[V]` | Cloned value or `None` when absent; requires clone-safe `V`. | | `dict.remove` | `remove(key: K) -> Option[V]` | Removes an entry and returns the previous value. | | `dict.keys` | `keys() -> list[K]` | Cloned keys in insertion order; requires clone-safe `K`. | | `dict.values` | `values() -> list[V]` | Cloned values in insertion order; requires clone-safe `V`. | | `dict.items` | `items() -> list[(K, V)]` | Cloned key/value tuples in insertion order; requires clone-safe `K` and `V`. | | `dict.clear` | `clear() -> None` | Removes all entries. | | `dict.update` | `update(other: own dict[K, V]) -> None` | Transfers entries from `other`; matching keys retain their positions. | | `dict.reserve` | `reserve(additional: int64) -> None` | Ensures capacity for at least `len() + additional`. | | `dict[K, V].with_capacity` | `with_capacity(minimum: int64) -> dict[K, V]` | Creates an empty dictionary with at least the requested capacity. | ### set[T] | API | Signature | Contract | | --- | --- | --- | | `set[T]()` | `set[T]()` | Empty set constructor. | | `set.len` | `len() -> int64` | Unique value count. | | `set.is_empty` | `is_empty() -> bool` | `true` when empty. | | `set.copy` | `copy() -> set[T]` | Returns independent owned storage; requires clone-safe `T`. | | `set.add` | `add(value: own T) -> None` | Transfers a value into the set. | | `set.remove` | `remove(value: T) -> None` | Removes an equal value; absence traps with `AU4008`. | | `set.discard` | `discard(value: T) -> None` | Removes an equal value when present. | | `set.clear` | `clear() -> None` | Removes all values. | | `set.reserve` | `reserve(additional: int64) -> None` | Ensures capacity for at least `len() + additional`. | | `set[T].with_capacity` | `with_capacity(minimum: int64) -> set[T]` | Creates an empty set with at least the requested capacity. | ## Concurrency See [Concurrency](/manual/concurrency) for structured-concurrency semantics. | API | Signature | Contract | | --- | --- | --- | | `Queue[T]()` | `Queue[T](capacity: int32 = ...)` | Queue constructor; bounded when capacity is supplied; Accepted ADR-0033 requires `T: Transfer`. | | `Queue.put` | `put(value: own T, timeout: Duration = ...) -> Result[None, SendError[T]]` | Sends a value or returns the unsent value in the error; Accepted ADR-0033 requires `T: Transfer`. | | `Queue.try_put` | `try_put(value: own T) -> Result[None, SendError[T]]` | Sends without waiting; Accepted ADR-0033 requires `T: Transfer`. | | `Queue.get` | `get(timeout: Duration = ...) -> QueueReceive[T]` | Receives an item, close, timeout, or cancellation outcome; does not itself recheck payload Transfer. | | `Queue.get_or_none` | `get_or_none(timeout: Duration = ...) -> Option[T]` | `Some(value)` or `None` for closed, timeout, cancellation, or immediate absence. | | `Queue.get_or` | `get_or(default: own T, timeout: Duration = ...) -> T` | Value or fallback. | | `Queue.close` | `close() -> None` | Closes the queue and wakes waiters. | | `Task.result` | `result(timeout: Duration = ...) -> TaskResult[T]` | Waits for task outcome; consumes the observation right when `T` is non-repeatable. | | `Task.result_or_none` | `result_or_none(timeout: Duration = ...) -> Option[T]` | `Some(value)` or `None` for failure, timeout, cancellation, or immediate absence; consumes the observation right when `T` is non-repeatable, including on `None`. | | `Task.result_or` | `result_or(default: own T, timeout: Duration = ...) -> T` | Value or fallback; consumes the observation right when `T` is non-repeatable. | | `TaskGroup()` | `TaskGroup()` | Task group resource constructor. | | `TaskGroup.start` | `start(function, own ...) -> Task[T]` | Requires every capture and result to be `Transfer`; accepts inferred or explicit `function[Types]` / `Type.associated_method[Types]` targets; starts the child on the guarded 512 KiB default stack. | | `TaskGroup.start_soon` | `start_soon(function, own ...) -> None` | Applies the same Transfer and target-specialization rules without returning a handle. | | `TaskGroup.start_with_stack` | `start_with_stack(bytes: int64, function, own ...) -> Task[T]` | Applies the same Transfer and target-specialization rules with an explicit guarded 256 KiB..64 MiB request; 256 KiB is for measured shallow tasks, not the default; Provisional under ADR-0032. | | `TaskGroup.start_soon_with_stack` | `start_soon_with_stack(bytes: int64, function, own ...) -> None` | Applies the same rules and explicit guarded range without retaining a handle; 256 KiB is for measured shallow tasks, not the default; Provisional under ADR-0032. | | `TaskGroup.cancel` | `cancel() -> None` | Signals cancellation to children. | ## I/O And Filesystem See [I/O Module](/manual/io) and [Filesystem Module](/manual/filesystem). | API | Signature | Contract | | --- | --- | --- | | `io.write` | `write(text: str) -> Result[None, io.Error]` | Writes text without a newline. | | `io.flush` | `flush() -> Result[None, io.Error]` | Flushes standard output. | | `io.read_line` | `read_line() -> Result[Option[str], io.Error]` | Reads strict UTF-8 without trailing LF/CRLF; `Ok(None)` on EOF. | | `fs.exists` | `exists(path: str) -> bool` | Path existence check. | | `fs.read_to_string` | `read_to_string(path: str) -> Result[str, io.Error]` | Reads UTF-8 text, capped at 256 MiB. | | `fs.read_bytes` | `read_bytes(path: str) -> Result[list[uint8], io.Error]` | Reads bytes, capped at 256 MiB. | | `fs.write_string` | `write_string(path: str, text: str) -> Result[None, io.Error]` | Creates or replaces a text file. | | `fs.write_bytes` | `write_bytes(path: str, bytes: list[uint8]) -> Result[None, io.Error]` | Creates or replaces a byte file. | | `fs.append_string` | `append_string(path: str, text: str) -> Result[None, io.Error]` | Appends text. | | `fs.append_bytes` | `append_bytes(path: str, bytes: list[uint8]) -> Result[None, io.Error]` | Appends bytes. | | `fs.create_dir` | `create_dir(path: str) -> Result[None, io.Error]` | Creates one directory. | | `fs.read_dir` | `read_dir(path: str) -> Result[list[str], io.Error]` | Returns sorted immediate entry names, with lossy host-path decoding. | | `fs.remove_file` | `remove_file(path: str) -> Result[None, io.Error]` | Removes a file. | | `fs.open` | `open(path: str) -> Result[fs.File, io.Error]` | Opens for reading. | | `fs.create` | `create(path: str) -> Result[fs.File, io.Error]` | Creates or truncates for writing. | | `fs.append` | `append(path: str) -> Result[fs.File, io.Error]` | Opens for append, creating if needed. | | `fs.File.read_all` | `read_all() -> Result[str, io.Error]` | Reads remaining strict UTF-8 text, capped at 256 MiB. | | `fs.File.read_bytes` | `read_bytes() -> Result[list[uint8], io.Error]` | Reads remaining bytes, capped at 256 MiB. | | `fs.File.write_all` | `write_all(text: str) -> Result[None, io.Error]` | Writes all text. | | `fs.File.write_bytes` | `write_bytes(bytes: list[uint8]) -> Result[None, io.Error]` | Writes all bytes. | | `fs.File.flush` | `flush() -> Result[None, io.Error]` | Flushes pending writes. | | `fs.File.close` | `close() -> None` | Closes the handle. | ## Control-Plane Modules See [Control-Plane Modules](/manual/control-plane). | API | Signature | | --- | --- | | `sys.args` | `args() -> list[str]` | | `sys.env` | `env(name: str) -> Option[str]` | | `sys.current_dir` | `current_dir() -> Result[str, io.Error]` | | `sys.unix_time_ms` | `unix_time_ms() -> int64` | | `sys.monotonic_time_ms` | `monotonic_time_ms() -> int64` | | `path.join` | `join(base: str, child: str) -> str` | | `path.parent` | `parent(path: str) -> Option[str]` | | `path.file_name` | `file_name(path: str) -> Option[str]` | | `path.extension` | `extension(path: str) -> Option[str]` | | `path.is_absolute` | `is_absolute(path: str) -> bool` | | `json.parse` | `parse(text: str) -> Result[json.Value, json.Error]` | | `json.dumps` | `dumps(value: json.Value, indent: Option[int64] = None) -> str` | | `json.is_null` | `is_null(value: json.Value) -> bool` | | `json.as_bool` | `as_bool(value: json.Value) -> Option[bool]` | | `json.as_int` | `as_int(value: json.Value) -> Option[int64]` | | `json.as_float` | `as_float(value: json.Value) -> Option[float64]` | | `json.into_string` | `into_string(value: own json.Value) -> Option[str]` | | `json.into_array` | `into_array(value: own json.Value) -> Option[list[json.Value]]` | | `json.into_object` | `into_object(value: own json.Value) -> Option[dict[str, json.Value]]` | | `json.is_valid` / `toml.is_valid` | `is_valid(text: str) -> bool` | | `json.stringify_map` / `toml.stringify_map` | `stringify_map(value: dict[str, str]) -> Result[str, str]` | | `json.parse_string_map` / `toml.parse_string_map` | `parse_string_map(text: str) -> Result[dict[str, str], str]` | | `log.debug/info/warn/error` | `(message: str, fields: dict[str, str]) -> None` | | `trace.event` | `(name: str, fields: dict[str, str]) -> None` | | `metrics.increment` | `(name: str, value: int64) -> None` | | `metrics.get` | `(name: str) -> int64` | | `metrics.reset` | `() -> None` | | `control.retry` | `retry[T, E](worker: def() -> Result[T, E], max_attempts: int32 = 3, initial_backoff: Duration = 0ms) -> Result[T, E]` | Metrics are process-global `int64` counters; missing names read as zero and overflow is a runtime diagnostic. Dynamic JSON object dumps and bounded JSON/TOML string maps serialize in sorted key order. See [JSON Module](/manual/json) and the control-plane chapter for exact value, limit, host-string/path, and telemetry-record rules. `control.retry` validates at least one attempt and a non-negative, host-representable backoff before invoking the worker. It runs its first attempt immediately, retries every `Err` with doubling delays, skips zero sleeps, returns the exact last `Err`, and performs no sleep or multiplication after the final attempt. Worker traps, backoff overflow, and current-task cancellation propagate. ## Network Constructors And HTTP Client Helpers See [Network Module](/manual/network) for behavior and examples. | API | Signature | | --- | --- | | `net.connect` | `connect(address: str) -> Result[net.TcpStream, io.Error]` | | `net.connect_timeout` | `connect_timeout(address: str, timeout: Duration) -> Result[net.TcpStream, io.Error]` | | `net.listen` | `listen(address: str) -> Result[net.TcpListener, io.Error]` | | `net.udp_bind` | `udp_bind(address: str) -> Result[net.UdpSocket, io.Error]` | | `net.http_listen` | `http_listen(address: str) -> Result[net.HttpListener, io.Error]` | | `net.websocket_listen` | `websocket_listen(address: str) -> Result[net.WebSocketListener, io.Error]` | | `net.websocket_connect` | `websocket_connect(url: str) -> Result[net.WebSocket, io.Error]` | | `net.websocket_connect_timeout` | `websocket_connect_timeout(url: str, timeout: Duration) -> Result[net.WebSocket, io.Error]` | | `net.unix_listen` | `unix_listen(path: str) -> Result[net.UnixListener, io.Error]` | | `net.unix_connect` | `unix_connect(path: str) -> Result[net.UnixStream, io.Error]` | | `net.unix_connect_timeout` | `unix_connect_timeout(path: str, timeout: Duration) -> Result[net.UnixStream, io.Error]` | | `net.tls_listen` | `tls_listen(address: str, cert_pem_path: str, key_pem_path: str) -> Result[net.TlsListener, io.Error]` | | `net.tls_connect` | `tls_connect(address: str, server_name: str, ca_pem_path: str) -> Result[net.TlsStream, io.Error]` | | `net.tls_connect_timeout` | `tls_connect_timeout(address: str, server_name: str, ca_pem_path: str, timeout: Duration) -> Result[net.TlsStream, io.Error]` | | `net.http_request_text` | `http_request_text(method: str, url: str, body: str, headers: dict[str, str]) -> Result[net.HttpResponse, io.Error]` | | `net.http_request_text_timeout` | `http_request_text_timeout(method: str, url: str, body: str, headers: dict[str, str], timeout: Duration) -> Result[net.HttpResponse, io.Error]` | | `net.http_request_bytes` | `http_request_bytes(method: str, url: str, bytes: list[uint8], headers: dict[str, str]) -> Result[net.HttpResponse, io.Error]` | | `net.http_request_bytes_timeout` | `http_request_bytes_timeout(method: str, url: str, bytes: list[uint8], headers: dict[str, str], timeout: Duration) -> Result[net.HttpResponse, io.Error]` | ## Network Resource Methods Bounded stream read counts are `1..=67108864`; UDP receive counts are `1..=65535`. Incoming HTTP parsing accepts at most 64 headers and 16 MiB of wire data per message; WebSocket limits are 64 MiB per message and 16 MiB per frame/write buffer. See [Network Module](/manual/network) for timeout, EOF, UTF-8, cancellation, and repeated-header contracts. | Type | API | Signature | | --- | --- | --- | | `net.TcpListener` | `accept` | `accept(timeout: Duration = ...) -> Result[net.TcpStream, io.Error]` | | `net.TcpListener` | `local_addr` | `local_addr() -> Result[str, io.Error]` | | `net.TcpListener` | `close` | `close() -> None` | | `net.TcpStream` | `read_all` | `read_all(timeout: Duration = ...) -> Result[str, io.Error]` | | `net.TcpStream` | `read_line` | `read_line(timeout: Duration = ...) -> Result[Option[str], io.Error]` | | `net.TcpStream` | `read_bytes` | `read_bytes(max_bytes: int32, timeout: Duration = ...) -> Result[Option[list[uint8]], io.Error]` | | `net.TcpStream` | `read_exact` | `read_exact(count: int32, timeout: Duration = ...) -> Result[list[uint8], io.Error]` | | `net.TcpStream` | `write_all` | `write_all(text: str, timeout: Duration = ...) -> Result[None, io.Error]` | | `net.TcpStream` | `write_bytes` | `write_bytes(bytes: list[uint8], timeout: Duration = ...) -> Result[None, io.Error]` | | `net.TcpStream` | `flush` | `flush() -> Result[None, io.Error]` | | `net.TcpStream` | `local_addr` | `local_addr() -> Result[str, io.Error]` | | `net.TcpStream` | `peer_addr` | `peer_addr() -> Result[str, io.Error]` | | `net.TcpStream` | `shutdown_read` | `shutdown_read() -> Result[None, io.Error]` | | `net.TcpStream` | `shutdown_write` | `shutdown_write() -> Result[None, io.Error]` | | `net.TcpStream` | `shutdown_both` | `shutdown_both() -> Result[None, io.Error]` | | `net.TcpStream` | `close` | `close() -> None` | | `net.UdpSocket` | `send_text` | `send_text(address: str, text: str, timeout: Duration = ...) -> Result[None, io.Error]` | | `net.UdpSocket` | `send_bytes` | `send_bytes(address: str, bytes: list[uint8], timeout: Duration = ...) -> Result[None, io.Error]` | | `net.UdpSocket` | `recv` | `recv(max_bytes: int32, timeout: Duration = ...) -> Result[Option[list[uint8]], io.Error]` | | `net.UdpSocket` | `recv_from` | `recv_from(max_bytes: int32, timeout: Duration = ...) -> Result[Option[net.UdpDatagram], io.Error]` | | `net.UdpSocket` | `local_addr` | `local_addr() -> Result[str, io.Error]` | | `net.UdpSocket` | `peer_addr` | `peer_addr() -> Result[str, io.Error]` | | `net.UdpSocket` | `close` | `close() -> None` | | `net.UdpDatagram` | `address` | `address() -> str` | | `net.UdpDatagram` | `bytes` | `bytes() -> list[uint8]` | | `net.UdpDatagram` | `text` | `text() -> Result[str, io.Error]` | | `net.HttpListener` | `accept` | `accept(timeout: Duration = ...) -> Result[net.HttpExchange, io.Error]` | | `net.HttpListener` | `local_addr` | `local_addr() -> Result[str, io.Error]` | | `net.HttpListener` | `close` | `close() -> None` | | `net.HttpExchange` | `method` | `method() -> str` | | `net.HttpExchange` | `path` | `path() -> str` | | `net.HttpExchange` | `headers` | `headers() -> dict[str, str]` | | `net.HttpExchange` | `body_text` | `body_text() -> Result[str, io.Error]` | | `net.HttpExchange` | `body_bytes` | `body_bytes() -> list[uint8]` | | `net.HttpExchange` | `respond_text` | `respond_text(status: int32, text: own str, headers: own dict[str, str]) -> Result[None, io.Error]` | | `net.HttpExchange` | `respond_bytes` | `respond_bytes(status: int32, bytes: own list[uint8], headers: own dict[str, str]) -> Result[None, io.Error]` | | `net.HttpResponse` | `status` | `status() -> int32` | | `net.HttpResponse` | `reason` | `reason() -> str` | | `net.HttpResponse` | `headers` | `headers() -> dict[str, str]` | | `net.HttpResponse` | `text` | `text() -> Result[str, io.Error]` | | `net.HttpResponse` | `bytes` | `bytes() -> list[uint8]` | | `net.WebSocketListener` | `accept` | `accept(timeout: Duration = ...) -> Result[net.WebSocket, io.Error]` | | `net.WebSocketListener` | `local_addr` | `local_addr() -> Result[str, io.Error]` | | `net.WebSocket` | `send_text` | `send_text(text: str, timeout: Duration = ...) -> Result[None, io.Error]` | | `net.WebSocket` | `send_bytes` | `send_bytes(bytes: list[uint8], timeout: Duration = ...) -> Result[None, io.Error]` | | `net.WebSocket` | `recv_text` | `recv_text(timeout: Duration = ...) -> Result[Option[str], io.Error]` | | `net.WebSocket` | `recv_bytes` | `recv_bytes(timeout: Duration = ...) -> Result[Option[list[uint8]], io.Error]` | | `net.WebSocket` | `close` | `close() -> None` | | `net.UnixListener` | `accept` | `accept(timeout: Duration = ...) -> Result[net.UnixStream, io.Error]` | | `net.UnixListener` | `close` | `close() -> None` | | `net.UnixStream` | `read_line` | `read_line(timeout: Duration = ...) -> Result[Option[str], io.Error]` | | `net.UnixStream` | `read_exact` | `read_exact(count: int32, timeout: Duration = ...) -> Result[list[uint8], io.Error]` | | `net.UnixStream` | `write_all` | `write_all(text: str, timeout: Duration = ...) -> Result[None, io.Error]` | | `net.UnixStream` | `close` | `close() -> None` | | `net.TlsListener` | `accept` | `accept(timeout: Duration = ...) -> Result[net.TlsStream, io.Error]` | | `net.TlsListener` | `local_addr` | `local_addr() -> Result[str, io.Error]` | | `net.TlsListener` | `close` | `close() -> None` | | `net.TlsStream` | `read_line` | `read_line(timeout: Duration = ...) -> Result[Option[str], io.Error]` | | `net.TlsStream` | `read_exact` | `read_exact(count: int32, timeout: Duration = ...) -> Result[list[uint8], io.Error]` | | `net.TlsStream` | `write_all` | `write_all(text: str, timeout: Duration = ...) -> Result[None, io.Error]` | | `net.TlsStream` | `close` | `close() -> None` | ## Process See [Process Module](/manual/process) for defaults, groups, and supervisor behavior. | API | Signature | | --- | --- | | `process.inherit` | `inherit() -> process.Stdio` | | `process.null` | `null() -> process.Stdio` | | `process.pipe` | `pipe() -> process.Stdio` | | `process.supervisor` | `supervisor() -> process.Supervisor` | | `process.start` | `start(command: list[str], cwd: Option[str] = ..., env: dict[str, str] = ..., stdin: process.Stdio = ..., stdout: process.Stdio = ..., stderr: process.Stdio = ..., group: bool = ...) -> Result[process.Child, process.Error]` | | `process.run` | `run(command: list[str], cwd: Option[str] = ..., env: dict[str, str] = ..., stdin: process.Stdio = ..., stdout: process.Stdio = ..., stderr: process.Stdio = ..., timeout: Duration = ..., group: bool = ...) -> Result[process.Completed, process.Error]` | | `process.Child.stdin` | `stdin() -> Option[process.Pipe]` | | `process.Child.stdout` | `stdout() -> Option[process.Pipe]` | | `process.Child.stderr` | `stderr() -> Option[process.Pipe]` | | `process.Child.wait` | `wait(timeout: Duration = ...) -> process.Wait` | | `process.Child.wait_or_none` | `wait_or_none(timeout: Duration = ...) -> Result[Option[process.ExitStatus], process.Error]` | | `process.Child.wait_ok` | `wait_ok(timeout: Duration = ...) -> Result[process.ExitStatus, process.Error]` | | `process.Child.kill` | `kill() -> Result[None, process.Error]` | | `process.Child.terminate` | `terminate() -> Result[None, process.Error]` | | `process.Child.close` | `close() -> None` | | `process.Pipe.read_all` | `read_all() -> Result[str, process.Error]` | | `process.Pipe.read_line` | `read_line(timeout: Duration = ...) -> Result[Option[str], process.Error]` | | `process.Pipe.read_bytes` | `read_bytes(max_bytes: int32, timeout: Duration = ...) -> Result[Option[list[uint8]], process.Error]` | | `process.Pipe.write_all` | `write_all(text: str, timeout: Duration = ...) -> Result[None, process.Error]` | | `process.Pipe.write_bytes` | `write_bytes(bytes: list[uint8], timeout: Duration = ...) -> Result[None, process.Error]` | | `process.Pipe.flush` | `flush() -> Result[None, process.Error]` | | `process.Pipe.close` | `close() -> None` | | `process.Completed.status` | `status() -> process.ExitStatus` | | `process.Completed.success` | `success() -> bool` | | `process.Completed.stdout` | `stdout() -> str` | | `process.Completed.stdout_bytes` | `stdout_bytes() -> list[uint8]` | | `process.Completed.stderr` | `stderr() -> str` | | `process.Completed.stderr_bytes` | `stderr_bytes() -> list[uint8]` | | `process.Completed.check` | `check() -> Result[None, process.Error]` | | `process.Supervisor.start` | `start(name: own str, command: own list[str], cwd: own Option[str] = ..., env: own dict[str, str] = ..., stdin: own process.Stdio = ..., stdout: own process.Stdio = ..., stderr: own process.Stdio = ..., restart: own process.RestartPolicy = ..., backoff: own Duration = ..., max_restarts: own int32 = ..., group: own bool = ...) -> Result[None, process.Error]` | | `process.Supervisor.wait` | `wait(timeout: Duration = ...) -> process.SupervisorWait` | | `process.Supervisor.wait_or_none` | `wait_or_none(timeout: Duration = ...) -> Result[Option[process.SupervisorEvent], process.Error]` | | `process.Supervisor.stop` | `stop() -> Result[None, process.Error]` | | `process.Supervisor.is_empty` | `is_empty() -> bool` | | `process.Supervisor.close` | `close() -> None` | Pipe `read_bytes` returns `Ok(None)` only at EOF; timeout and cancellation are `process.Error` variants. Whole/captured reads are capped at 64 MiB. `process.Completed.stdout()` and `.stderr()` raise a runtime diagnostic on invalid UTF-8, so byte accessors are the safe boundary for untrusted output. ## Builtin Enum Variants | Type | Variants | | --- | --- | | `Option[T]` | `Some(value: own T)`, `None` | | `Result[T, E]` | `Ok(value: own T)`, `Err(error: own E)` | | `SendError[T]` | `Closed(value: own T)`, `Cancelled(value: own T)`, `TimedOut(value: own T)`, `Full(value: own T)` | | `QueueReceive[T]` | `Item(value: own T)`, `Closed`, `TimedOut`, `Cancelled` | | `TaskResult[T]` | `Ready(value: own T)`, `Error(message: own str)`, `TimedOut`, `Cancelled` | | `SelectOutcome[Q, T]` | `Queue(index: own int64, outcome: own QueueReceive[Q])`, `Task(index: own int64, outcome: own TaskResult[T])`, `Deadline(index: own int64)`, `Cancelled` | | `WaitAny[T]` | `Ready(index: own int64, value: own T)`, `Error(index: own int64, message: own str)`, `TimedOut`, `Cancelled` | | `WaitAll[T]` | `Ready(values: own list[T])`, `Error(index: own int64, message: own str)`, `TimedOut`, `Cancelled` | | `bytes.Error` | `InvalidUtf8(index: own int32)`, `InvalidHexLength(length: own int32)`, `InvalidHexDigit(index: own int32, byte: own uint8)`, `InvalidBase64(index: own int32)` | | `io.Error` | `NotFound`, `PermissionDenied`, `AlreadyExists`, `IsDirectory`, `ConnectionRefused`, `ConnectionReset`, `ConnectionAborted`, `NotConnected`, `AddrInUse`, `AddrNotAvailable`, `BrokenPipe`, `TimedOut`, `WouldBlock`, `UnexpectedEof`, `InvalidInput`, `InvalidData`, `Closed`, `Cancelled`, `Other(message: own str)` | | `process.Stdio` | `Inherit`, `Null`, `Pipe` | | `process.ExitStatus` | `Exited(code: own int32)`, `Signaled(signal: own int32)` | | `process.Wait` | `Exited(status: own process.ExitStatus)`, `TimedOut`, `Cancelled`, `Failed(error: own process.Error)` | | `process.RestartPolicy` | `Never`, `OnFailure`, `Always` | | `process.Error` | `NoCommand`, `TimedOut`, `Cancelled`, `Io(error: own io.Error)`, `Spawn(message: own str)`, `Other(message: own str)` | | `process.SupervisorEvent` | `Exited(name: own str, status: own process.ExitStatus, restart_count: own int32)`, `Restarted(name: own str, status: own process.ExitStatus, restart_count: own int32)`, `Failed(name: own str, error: own process.Error, restart_count: own int32)` | | `process.SupervisorWait` | `Event(event: own process.SupervisorEvent)`, `TimedOut`, `Cancelled` | ## Source: docs/manual/assertions.md # Assertions An assertion states an invariant that must hold for execution to continue. Assertions are for programmer errors and internal consistency, not recoverable input or protocol failures. Use a typed `Result` when a caller should be able to handle the outcome. ## Grammar The two forms are: assert condition assert condition, message The normative production is `assert-statement = "assert", non-tuple-expression, [ ",", non-tuple-expression ], statement-end`. The top-level comma belongs to the assertion statement rather than either operand. There is no parenthesized statement form, trailing comma, or additional argument. Either operand may use ordinary delimiter continuation. For example, the condition may be grouped across physical lines; that remains an assertion statement and does not create an `assert(...)` call form. `assert` is a reserved keyword. An assertion is valid anywhere an ordinary statement is valid, including a script-style entry module. A file still cannot combine executable top-level statements with a local `main`. ## Typing Rules The condition must have exactly type `bool`; numbers, strings, collections, resources, and class values are not converted by truthiness. The optional message must have exactly type `str`. An assertion is a fallthrough statement for static control-flow analysis. It does not establish a permanent narrowing or value refinement, and a statically false condition is not treated as a substitute for a return. ## Runtime Semantics The condition evaluates exactly once. If it is `true`, execution continues and the optional message is not evaluated. If it is `false`, the message evaluates exactly once and the assertion traps. Without a message, the exact diagnostic text is `assertion failed`. A custom message is preserved exactly, including an empty or whitespace-only str. A trap while evaluating the condition or message occurs first and prevents the assertion trap. ### Operand Introspection When the whole condition is one of the following non-consuming operations, a failed assertion reports the two values used by that operation: assert left == right assert left != right assert left < right assert left <= right assert left > right assert left >= right assert item in collection Parentheses around the whole condition preserve introspection. Each operand is evaluated once from left to right. The comparison or membership operation uses those captured values. The values are rendered only on the failure edge with the ordinary `str()` contract, before the lazy message is evaluated. Comparison chains, `not in`, Boolean combinations, calls that return `bool`, and consuming custom comparison dispatch keep the ordinary assertion diagnostic without operand values. Aura does not clone, move twice, or make a second observation of an operand to produce a diagnostic. Membership is the builtin operation on `list`, `dict`, `set`, and `str`; Aura has no custom membership protocol. The following verified program demonstrates successful fallthrough and a lazy message: ```aura def build_message() -> str: print("message evaluated") return "unexpected arithmetic result" def main(): print("before") assert 2 + 2 == 4, build_message() assert true print("after") ``` Its output is: ```text before after ``` ## Ownership And Evaluation Order Condition effects complete before any message effect. Values moved, copied, borrowed, or mutated while evaluating either expression obey the ordinary expression rules; an assertion inserts no hidden clone. Because the message belongs only to the false branch, its moves and mutations do not occur on the true path. Assertion failure exits active `with` scopes. Registered cleanup runs exactly once in reverse nesting order. The assertion diagnostic is established before cleanup begins and remains primary if cleanup also fails. ## Diagnostics `AU2002` reports a condition whose type is not `bool` or a message whose type is not `str`. The primary location points at the `assert` keyword. `AU4001` reports a failed assertion at runtime. It uses the same keyword location and the exact default or custom message described above. A condition or message trap keeps its own diagnostic code, message, and span instead. An introspected comparison appends `left = ...` and `right = ...` notes in operand order. Membership appends `item = ...` and `collection = ...`. Each rendered value is bounded to 4,096 UTF-8 bytes. Longer values end with `... (truncated)` at a valid UTF-8 boundary. Structured diagnostic schema 1 includes `assertion_operands` only for an introspected failure. Its two entries contain `label`, `type`, `value`, and `truncated`. Other diagnostics omit the field. ## Backend Support The checker and MIR lowering are shared. `aura run`, directly emitted native programs, and auto-backend builds preserve the same evaluation order, exact messages, `AU4001` keyword span, standard-output ordering, and cleanup precedence. File-level `aura test` reports an assertion trap as a failed test program with the same diagnostic. Function-level `test_*` cases and JSON test records preserve the same structured operand data. ## Limits And Implementation-Defined Behavior Aura has no assertion-stripping mode, optimization flag, environment switch, or backend option. Every accepted assertion executes in every build. Message contents are preserved exactly. The human diagnostic renderer adds its normal `error[AU4001]` prefix, source context, and bounded operand notes when the condition qualifies for introspection. Assertion failure terminates the current Aura execution path; it is not a catchable exception. Use `Result` for recoverable validation. ## Status Both assertion forms and their base sequencing, cleanup, top-level, and no-strip behavior are accepted under ADR-0024. Aura 0.3 adds the bounded two-operand diagnostic contract under ADR-0045. Exception statements, `raise`, and catchable assertion failures are not part of Aura 0.3. ## Source: docs/manual/bytes.md # Bytes, Text Codecs, And SHA-256 Aura represents an owned sequence of bytes as `list[uint8]`. There is no separate `Bytes` nominal type and no implicit conversion between text and bytes. UTF-8 conversion is available on `str`; hexadecimal, base64, and SHA-256 operations live in the built-in `bytes` module. ## Public API | API | Signature | Result | | --- | --- | --- | | `str.to_bytes` | `to_bytes() -> list[uint8]` | A fresh list containing the receiver's exact UTF-8 encoding. | | `str.from_bytes` | `from_bytes(bytes: list[uint8]) -> Result[str, bytes.Error]` | A fresh str when `bytes` is valid UTF-8, otherwise a typed error. | | `bytes.hex_encode` | `hex_encode(value: list[uint8]) -> str` | Lowercase hexadecimal with two ASCII digits per byte. | | `bytes.hex_decode` | `hex_decode(text: str) -> Result[list[uint8], bytes.Error]` | Strict hexadecimal decoding. | | `bytes.base64_encode` | `base64_encode(value: list[uint8]) -> str` | RFC 4648 standard-alphabet base64 with canonical padding. | | `bytes.base64_decode` | `base64_decode(text: str) -> Result[list[uint8], bytes.Error]` | Strict canonical RFC 4648 decoding. | | `bytes.sha256` | `sha256(value: list[uint8]) -> list[uint8]` | The raw 32-byte SHA-256 digest of `value`. | | `bytes.sha256_string` | `sha256_string(text: str) -> list[uint8]` | The raw SHA-256 digest of the text's exact UTF-8 bytes. | The displayed parameter names are part of the callable contract and may be used as named arguments. Bare `str` and `list[uint8]` parameters use the ordinary shared-borrow default. None of these calls consumes or mutates an input, and every returned collection or str is a fresh owned value. `str.from_bytes` is an associated str method, so it is called on the type, as in `str.from_bytes(payload)`. It is not a `str(...)` constructor. The `encoding` parameter name is reserved for a possible future extension; Aura 0.3 accepts no encoding argument on either str conversion. ## Error Model Malformed input is recoverable when its required offset or length fits the retained `int32` error-payload domain, and returns one of these `bytes.Error` variants: | Variant | Payload meaning | | --- | --- | | `InvalidUtf8(index: int32)` | `index` is the zero-based byte offset at which the first invalid UTF-8 sequence begins. | | `InvalidHexLength(length: int32)` | `length` is the odd UTF-8 byte length of the hexadecimal input. | | `InvalidHexDigit(index: int32, byte: uint8)` | `index` identifies the first non-hex byte and `byte` is its exact value. | | `InvalidBase64(index: int32)` | `index` identifies the first byte that violates canonical base64; a missing required byte is reported at the position immediately after the input. | All positions and lengths are byte counts, not Unicode-scalar positions. Hexadecimal length is validated before individual digits, so an odd input returns `InvalidHexLength` even when it also contains a non-hex character. For base64, an invalid alphabet byte reports that byte, a missing required padding byte reports `text.byte_len()`, and nonzero discarded bits report the last data symbol that contains them. If the exact malformed-data offset or length exceeds `2147483647`, Aura cannot construct the retained `int32` payload without losing information. That metadata overflow traps with `AU4005`; it is never truncated, clamped, or wrapped into a `bytes.Error`. Resource or allocation failure likewise is not a `bytes.Error` variant and traps with `AU4005` as described below. ## UTF-8 Conversion `to_bytes` emits the standard UTF-8 encoding of every Unicode scalar in the `str`. Embedded NUL bytes are preserved. A leading U+FEFF is encoded as the ordinary bytes `ef bb bf`; it is not inserted, removed, or treated as a byte-order marker. Conversion performs no normalization, case folding, or newline replacement. `from_bytes` validates strictly. It never inserts U+FFFD and never decodes a prefix while discarding a malformed suffix. On success it preserves the byte sequence exactly, including embedded NUL and a leading UTF-8 encoding of U+FEFF. After matching `str.from_bytes(text.to_bytes())`, a successful `case Result.Ok(decoded):` branch therefore satisfies `decoded == text`. ## Hexadecimal Hex encoding emits exactly two lowercase ASCII digits for each input byte, using `0` through `9` and `a` through `f`. Empty input produces the empty `str`. Hex decoding accepts either lowercase or uppercase ASCII digits. It does not accept a `0x` prefix, signs, separators, whitespace, or non-ASCII digits. Empty text produces an empty list. An even-length input is processed from left to right, and the first invalid byte determines `InvalidHexDigit`. ## Base64 Base64 uses the RFC 4648 standard alphabet `A-Z`, `a-z`, `0-9`, `+`, and `/`. Encoding always emits the canonical number of trailing `=` bytes. Empty input produces empty text. Decoding accepts only that standard alphabet and canonical padding. It rejects the URL-safe `-` and `_` characters, whitespace, separators, omitted padding, excess padding, padding in a non-final quartet, data after padding, and nonzero discarded bits. It does not ignore malformed bytes and does not silently repair input. Successfully decoded output may contain arbitrary bytes and is not required to be UTF-8. ## SHA-256 `bytes.sha256` is the FIPS 180-4 SHA-256 function. It returns a fresh list of exactly 32 digest bytes. `bytes.sha256_string(text)` is exactly equivalent to hashing `text.to_bytes()`; it adds no terminator and performs no text normalization or newline conversion. The digest is raw bytes, not hexadecimal text. Compose the operations when a text digest is needed: `bytes.hex_encode(bytes.sha256(payload))`. SHA-256 is a general-purpose digest. It is not encryption, a message authentication code, a signature, a password hash, a random generator, or a constant-time equality operation. This module does not imply suitability for any of those uses. ## Example ```aura import bytes def main(): text = "Aura 🌌" encoded = text.to_bytes() print(bytes.hex_encode(encoded)) match str.from_bytes(encoded): case Result.Ok(decoded): print(decoded) case Result.Err(error): print(error) payload: list[uint8] = [0, 1, 254, 255] print(bytes.base64_encode(payload)) print(bytes.hex_encode(bytes.sha256_string("abc"))) print(bytes.hex_encode(encoded)) ``` The program prints the UTF-8 bytes as lowercase hex, the original text, `AAH+/w==`, the standard SHA-256 digest of `abc`, and the same UTF-8 hex again. The final line demonstrates that conversion did not consume `encoded`. ## Grammar The Bytes surface adds no source-language grammar. `list[uint8]`, `import bytes`, associated calls, method calls, module calls, named arguments, and `Result` patterns use the ordinary forms defined elsewhere in this Manual. Aura 0.3 has no byte-string literal. ## Typing Rules `list[uint8]` is the sole built-in bytes representation. The signatures in the Public API table are normative. There is no implicit `str`/byte-list coercion and no overload that accepts another integer element type. `bytes.Error` is a copy-valued enum because all of its payloads are copy types. Its offsets and lengths remain `int32` as the current error-payload contract; that fixed payload type is independent of the public `str` and `list` length domains. The invalid hexadecimal byte payload is `uint8`. Required malformed-data metadata above the `int32` maximum traps with `AU4005` instead of constructing a lossy payload. Match handling follows the ordinary exhaustive enum rules. All successful functions return owned values. Ordinary bare inputs grant shared access for the call, so a caller may reuse the input after `to_bytes`, `from_bytes`, encode, decode, or hash. Explicit `own` is neither required nor implied by these signatures. An `encoding` positional or named argument is not part of the 0.3 signature and is rejected by ordinary argument checking. A user-defined source module whose final component is named `bytes` does not acquire this built-in API: built-in behavior belongs only to the compiler-synthesized `bytes` module. ## Runtime Semantics All operations first evaluate the receiver, then supplied arguments in source order. They observe the input value produced at that point and allocate a fresh result. No operation changes an input list or str. UTF-8 validation returns the first invalid sequence start. Hex decoding first checks even byte length, then decodes pairs from left to right. Base64 decoding validates the canonical standard-alphabet representation rather than using a whitespace-tolerant or unpadded mode. SHA-256 follows FIPS 180-4 over the exact input byte sequence. In particular, the digest of empty input is `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` when rendered through `hex_encode`, and the digest of `abc` is `ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad`. ## Ownership And Evaluation Order Every public input uses shared access. A str receiver remains usable after `to_bytes`, and a byte list remains usable after `from_bytes`, an encoder, or `sha256`. A text argument remains usable after a decoder or `sha256_string`. Returned Strings and vectors do not alias mutable storage in the input. Nested calls use ordinary inside-out evaluation. For example, `bytes.hex_encode(bytes.sha256(payload))` first hashes a shared observation of `payload`, then passes the fresh digest to `hex_encode`; `payload` remains owned by the caller. When arguments have other observable effects, their source order remains the language-wide call order. ## Diagnostics Malformed UTF-8, hex, and base64 return `bytes.Error` when the exact error offset or length fits its retained `int32` payload. Required metadata above `2147483647` traps with `AU4005` rather than emitting a truncated or wrapped typed error. Static misuse uses the ordinary name/type/argument codes, including `AU2001`, `AU2002`, and `AU2004`. `AU4005` reports a fresh codec destination above the fixed 2,147,483,647-byte safety ceiling, arithmetic overflow while computing the expanded destination size, error metadata outside the retained `int32` payload domain, or allocation failure. This codec output/resource boundary is independent of the public str and `list` length domains. The operation produces no partial successful value. ## Backend Support The MIR and direct backends implement the same UTF-8, hex, base64, and SHA-256 contract and must return identical bytes, text, variants, offsets, and runtime diagnostics. Both backends use the same strict codec policy. The maintained backend-parity fixture matrix covers successful and malformed inputs. Compiler analysis and the language server expose the same canonical `bytes` module, `bytes.Error` variants, str methods, named parameters, and return types as the runtime surface. ## Limits And Implementation-Defined Behavior Codec inputs have no separate byte-count cap. Each fresh str or `list[uint8]` destination produced by a byte conversion, encoder, or decoder has a fixed safety ceiling of 2,147,483,647 bytes. This is a codec output/resource boundary independent of the public str and `list` length domains. Hex output requires `2 * input_length` bytes. Padded base64 output requires `4 * ceil(input_length / 3)` bytes. Operations preflight the destination size before allocating; a destination exactly at the ceiling is accepted when allocation succeeds, and the first larger destination traps with `AU4005`. Because the input domain is wider than a `bytes.Error` payload, malformed input whose exact reported offset or length exceeds `2147483647` also traps with `AU4005`. Actual allocation success within the codec destination ceiling is host-dependent. SHA-256 output is always 32 bytes. Codec output, errors, and offsets are not host-dependent. Aura 0.3 does not provide alternate text encodings, URL-safe or unpadded base64, streaming codecs, incremental hashing, HMAC, password hashing, constant-time digest comparison, a distinct mutable byte buffer, or implicit text conversion. The reserved `encoding` parameter is not implemented. ## Status `list[uint8]` is the implemented Aura 0.3 bytes type. The conversion, codec, error, and hash policy on this page is implemented as the Phase 3 control-plane surface and is accepted under ADR-0023. Derived class/enum codecs and schemas remain deferred beyond Phase 6. ## Source: docs/manual/classes.md # Classes Classes define nominal product types: one value contains a fixed set of named fields and exposes class methods. Ordinary classes are move types unless declared `copy class`. The complete syntax is in [Grammar](/manual/grammar#classes). Class names, field types, defaults, methods, visibility, constructors, ownership category, and recursive layout are all statically checked. ## Declaration ```aura class Point: x: float64 y: float64 ``` A class body contains one or more fields, methods, or `pass` entries. Fields and methods may be interleaved. Field names must be unique among fields, and method names must be unique among methods. Every field has an explicit type. A field may have a default expression of exactly that type: ```aura class Server: host: str = "127.0.0.1" port: int32 = 8080 ``` Field defaults are evaluated afresh for each construction where the field is omitted. They are not shared mutable singletons. A default is checked in declaration context, not in the caller's local scope. Generic classes declare bounded or unbounded type parameters after the name: ```aura class Box[T]: value: T class NamedBox[T: Named]: value: T ``` Type parameter names must be unique, all field and method types must be known with the correct arity, and every concrete substitution must satisfy its bounds. Generic arguments are invariant. See [Generics And Traits](/manual/generics-and-traits). ## Construction Calling the class name constructs a value. Arguments may be positional in field declaration order, named by field, or positional followed by named: ```aura point = Point(3.0, 4.0) server = Server() custom = Server("0.0.0.0", port=9090) named = Point(x=3.0, y=4.0) ``` Every constructor field is an owned position. Conceptually, `Point` exposes `Point(x: own float64, y: own float64)` and `Box[T]` exposes `Box(value: own T)`: a non-copy argument moves into the new object. Defaults likewise create fresh owned field values. Construction follows these rules: 1. positional arguments fill fields in declaration order 2. positional arguments cannot follow a named argument 3. a field cannot be supplied more than once 4. an unknown field name or excess positional argument is rejected 5. every field without a default must be supplied 6. each provided or default value must have the field's exact substituted type 7. every field argument is `own`; constructing with a move value consumes it, while copy values are duplicated Every supplied field expression is evaluated first, in call-site source order. Its copy or move result is captured into the owned field slot before the next supplied expression begins, so later side effects cannot change an earlier captured field value. Aura then evaluates the defaults for omitted fields in field declaration order. Binding positional or named arguments to field slots never reorders evaluation, and supplying a field suppresses that field's default completely. Generic arguments may be explicit: ```aura box = Box[int32](value=42) ``` Without explicit arguments, the checker infers them from provided fields or an expected class type. Every declared type parameter must resolve, even when it appears only in an omitted/defaulted field. ## Visibility And Construction Across Modules Classes, fields, and methods are private to their defining module unless marked `public`: ```aura public class Counter: public value: int32 = 0 public def get(self) -> int32: return self.value ``` Another module may import only a `public class`. It may read or call only public members. A cross-module constructor may explicitly initialize only public fields. Consequently, a private field on a publicly constructed class must have a declaration default; otherwise an external caller cannot satisfy the required field. Imported declarations retain their defining module identity for private-access checks. See [Names And Scopes](/manual/names-and-scopes#imports). ## Methods And Receivers ```aura class Counter: value: int32 = 0 def get(self) -> int32: return self.value def increment(mut self): self.value += 1 def into_value(own self) -> int32: return self.value def zero() -> Counter: return Counter(value=0) ``` The receiver, when present, is the first method parameter: | Receiver | Call contract | | --- | --- | | `self` | Shared receiver and the default spelling. It can read, but cannot mutate or move non-copy fields out. | | `own self` | Consuming receiver. A non-copy instance is moved into the call. | | `mut self` | Exclusive mutable receiver. The call requires a mutable place and may mutate it. | | none | Associated method. It is called through the type, not an instance. | ```aura mut counter = Counter.zero() counter.increment() print(counter.get()) value = counter.into_value() ``` Methods otherwise follow the function rules for generic parameters, ordinary parameters, defaults, and owned returns. Ordinary parameter names are unique and cannot collide with a declared `self` receiver. A typed first parameter such as `self: Counter` is not a receiver and is rejected with a diagnostic naming the valid forms. `Self` may be used in class method parameter and return type positions and denotes the enclosing class specialization. An associated method has no implicit `self` and is called as `Counter.zero()`. Instance syntax is reserved for methods with a compatible receiver and for trait methods selected for the instance type. ## Mutation A field assignment requires a mutable base place: ```aura mut counter = Counter.zero() counter.value = 10 counter.increment() ``` An owned local is mutable only when introduced with `mut`. Inside a `mut self` method, `self` is a mutable place even though parameter bindings themselves are not reassigned. Inside shared `self` (whether written `self` or `self`), mutation through `self` is rejected. Moving one non-copy field from an owned class partially moves that value. Disjoint fields remain usable, but use of the complete class is rejected until the moved field is reinitialized. See [Ownership And Borrowing](/manual/ownership-and-borrowing#partial-moves-and-reinitialization). ## Returning Fields A consuming receiver may return an owned field because it owns the class value: ```aura class User: name: str def into_name(own self) -> str: return self.name ``` A shared-borrowed receiver cannot move an owned field. When the field type supports cloning, clone to produce an owned result: ```aura class User: name: str def name_copy(self) -> str: return self.name.clone() ``` Returning a copy-valued field produces an ordinary independent copy: ```aura class Counter: value: int32 def value_copy(self) -> int32: return self.value ``` Returning a non-copy field requires ownership: clone it when clone-safe, or consume the owner with `own self`. Return annotations do not carry a source label or reserve an aliasing contract. See [Functions](/manual/functions#owned-returns). ## `copy class` ```aura copy class Pair: left: int32 right: int32 ``` A `copy class` value is duplicated by assignment and by-value use. The declaration is valid only when every field is statically copyable. A `str`, collection, resource, ordinary class, or enum with move payloads therefore prevents copy-class declaration. Copyability is structural through copy classes and eligible enum payloads, but generic type parameters are not assumed copyable merely because one later instantiation happens to use a copy type. The complete current categories are listed in [Types](/manual/types#copy-and-move-categories). ## Recursive Fields And `indirect` A field layout cannot contain its class again through an all-direct class-field path. This includes direct self-recursion, recursion nested inside another type, and mutual recursion through other classes. Mark a field `indirect` to break the direct layout cycle: ```aura class Node: value: int32 next: indirect Option[Node] = Option.None ``` `indirect` applies to the complete following type reference. It is a field-layout marker, not a general pointer expression and not valid as an arbitrary runtime operation. At least one field on every recursive layout cycle must provide the indirection. ## User Resource Classes A non-generic user class may be managed by `with` when it declares this exact instance method shape: ```aura class Resource: name: str def close(mut self) -> None: print("closing " + self.name) ``` The method must be named `close`, use `mut self`, take no ordinary parameters, and return `None`. Generic user resource classes are not supported by `with` in Aura 0.3. ```aura with resource = Resource(name="db"): print("using resource") ``` `with` consumes the resource expression into a fresh mutable managed binding. That binding cannot be moved out while cleanup is active. Cleanup runs exactly once for the registration on normal and maintained abnormal exits, in reverse nesting order. See [Execution Model](/manual/execution-model#resource-lifetime-and-cleanup). ## Grammar The normative productions for `class`, `copy class`, visibility, type parameters, fields, field defaults, `indirect`, methods, receivers, and associated methods are in [Grammar](/manual/grammar#classes). A class suite contains fields, methods, and/or `pass`; Aura has no separate constructor, property, inheritance, or destructor declaration grammar. ## Typing Rules Classes are nominal and generic arguments are invariant. Every field has one declared type; defaults and constructor arguments must have that exact type after substitution. Constructor binding follows field declaration order, requires every non-defaulted accessible field, and rejects duplicate, unknown, inaccessible private, or excess arguments. Receiver mode controls legal field access. A `copy class` requires every field to be statically copyable, and every direct recursive layout cycle requires `indirect`. Cross-module visibility and the exact user-resource `close(mut self) -> None` shape are checked before lowering. ## Runtime Semantics Construction creates one fresh nominal value. Every supplied field expression is evaluated first in call-site source order and its copy or move result is captured into the owned field slot before later field-expression side effects, followed by every omitted field default in field declaration order. Each default is evaluated afresh; binding the resulting values to field slots does not reorder evaluation, and a supplied field's default is not evaluated. Instance calls invoke the statically selected inherent or trait method; associated methods receive no implicit instance. Class equality compares the nominal class identity and represented field values. A managed user-resource class is closed exactly once by its active `with` registration under the cleanup rules in [Execution Model](/manual/execution-model). ## Ownership And Evaluation Order Every constructor field is an owned destination: copy arguments are copied and non-copy arguments move into the new value. Ordinary classes move; valid `copy class` values copy. Shared receivers read, `own self` consumes, and `mut self` requires an exclusive mutable place. Moving an owned non-copy field partially moves its class until that field is reinitialized; moving through a borrowed receiver is rejected. Aura inserts no hidden clone at a constructor, field, receiver, or return boundary. Constructor side effects follow the supplied-then-default order above even when named arguments bind fields in a different declaration order. ## Diagnostics `AU1101` reports malformed class, field, method, and receiver syntax. `AU2001` reports unresolved classes, field types, methods, and members. `AU2002` covers field/default/constructor type mismatch, generic arity or bound failure, and an invalid non-copy field in a `copy class`. `AU2004` reports constructor or method argument-binding failures. `AU2999` covers duplicate declarations, invalid visibility or recursive layout, unsupported member use, and other class rejections without a narrower category. `AU3001` reports use of a moved class or field. `AU3002` reports overlapping receiver/argument borrows, moving a field through shared access, or an invalid user-resource close contract. `AU3003` reports mutation through an immutable class place, including a shared `self` receiver, and `AU3004` reports an invalid ownership or receiver mode. A field default, method, or cleanup body retains the diagnostic for the operation that traps: `AU4001` for a general runtime trap, `AU4002` for arithmetic overflow or underflow, `AU4003` for a bounds or lookup violation, `AU4004` for a zero divisor, and `AU4005` for a resource or I/O failure. ## Backend Support Nominal classes, generic specialization, fields and defaults, all maintained receiver modes, partial moves, structural equality, `copy class`, `indirect`, visibility, and user-resource cleanup are implemented by both MIR execution and direct native generation. Both receive the same checked class and method metadata; compiler analysis and the LSP use that same metadata for member resolution and signatures. ## Limits And Implementation-Defined Behavior Aura 0.3 has no class inheritance, overloads, property syntax, custom constructor hook, or general destructor hook. Generic user classes cannot be managed directly by `with`. A class field default cannot call a user-defined function in the current compiler; compute that value before construction and pass it as an explicit field argument. `indirect` is only a recursive field-layout marker; its storage representation and the physical order or padding of fields are not observable language contracts. Construction and method evaluation order are language-defined rather than implementation-defined. ## Status Ordinary and copy classes, generic classes, construction, defaults, visibility, inherent and associated methods, all maintained receiver modes, partial-field moves, recursive `indirect` fields, and non-generic user-resource classes are implemented for the post-Phase 1.5 surface. First-class field loans or views would require a new design; current return syntax reserves no such contract. Inheritance, properties, custom constructor/destructor hooks, and generic `with` resources are unavailable and MUST NOT be inferred from accepted class syntax. The constructor evaluation rule is implemented under `architecture_docs/decisions/0015-explicit-and-default-argument-order.md`, whose status is **Accepted**, and is pinned by `crates/aura-compiler/tests/fixtures/run-pass/explicit_and_default_argument_order.au` on both backends. ## Source: docs/manual/cli-and-tooling.md # CLI And Tooling The `aura` CLI is the product surface for checking, running, building, inspecting, and editor integration. During repository development, commands are usually run through Cargo: ```bash cargo run -p aura -- check examples/classes/point_distance.au ``` In an installed environment, the command shape is the same without the Cargo prefix: ```bash aura check app.au ``` ## Commands | Command | Purpose | | --- | --- | | `aura check file.au` | Parse and type-check without executing. | | `aura run file.au` | Execute through the MIR runtime, which is the default backend. | | `aura run --backend mir\|direct\|auto file.au` | Choose the execution backend explicitly. | | `aura run file.au -- args...` | Execute with program arguments available through `sys.args()`. | | `aura build -o path file.au` | Build a native binary. | | `aura ast file.au` | Print the syntax tree. | | `aura ast-json file.au` | Print syntax tree JSON. | | `aura mir file.au` | Print lowered MIR. | | `aura analyze file.au` | Emit diagnostics, symbols, hover data, and definition data. | | `aura complete --line N --character C --trigger . file.au` | Emit completion items. | | `aura deps update [name]` | Refresh all git dependencies or one named dependency. | | `aura new path` | Create `Aura.toml` and `src/main.au` without overwriting an existing path. | | `aura fmt [--check] [paths...]` | Normalize Aura source whitespace or verify formatting. | | `aura test [-k substring] [--format json] [--timeout-ms N] [paths...]` | Discover package-aware `.au` tests, select canonical case names, and report one result per case; defaults to `tests/` and a 30-second per-case timeout. | | `aura upgrade` | Download and run the verified installer to replace the compiler and bundled runtime with the latest published release. | | `aura lsp` | Run the persistent JSON-lines compiler service used by the language server. | | `aura help` / `aura --help` | Print usage. | | `aura version` / `aura --version` | Print the build channel and 12-hex-digit source commit. Release archives print `aura 0.3.2-preview (0123456789ab)`; source builds print `aura 0.3.2-dev (0123456789ab)`. | ## Checking `check` is the fastest way to validate syntax, types, imports, ownership, and package resolution: ```bash cargo run -p aura -- check examples/collections/list_basics.au ``` Use `check` before `run` when you are editing a package or diagnosing type errors. ## Running `run` executes a source file through the MIR runtime: ```bash cargo run -p aura -- run examples/control_flow/while_break_continue.au ``` Runtime diagnostics include source context where possible. Task execution uses the available parallelism reported by the host by default. The provisional `AURA_WORKERS` environment override accepts a positive integer, including a count larger than the host's available-core count. For example, `AURA_WORKERS=4 aura run app.au` selects four pinned task workers. `AURA_WORKERS=1` preserves single-worker cooperative execution through the same pinned-worker architecture. MIR runs, forced-direct runs, and standalone direct binaries use the same override. Empty, zero, signed, whitespace-padded, nonnumeric, and overflowing values stop execution with `AU4006` and identify the raw invalid value. Checking, analysis, completion, and formatting do not start the task runtime. Blocking host operations use a separate process-wide pool. Its operational settings are: - `AURA_BLOCKING_WORKERS=` requests that exact blocking worker count without clamping. When absent, the runtime uses available host parallelism, falls back to `4`, and clamps that derived default to `2..=8`. - `AURA_BLOCKING_QUEUE_CAPACITY=` bounds accepted jobs waiting in the pool's FIFO queue. Running jobs and callers waiting for admission do not consume this capacity. When absent, the pending queue is unbounded. MIR execution, direct execution, and launched standalone native binaries validate both settings before any user code runs. Empty, zero, signed, whitespace-padded, non-decimal, and overflowing values stop execution with `AU4006`, naming the setting and rendering the supplied value; a non-Unicode value is displayed lossily. The first runtime preflight reads both settings once, and the resulting configuration is immutable for the process lifetime. Valid preflight creates no blocking worker threads. First submission creates the complete configured set, which production reuses until process exit without an Aura shutdown/join surface. A full bounded queue parks a lightweight task through the scheduler; timeout or cancellation before queue insertion prevents submission. Once inserted, host work cannot be retracted and any late result is discarded. A bound limits accepted pending backlog, not running work or admission waiters, so it cannot guarantee progress for unrelated blocking I/O while all workers are stuck. ## Building ```bash cargo run -p aura -- build --backend auto -o ./target/app app.au cargo run -p aura -- build --backend direct -o ./target/app app.au ``` `auto` is the default. It first attempts the maintained direct backend and may fall back to a native launcher that embeds serialized MIR and the MIR runtime when direct emission is unavailable. `--backend direct` forbids that fallback. Both forms are standalone executables and must implement the same checked language behavior. An installed release archive resolves its native runtime relative to `bin/aura`, under `lib/aura`, and needs only a host C compiler for the final link. A source-checkout binary falls back to Cargo-built runtime artifacts for contributor convenience. ## Stdin Buffers Editor-style commands can read from stdin while using a supplied path for package roots and local imports: ```bash cat examples/modules/simple_import.au | \ cargo run -p aura -- analyze --stdin "$(pwd)/examples/modules/simple_import.au" ``` Stdin analysis and completion do not mutate package lockfiles. ## Analyze `analyze` emits machine-readable data for editor tooling: - diagnostics - symbols - hover information - definition targets The output is one JSON object with `diagnostics`, `symbols`, and `occurrences` arrays. Positions are zero-based. Diagnostics contain `code`, `line`, `start_character`, `end_character`, `message`, numeric `severity`, `secondary_spans`, `notes`, `help`, `edits`, and always-present `call_frames` and `task_ancestry` arrays. Analysis frame spans use zero-based coordinates and an optional `file_path`; symbols contain `name`, `kind`, `detail`, and recursive `children`; occurrences contain `hover` and an optional `definition` range, whose `file_path` may identify another module. An edit includes its range, replacement text, and applicability. `analyze` exits successfully even when the JSON contains source diagnostics: the request itself succeeded and the diagnostics are data. The language server prefers this compiler-backed analysis when it succeeds. ## Complete `complete` emits completion items at a zero-based line and character position: ```bash cargo run -p aura -- complete --line 12 --character 8 --trigger . app.au ``` Completion output is intended for tools, not humans, but it is useful when debugging the LSP. The JSON result is an array of `{ "name": str, "kind": str, "detail": str }` objects. `line` and `character` are zero-based and `--trigger` uses its first character. ## Machine-Readable And Inspection Formats `ast-json`, `analyze`, `complete`, and `lsp` emit JSON. The `analyze` and `complete` shapes described here are maintained tooling contracts for Aura 0.3. `ast`, `ast-json`, and `mir` expose compiler inspection data for people and tests; their exact formatting and internal node/block shape are not a stable cross-version serialization API. `aura lsp` is a persistent JSON-lines compiler service. Every request requires `semantic_interface_version: 5` plus string fields `method`, `path`, and `source`; `id` is optional and is echoed in the response. Supported requests are: ```json {"id":1,"semantic_interface_version":5,"method":"analyze","path":"/absolute/app.au","source":"print(1)\n"} {"id":2,"semantic_interface_version":5,"method":"complete","path":"/absolute/app.au","source":"value.\n","line":0,"character":6,"trigger":"."} ``` Each response is one line containing the same `id`, `semantic_interface_version: 5`, and either `result` or an `error` string. Paths give the virtual source a package/import context; ranges and completion positions are zero-based. A missing or different semantic interface version is an incompatible request and returns a schema-mismatch error. ## Output And Exit Status | Outcome | Exit status and streams | | --- | --- | | help/version | `0`; result on stdout | | malformed command usage | `2`; usage on stderr | | `check` success | `0`; exactly `ok` plus a newline on stdout | | compile, build, or runtime failure | `1`; rendered diagnostic on stderr | | `run` with `main() -> None` | `0` | | `run` with `main() -> int32` | the returned integer requested as the host process status | | successful `analyze` containing source diagnostics | `0`; JSON on stdout | | completed human `test` run | `0` when every selected case passes; `1` otherwise; case output and summary on stdout, failures on stderr | | completed JSON `test` run | `0` when every selected case passes; `1` otherwise; exactly one schema-version-1 document on stdout and no human progress lines | A broken stdout pipe is intentional clean termination and exits `0`; this lets commands compose with consumers such as `head` without printing a secondary failure. ## Testing With no paths, `aura test` recursively reads `.au` files under `tests/`. Given files or directories, it uses those inputs. Files are visited in normalized path order. Within a file, module functions are discovered in declaration order. A parameterless `test_*` function returning `None` is one case. Its canonical name is `path::test_name`. A module `test_*` function with parameters is an invalid test declaration. Class, trait, and implementation methods are not discovered. If a file declares no module `test_*` function, the file remains one case named `path`, entered through `main()` or its top-level statements. `-k substring` performs a literal, case-sensitive substring match over the complete canonical case name. Selection happens after parameter registrations are expanded, so the substring may select a bracketed case label. `-k` may appear once and its value must be non-empty. A valid filter that selects no cases succeeds with `0 passed; 0 failed`. Missing, empty, or repeated filter values are usage errors and exit with status 2. ### Setup And Teardown A file may declare ordinary parameterless `setup()` and `teardown()` module functions returning `None`. For each selected case, the runner invokes setup, then the case only when setup succeeds, then teardown. Teardown runs after an attempted setup even when setup traps, and after a case trap or non-zero file-level `main()` result. It is not run during discovery. A hook with parameters, a non-`None` result, or a collision with a non-function declaration is a check-time test failure. Setup, case, and teardown are isolated entries into one already checked and lowered module. The runner does not re-read or re-check the source between phases. Aura values and module-runtime state do not pass between phases or cases; external effects such as file writes remain observable. The first failure is primary. When teardown also fails, human output prints `teardown also failed for ...` after the primary failure, while JSON stores the teardown failure in the case record's `secondary` object with `stage: "teardown"`. If setup succeeds and only teardown fails, the teardown failure is primary. The timeout covers the complete lifecycle. A timed-out worker cannot be forcibly stopped, so teardown is not promised after timeout. ### Parameterized Registration A parameterized `test_*` function is parameterless and returns `list[(str, def() -> None)]`. The runner invokes it once during discovery and expands its list in order. Each tuple contains a non-empty label and a named, capture-free, repeatable, parameterless function returning `None`. Labels must be unique within that registration. The canonical case name is `path::test_name[label]`. Registration finishes before filtering, and it never executes a returned case. The required `def() -> None` element type excludes capturing closures and keeps every expanded case independently invocable. A registration trap, timeout, invalid returned value, empty label, duplicate label, or invalid case function is one discovery failure for that registration; none of its cases run. An empty registration contributes no cases. Registration itself never invokes setup or teardown. Registration stdout is captured once. Human mode writes all registration stdout before case results. JSON mode records non-empty registration stdout in the top-level `discovery` array, whose entries contain `name`, `file`, and `stdout` in registration order. ### Test Output Contract Human mode writes each case's captured stdout, then `ok ` for a passing case. A failed case writes `FAILED ` and its ordinary source diagnostic, or a runner reason such as a timeout, to stderr. Standard output ends with ` passed; failed`. Test records and the summary retain canonical discovery order. `aura test --format json` writes exactly one JSON document to stdout and no human progress lines. The top-level object has integer `schema_version: 1`, a `summary` object with integer `selected`, `passed`, and `failed` counts, and an ordered `tests` array. Every test record contains `name`, `file`, `outcome` (`passed` or `failed`), and a non-negative integer `duration_ms` covering its complete lifecycle. Non-empty captured output appears as `stdout`. A trapped test record contains `diagnostic`, using the existing structured diagnostic schema including optional assertion operand records. A runner failure contains `reason`; a failed record has exactly one primary failure form. A second teardown failure appears as `secondary`, with `stage` plus either `diagnostic` or `reason`. Invalid command usage still goes to stderr and exits 2. Assertions execute normally in every mode; `aura test` has no assertion-stripping option. ## VS Code And LSP The VS Code extension keeps one persistent `aura lsp` process for diagnostics, symbols, hover, go-to-definition, and completions. Requests are debounced, cancellable, version-guarded, and invalidated by dependency. If the compiler process cannot start, a small lexical recovery layer provides declarations and top-level completion; it intentionally does not duplicate compiler semantics. Compiler-backed method hover and completion details include the receiver contract. They render shared receivers canonically as `self`, consuming receivers as `own self`, and mutable receivers as `mut self`. Ordinary parameter signatures preserve bare, `own`, and `mut` spelling, and built-in hover/completion detail exposes retained-value contracts such as `list.append(value: own T)`. Class field and enum payload completion detail also renders their implicit constructor ownership as `own`. Useful repo commands: ```bash npm run check:lsp npm run test:lsp npm run check:extension npm run test:extension ``` ## Documentation Site The VitePress book is served with: ```bash npm run docs:dev ``` Build it with: ```bash npm run docs:build ``` Validate the normative reference structure and navigation with: ```bash npm run check:reference ``` GitHub Pages builds use the same command with `VITEPRESS_BASE=/Aura/` so project-page asset URLs are rooted correctly. ## Repository Gates The local repo gate is: ```bash npm run ci ``` That gate checks Rust formatting, Rust tests, native/MIR parity, LSP tests and coverage, VS Code extension tests, compiler coverage, reference integrity, docs build, npm and RustSec audits, Clippy with warnings treated as errors, and repository hygiene. GitHub Actions runs the repo gate on Linux and macOS. The release workflow publishes `v*` tag builds as GitHub Release assets, including platform CLI archives, the packaged VS Code extension, and a static docs archive. ## Grammar The command line is a tooling protocol, not part of Aura source grammar. Its maintained invocation forms are the command forms in the table above and the usage text printed by `aura help`. The single-source compiler commands use either one `.au` path or their documented `--stdin ` form; the virtual path supplies module and package context while standard input supplies the source text. `fmt` and `test` instead accept their documented path lists. `aura run` alone accepts program arguments after `--`. `--format human|json` is accepted by `check`, `run`, and `build` and does not change source-language parsing. Aura source accepted by these commands is governed by the [Grammar](/manual/grammar), not by this page. Command names, options, output formats, and exit statuses are case-sensitive. ## Typing Rules `check`, `run`, and `build` use the same package resolver, parser, static checker, and ownership checker. A program that fails those stages is not executed or emitted. `analyze` exposes the same semantic model in a recoverable editor-oriented report, and `complete` queries completion at a zero-based source position. Inspection commands expose intermediate compiler data but do not define additional source types. For `check`, `run`, and `build`, JSON diagnostic mode has schema version `1` and contains a `diagnostics` array. The current compile pipeline stops at its first failure, so a failed invocation contains exactly one diagnostic and a successful `check` contains none; tools must not treat that cap as proof that the rest of an invalid source file has no errors. Each diagnostic carries its stable code, severity, message, optional primary span, secondary spans, notes, help, machine-applicable edits, `call_frames`, and `task_ancestry`. The frame arrays are always present. Call frames are ordered innermost first; task ancestry is ordered youngest first. Every public schema-version-1 frame span owns a required `path` and its coordinates, so multi-file failures do not rely on the primary span's path. The `analyze` and persistent-service representations carry the same semantic diagnostic information in their documented zero-based editor-coordinate shapes, where `file_path` is optional only for source-only analysis. ## Runtime Semantics `check` performs no program execution. `run` executes checked MIR and forwards arguments after `--` to `sys.args()`. `build` emits a standalone host executable: `auto` tries the direct backend and may use the MIR-launcher fallback, while `direct` makes inability to emit directly an error. Both built forms must preserve the checked language semantics. Human-format `check` success writes exactly `ok` followed by a newline. JSON-format success writes a schema-version-1 object with an empty diagnostic array. `analyze` returning source diagnostics is a successful tooling request and therefore exits `0`; malformed CLI usage exits `2`; compile, build, and runtime failures exit `1`. A successful `main() -> int32` requests that integer as the process status. The complete stream and status rules are in the table above. ## Ownership And Evaluation Order Selecting a CLI command or output format does not alter Aura ownership, borrowing, cleanup, or evaluation order. `run`, a directly built program, and a MIR-launcher build must observe the same left-to-right source evaluation and the same resource cleanup rules. Tool-side mutations are explicit: `fmt` without `--check`, `deps update`, and successful lockfile-producing package commands may write files; `analyze --stdin` and `complete --stdin` do not write a lockfile. Source received through `--stdin` is not retained after the command or service request, but its virtual path remains semantically significant for imports, module identity, and diagnostic locations. ## Diagnostics Compiler-backed commands can surface the complete append-only registry. `AU1001` means invalid lexical input; `AU1002` means an invalid f-string delimiter; and `AU1101` means invalid syntax. `AU2001` means name-resolution failure; `AU2002` means type mismatch; `AU2003` means unsupported operator; `AU2004` means argument-binding failure; `AU2005` means unsupported syntax or feature; `AU2006` means a builtin method collision; `AU2007` means builtin function redefinition; `AU2008` means equality unavailable; and `AU2999` means a general compile-time rejection without a narrower code. `AU3001` means use of a moved value; `AU3002` means a borrow violation; `AU3003` means a mutability violation; `AU3004` means an invalid ownership mode; `AU3005` means a non-copy indexed read; `AU3006` means a non-copy indexed compound assignment; `AU3007` means non-cloneable state duplication; `AU3008` means a non-transferable task or Queue boundary; and `AU3009` means single-consumer task-result duplication. `AU4001` means a general runtime trap; `AU4002` means arithmetic overflow or underflow; `AU4003` means a bounds or lookup violation; `AU4004` means a zero divisor; `AU4005` means a resource, allocation, or I/O failure; `AU4006` means invalid runtime configuration; and `AU4007` means a numeric Array shape or reduction violation. The structured schema is defined in [Diagnostics](/manual/diagnostics). Human diagnostics render as `error[AU####]` with source context when a span is available. Ordinary notes are followed by readable call-chain and task-entry notes synthesized from the typed frame arrays; those generated lines are not duplicated in the structured `notes` field. `--format json` emits the schema-version-1 report on standard error for a failing `check`, `run`, or `build`. Usage errors, missing command-line operands, and host failures that prevent the tool itself from starting are CLI errors rather than Aura-language diagnostics; they print usage or a tool error and have no `AU####` code. ## Backend Support The parser, checker, package resolver, diagnostic model, analysis engine, and MIR lowering are shared by all maintained execution routes. `aura run --backend mir` executes the lowered MIR and is the default. `aura run --backend direct` builds a native binary with the direct backend and executes it, reporting a build or launch failure as an error. For `--format json` on maintained Unix hosts, the CLI supplies a private trap-signal pipe plus a separate diagnostic-data pipe bounded to 1,048,576 bytes. A native child signals a trap and writes exactly one EOF-delimited compiler-owned diagnostic JSON record, suppressing human stderr only after that write succeeds. Native initialization owns both descriptors, marks them close-on-exec, and removes their internal environment entries before user code, so an Aura-started subprocess cannot observe them or delay EOF. No signal or record is written for a normal `main` result, including status `1`, so the CLI does not infer a trap from a process status or parse human text. A trap signal without one valid record is a hard host execution failure. Human direct runs create no private protocol; the child renders its complete human diagnostic. `aura run --backend auto` prefers the direct backend and degrades to the MIR runtime only when direct building or launching is unavailable. Once a direct child runs, an Aura trap, signal termination, wait failure, or diagnostic-protocol failure is a final program/execution outcome and never triggers MIR fallback. Human mode prints an actual fallback reason on standard error before the MIR program runs; JSON mode includes it in the final structured report after execution. A forced `direct` run never degrades, so a parity or benchmark caller cannot silently measure the other backend. Every backend observes the same program arguments, standard output, exit code, and complete runtime diagnostic, including typed call frames and task ancestry. The native path is content-addressed. A successful direct build atomically publishes its binary, that artifact's SHA-256, and a key-bound unique entry identity into a cache keyed by native cache format `v5`, compiler-owned semantic-interface schema version `5`, this compiler's version, the host target, the backend, the exact linked runtime archive content, its ordered native link arguments, and the complete lowered program, which already incorporates the entry source and every resolved dependency source. The format and semantic identities are independent key fields: changing compiler-owned type or ownership metadata invalidates artifacts even if the native container format remains readable. Cache artifacts above 512 MiB are simply not retained; the just-built program still runs. A later run with the same inputs requires a regular directory and bounded regular sidecars, verifies the entry identity, digest, artifact size, execute permission, and platform-native executable shape, and only then uses the entry. It launches a private copy of exactly those verified bytes through a no-shell-fallback native execution path, so replacement of the shared cache pathname after verification cannot substitute different bytes. Missing or mismatched metadata, truncation, a non-regular member, a lost execute permission, or an executable-format/architecture rejection makes the entry a cache miss: Aura quarantines and removes that exact entry, then rebuilds before running. A temporary-directory failure, process-resource failure, `noexec` mount, or other environmental launch failure is not evidence that verified cache bytes are corrupt; Aura preserves the entry and reports or falls back according to the selected backend. On maintained Unix hosts, cache establishment is coordinated across processes. A short runtime-identity lock protects source-checkout runtime discovery, and a separate writer lock for each content key protects the miss/recheck/build/publish sequence. Therefore, N concurrent cold runs of the same program perform one build; after that publication, the other N-1 processes recheck and consume the verified entry. Existing verified hits take the optimistic read path and do not wait for a writer holding that key. Locks are released before linking output is executed, while atomic publication and invalidation continue to ensure that readers never observe a partial entry and a stale invalidator cannot delete a replacement published for the same key. In human mode, a native `run` flushes the exact line `aura: waiting for a concurrent build...` before it blocks on another builder and `aura: building native program...` before it starts building a native program artifact. A source-checkout `aura build` flushes the same exact wait line before blocking on another process refreshing the shared runtime. The reporter deduplicates each notice within one invocation. JSON `run` mode provisionally prioritizes the one-document stderr contract over immediate progress: it buffers the same exact strings and emits them in a successful report's `progress` array or a failed diagnostic's `notes`. A JSON build failure likewise retains a buffered wait notice in the diagnostic's `notes`. A successful `auto` run fallback also carries `fallback: {"from":"direct","to":"mir","reason":"..."}`; a failed MIR fallback retains the direct failure and progress as diagnostic notes. Tools should therefore not expect real-time progress in JSON mode until a structured streaming contract is ratified. `AURA_CACHE_DIR` selects the cache directory; the default is `~/.cache/aura/native`. The directory is a trust boundary. Its colocated SHA-256 detects corruption but does not authenticate bytes written by a hostile account, so the root must be private to the current OS user and every writer with access to it must be trusted. On the maintained Unix hosts, Aura rejects a root that is owned by another user or writable by group/other and creates or tightens accepted cache directories to mode `0700`. Private launch copies are removed after the child exits. Each launch carries an inherited exclusive lease, so later cleanup preserves the directory while either the `aura` parent or native child is still using it. Interrupted cache-publication, memo, and quarantine stages are collected only after their encoded 24-hour grace period and confirmation that their owner process is gone. An installed immutable runtime can still perform a direct build when caching is disabled or unavailable; no cache lock is required merely to build, and the uncached artifact is not retained. ADR-0031 ratifies the command split: `aura run` defaults to `mir` for the interactive edit-run path, while `aura build` defaults to `auto` for artifact production. Maintained measurements put a cold miss at about 1.3 seconds; a direct hello-world executable is roughly 57 MB of statically linked runtime. Each cache hit reads, hashes, and privately materializes the artifact, and workloads dominated by programs seen once, including CI, still pay the cold path on every program. `aura build --backend direct` uses native direct emission, and `--backend auto` may select the checked MIR-launcher fallback. The language server delegates semantic analysis and completion to the persistent compiler service; every JSON-lines request and response identifies semantic-interface schema version `5`. A missing or different identity closes the incompatible service and invalidates all document analysis before the lexical recovery path is used, so cached function-type or ownership metadata cannot cross compiler versions. The lexical fallback is recovery-only and is not a second language implementation. Backend parity is a release gate. A construct accepted by one maintained execution backend must have the same observable result or complete diagnostic in the other, including frame records and their source paths, subject only to the platform limits documented below. The parity harness performs no MIR-specific frame-note masking. ## Limits And Implementation-Defined Behavior Native linking requires a supported host C compiler and the installed Aura runtime layout described above. `ast`, `ast-json`, and `mir` are inspection formats, not stable serialization APIs. The formatter currently normalizes the maintained whitespace surface; it is not a configurable style engine. `aura test` discovers tests by the `test_` name prefix rather than by annotation, and a timed-out worker cannot be forcibly stopped inside the CLI process. Filesystem path interpretation, process exit-code width, executable format, linker selection, and availability of Unix-only APIs follow the maintained host platform. Package graph, source-size, recursion, runtime, and backend limits are collected in [Current Limits](/manual/current-limits). ## Status The commands and contracts documented as maintained on this page are implemented in Aura 0.3 and covered by CLI, compiler, LSP, extension, backend-parity, and repository-gate tests. `analyze`, `complete`, and diagnostic schema version `1` are maintained tooling contracts; internal AST and MIR layouts are intentionally unstable. Aura 0.3 has no package registry, publishing and installation workflow, Windows support, configurable formatter, or annotation-based test discovery. Its maintained execution engines are the MIR runtime and direct native backend. ## Source: docs/manual/closures.md # Closures Aura closures use `lambda parameters: expression`. They are small expression-bodied callable values. Parameter types come from context; a zero-parameter lambda may infer its result type from its body. Captures are always by value: Copy values are copied and owned non-Copy values are moved when the closure is created. ```aura def main(): factor: int32 = 2 scale: def(int32) -> int32 = lambda value: value * factor name = "Aura" length: def() -> int64 = lambda: name.len() token = "owned" take: def() -> str = lambda: token print(scale(21)) print(scale(6)) print(length()) print(length()) print(take()) ``` This prints `42`, `12`, `6`, `6`, and `owned` on separate lines. `factor` is copied into `scale`; `name` moves into a read-only, repeatable closure; and `token` moves into a consuming closure that is called once. ## Grammar The closure productions are: ```ebnf lambda-expression = "lambda", [ lambda-parameter, { ",", lambda-parameter } ], ":", expression ; lambda-parameter = [ "mut" | "own" ], identifier ; ``` A lambda is the lowest-precedence expression form. The body is exactly one expression; the colon does not introduce an indented suite. Parameter lists do not accept types, defaults, or a trailing comma. Zero parameters use `lambda: expression`. `lambda` is a contextual expression introducer: the lexer still produces an identifier token, but the spelling always begins a lambda at the start of an expression. Member and named-argument positions may use the same identifier spelling. A lambda may appear anywhere an expression is accepted, subject to the contextual typing rule below. There is no arrow spelling, capture list, statement body, `async` form, or nested `def`. ## Typing Rules A lambda with parameters requires a complete expected parameter contract from a structural function type such as `def(T1, mut T2, own T3) -> R`. The expected type fixes the parameter count and each parameter's capability and type. An expected result type also constrains the body. A zero-parameter `lambda: expression` may instead infer `def() -> R` from the body when no expected callable type is present. ```aura shared: def(str) -> int64 = lambda text: text.len() owned: def(own str) -> str = lambda own text: text push_one: def(mut list[int32]) -> None = lambda mut values: values.append(1) ``` A bare lambda parameter matches a bare shared parameter, `own name` matches an owned parameter, and `mut name` matches a mutable parameter. Modes cannot be silently changed. The body must have exactly the expected result type. Parameters are in scope only in the body and follow the ordinary no-shadowing rules. The compiler does not guess parameter types from body operations. Generic lambdas and lambda parameter type annotations are unavailable. A capture-free lambda uses the ordinary function-value representation and is Copy and Transfer. It may appear anywhere an ordinary function value can appear, including arguments, fields, collections, and returns. A capturing closure retains semantic environment and call-kind metadata that an arbitrary written `def(...) -> R` storage type does not describe. It may be held in an immutable inferred or contextually typed local, called directly, passed directly to compiler-known repeatable callback sites such as the list algorithms and `control.retry`, or moved into a qualifying task start. It cannot be coerced through an arbitrary written `def` parameter, stored in a `def` field or collection element, or returned through an annotated `def` result. Those metadata-erasing boundaries report `AU2002`. A conditional or `match` expression also cannot merge capturing closure values from different branches. The branches may have different capture sets, ownership states, and call kinds, and Phase 6.3 has no closure-union type that preserves those differences. Call the closure inside each branch, or return capture-free lambdas or named functions with one structural `def(...) -> R` type. Creating and calling a closure wholly inside a branch remains supported. A resolved name in the body is a capture only when it denotes an outer owned local or an `own` parameter. Lambda parameters, module functions, types, builtins, and imported items are resolved normally and are not stored in the environment. ## Runtime Semantics Evaluating a lambda constructs its callable value immediately. Each captured Copy value is snapshotted into the environment; each captured non-Copy owned value moves into it. Later changes to an outer mutable Copy binding do not retarget the snapshot. Calling the closure evaluates arguments under its contextual structural function signature and then evaluates the body. A closure whose body only reads captures borrows its environment for the call and can be invoked repeatedly. A body that consumes a non-Copy capture consumes the closure on its first call. The existing move checker rejects another call or use. Capture-free lambdas dispatch as ordinary function values. Capturing closures carry an owned environment and are non-Copy, including when their captures are individually Copy. They are also not clone-safe: a clone-producing generic specialization that would duplicate the environment reports `AU3007`. Use a named function or capture-free lambda when a callable must be copied or cloned. ## Ownership And Evaluation Order Capture is by value and happens at closure creation, not on the first call. Copy captures leave their sources usable. Non-Copy captures move, so using the outer source afterward reports `AU3001`. Clone before creation when both owners are required: ```aura def main(): name = "Aura" kept = name.clone() length: def() -> int64 = lambda: kept.len() print(name) print(length()) ``` A bare parameter of an enclosing function is shared capability, not owned data, and cannot be captured. Take it as `own`, or clone the data into an owned local before building the closure. A `mut` enclosing parameter is also caller-owned capability and cannot be captured. An inner lambda cannot capture a bare parameter of its enclosing lambda, even when the parameter type is Copy. When the surrounding callable contract allows it, make that outer parameter `own`; passing a Copy argument to the owned position duplicates the value, and the inner lambda may capture that owned parameter. When the outer contract must remain bare, pass the Copy value to a named helper with an `own` parameter and create or invoke the inner closure there. Phase 6.3 closure environments are read-only. A body cannot pass a capture to a `mut` parameter, call a `mut self` method on a capture, or otherwise request mutable access to it. This does not restrict the lambda's own `mut` parameter, which writes through the mutable argument supplied for that call. A closure is Transfer exactly when all of its captures are Transfer. Moving a qualifying closure into `TaskGroup.start`, `start_soon`, or an explicit-stack variant transfers the complete environment to child-owned storage. A non-Transfer leaf retains the ordinary `AU3008` boundary explanation. ### Comprehension Interaction Comprehensions do not change closure capture. A lambda enclosing a comprehension captures outer names used by iterable, filter, and output expressions, while comprehension targets are local bindings in the lambda body and are not captures. A lambda expression reached inside a comprehension is created at that runtime position. It may snapshot a Copy target, and it may move a Queue-received owned target when the surrounding use permits one consuming closure. A shared non-Copy list/set target is a capability into the source and cannot be captured; pass an explicit clone to a named helper or arrange another owned value outside the comprehension when independent storage is required. Capture environments remain read-only. The ordinary storage boundary also remains. A capturing closure cannot itself be inserted as a list, set, or dictionary comprehension result because collection storage erases its environment/call-kind metadata. It may be used immediately at a compiler-known callback or direct-call site inside an iterable, filter, key, value, or element expression. Such creation happens only when preceding clauses and filters reach it, and repeatable callback sites still reject a consuming closure. ## Diagnostics `AU1101` reports malformed lambda parameter or body syntax. `AU2002` reports a missing or mismatched parameter context, parameter capability, result type, metadata-erasing storage boundary, or a consuming closure supplied where a repeatable callback is required. `AU3001` reports use after a non-Copy value moved into a closure and use after a consuming closure call. `AU3002` rejects capture of shared or mutable caller capability. `AU3003` rejects mutable access through a captured environment. `AU3008` reports a closure whose captured environment cannot cross a task boundary because some captured value is not Transfer. The shared-capability diagnostic recommends cloning to an owned local or taking owned input. Move diagnostics identify closure creation or the consuming call as the ownership origin. ## Backend Support Contextual checking, capture analysis, move checking, MIR lowering, and direct native lowering implement the same closure contract. Both maintained backends copy or move captures at creation, preserve repeated read-only calls, enforce single-use consumption statically, and clean up an owned environment exactly once. Compiler analysis and the language server expose lambda parameter scope, captured-name definitions, callable hover, completions, and the compiler-owned diagnostics. ## Limits And Implementation-Defined Behavior Closures are expression-only and contextually typed. They do not support statement bodies, inline parameter types, defaults, generics, capture lists, implicit reference capture, mutable captured state, method values, trait objects, FFI callbacks, asynchronous syntax, shared-capability capture, or mutable captured state. Arbitrary structural `def` parameters and stored `def` fields, collection elements, and annotated returns currently carry only capture-free code pointers. Compiler-known callback sites preserve repeatable closure metadata; `control.retry` and the list callbacks reject consuming closures. Task start accepts a qualifying closure by move for one invocation. Conditional and `match` expressions cannot merge capturing closures from multiple branches. This is an explicit closure-union boundary, not an implementation-defined coercion. The capture, callability, ownership, and Transfer rules are language-defined; the implementation does not choose a reference-versus-value capture mode. ## Status Expression closures and by-value capture are implemented under Accepted ADR-0037 (`architecture_docs/decisions/0037-expression-closures-and-value-capture.md`) after ratification at the Batch 6 opening checkpoint. Capture-free function values remain governed by [Functions](/manual/functions), and task-boundary Transfer remains governed by Accepted ADR-0033. Comprehensions preserve this contract under Accepted ADR-0039 rather than adding a capture exception. ## Source: docs/manual/collections.md # Collections Aura provides three generic owned collection types: - `list[T]` for ordered sequences - `dict[K, V]` for key/value lookup - `set[T]` for uniqueness and membership `str` is the immutable owned UTF-8 text type. Collections are move types. A bare parameter or loop grants shared access, `mut` grants exclusive mutable access, and `own` transfers the value. ## Literals And Constructors List literals and constructors are homogeneous: ```aura values = [1, 2, 3] empty: list[int32] = [] other = list[int32]() ``` Dictionary literals evaluate entries from left to right, with each key before its value. An equal key updates the value at the key's first insertion position. ```aura counts = {"ready": 1, "done": 2} empty: dict[str, int32] = {} other = dict[str, int32]() ``` Set literals use braces when a `set[T]` context is available. `{}` is a dictionary literal, so an empty set uses its typed constructor. ```aura seen: set[int32] = {1, 2, 3} empty = set[int32]() ``` Every literal is homogeneous in each type position. Mixed list or set element types, mixed dictionary key types, and mixed dictionary value types are rejected with `AU2002`. Contextual integer-literal typing applies, but Aura does not convert an already typed value or infer a union for a collection literal. Collection operations that compare stored values require a defined equality relation for the relevant type. This includes list `remove`, `index`, and `count`; `in` and `not in`; set insertion; and every dictionary-key operation. Callables, `random.Rng`, opaque FFI handles, and values containing any of those types do not define equality. The compiler rejects the operation with `AU2008` and names the unmet obligation. ## Iteration Lists support shared, consuming, and mutable place iteration. Sets support shared and consuming iteration. Mutable set iteration is unavailable. ```aura for value in values: print(value) for value in own values: consume(value) for value in mut values: value += 1 ``` Dictionaries expose eager owned snapshots. `items()` returns key/value tuples in insertion order: ```aura for key, value in counts.items(): print(key + "=" + value.to_string()) ``` Bare list and set iteration freezes the selected collection for the loop. `own` selects and consumes the source once. Comprehensions use the bare shared form and eagerly build fresh owned collections: squares = [value * value for value in values] even = {value for value in values if value % 2 == 0} labels = {value: str(value) for value in values} Nested clauses execute in outer-major order. Filters run from left to right. A dictionary comprehension evaluates its key before its value. Output storage owns each produced element, key, and value. ## list[T] `list[T]` preserves element order. `len(values)` and `values.len()` return `int64`. | Method | Signature | Contract | | --- | --- | --- | | `append` | `append(value: own T) -> None` | Transfers `value` to the end. | | `pop` | `pop(index: int64 = -1) -> T` | Removes and transfers the normalized position. | | `remove` | `remove(value: T) -> None` | Removes the first equal element. | | `index` | `index(value: T) -> int64` | Returns the first position containing an equal element. | | `count` | `count(value: T) -> int64` | Counts equal elements. | | `insert` | `insert(index: int64, value: own T) -> None` | Transfers `value` before the clamped position. | | `extend` | `extend(other: own list[T]) -> None` | Transfers all elements from `other` in order. | | `clear` | `clear() -> None` | Removes all elements. | | `reverse` | `reverse() -> None` | Reverses the list in place. | | `sort` | `sort(reverse: bool = false) -> None` | Stably sorts an orderable list in place. | | `sort` | `sort[K](key: def(T) -> K, reverse: bool = false) -> None` | Stably sorts by keys computed once per element. | | `copy` | `copy() -> list[T]` | Returns independent owned storage; requires clone-safe `T`. | | `get` | `get(index: int64) -> Option[T]` | Returns a cloned element or `None`; requires clone-safe `T`. | | `set` | `set(index: int64, value: own T) -> T` | Replaces a position and transfers out its old element. | | `swap` | `swap(first: int64, second: int64) -> None` | Swaps two positions. | | `reserve` | `reserve(additional: int64) -> None` | Ensures room for `len() + additional` elements. | | `with_capacity` | `list[T].with_capacity(minimum: int64) -> list[T]` | Creates an empty list with at least the requested capacity. | `map` and `filter` remain eager source-retaining operations. `map` owns each callback result. `filter` clones accepted elements and therefore requires a clone-safe element type. ### Positions, Indexing, And Slicing Direct indexing, `get`, `set`, `swap`, and `pop` normalize a negative position once as `len() + index`. The result must be in `0..len()`. Invalid direct positions and invalid `pop`, `set`, or `swap` positions trap with `AU4003`. `get` returns `None` for an invalid position. ```aura match values.get(index): case Option.Some(value): print(value) case Option.None: print("missing") ``` `pop()` selects the final element. It traps on an empty list. `remove(value)` and `index(value)` search from the start and trap with `AU4008` when the value is absent. `count(value)` returns zero when the value is absent. ```aura def main(): mut values = [10, 20, 30] print(values[-1]) print(values.get(-2)) ``` `insert` applies Python clamping. A negative input first adds the current length. A result below zero becomes zero, and a result above the length becomes the length. The value is inserted before that effective position. List positions and written slice endpoints use the `int64` index domain. Values of type `int8`, `int16`, `int32`, `uint8`, `uint16`, and `uint32` widen losslessly at these positions. This position rule does not convert ordinary assignments or function arguments. One-colon slices return fresh owned lists. Endpoints are half-open, may be omitted, and normalize negative values once. Both effective endpoints must be in `0..=len()`, and the start must not exceed the end. Invalid or reversed bounds trap with `AU4003`. List slices copy Copy elements and clone clone-safe non-Copy elements. `str` slicing uses the same position rules and counts Unicode scalar values. It returns a fresh valid UTF-8 `str`. Integer indexing of `str` is unavailable. ### Stable Sorting The canonical calls are: ```aura def make_key(value: int64) -> int64: return -value def main(): mut values = [3, 1, 2] values.sort() values.sort(reverse=true) values.sort(key=make_key) values.sort(key=make_key, reverse=true) ``` Natural sorting requires `T: Ord`. Key sorting requires an orderable key type. Equal elements or keys retain their relative input order in both directions. The key function runs exactly once per element from first to last, and all keys are stored before the list mutates. Argument, key, ordering, or allocation failure before mutation leaves the receiver unchanged. ## dict[K, V] Dictionaries preserve insertion order for iteration and snapshots. Indexing, assignment, and membership are the primary lookup and storage forms: value = table[key] table[key] = value present = key in table | Method | Signature | Contract | | --- | --- | --- | | `get` | `get(key: K) -> Option[V]` | Returns a cloned value or `None`; requires clone-safe `V`. | | `remove` | `remove(key: K) -> Option[V]` | Removes the entry and transfers its value, or returns `None`. | | `keys` | `keys() -> list[K]` | Returns cloned keys in insertion order. | | `values` | `values() -> list[V]` | Returns cloned values in insertion order. | | `items` | `items() -> list[(K, V)]` | Returns cloned key/value tuples in insertion order. | | `copy` | `copy() -> dict[K, V]` | Returns independent owned storage. | | `update` | `update(other: own dict[K, V]) -> None` | Transfers entries from `other` in insertion order. | | `clear` | `clear() -> None` | Removes all entries. | | `reserve` | `reserve(additional: int64) -> None` | Ensures room for `len() + additional` entries. | | `with_capacity` | `dict[K, V].with_capacity(minimum: int64) -> dict[K, V]` | Creates an empty dictionary with at least the requested capacity. | `keys()` and `copy()` require clone-safe `K`; `values()` requires clone-safe `V`; `items()` requires both. These methods return eager snapshots, not live views. `get` accepts no default argument. Absence is represented by `Option[V]`. ```aura def bump(counts: mut dict[str, int32], key: own str): match counts.get(key): case Option.Some(count): counts[key] = count + 1 case Option.None: counts[key] = 1 ``` An indexed read follows the collection ownership rule for `V` and traps with `AU4003` when the key is absent. Indexed assignment transfers its key and value as needed. It inserts an absent key and updates an equal key without changing the key's insertion position. `update` applies the same position rule. ## set[T] Sets store one value per equality class. Membership uses `in` and `not in`. | Method | Signature | Contract | | --- | --- | --- | | `add` | `add(value: own T) -> None` | Transfers a value into the set. | | `remove` | `remove(value: T) -> None` | Removes an equal value; absence traps with `AU4008`. | | `discard` | `discard(value: T) -> None` | Removes an equal value when present. | | `copy` | `copy() -> set[T]` | Returns independent owned storage; requires clone-safe `T`. | | `clear` | `clear() -> None` | Removes all values. | | `reserve` | `reserve(additional: int64) -> None` | Ensures room for `len() + additional` values. | | `with_capacity` | `set[T].with_capacity(minimum: int64) -> set[T]` | Creates an empty set with at least the requested capacity. | `add`, `remove`, `discard`, and membership require equality for `T`. Mutating methods return `None`. Callers use membership to distinguish presence. ```aura def main(): mut ids = set[int32]() ids.add(42) ids.discard(7) ``` A non-empty set renders as `{first, second}` in its defined iteration order. An empty set renders as `set()`. ## Equality, Copying, And Capacity Lists compare elements in order. Dictionaries compare equal key/value mappings. Sets compare equal membership. Equality consumes neither operand. `copy` creates independent owned storage under the stated clone-safety requirements. Removing methods transfer stored values. Shared lookup and search operations retain the collection and their arguments. `reserve(additional)` guarantees capacity of at least `len() + additional`. `with_capacity(minimum)` creates an empty collection with capacity of at least `minimum`. These operations do not change contents or order. A negative value traps with `AU4003`. Overflow, maintained-limit violations, and allocation failure trap with `AU4005`; a failed reserve leaves the receiver unchanged. This executable example covers collection literals, eager algorithms, stable sorting, set deduplication, and comprehension order: ```aura def doubled(value: int32) -> int32: return value * 2 def is_even(value: int32) -> bool: return value % 2 == 0 def descending_key(value: int32) -> int32: return -value def main(): values: list[int32] = [3, 1, 2, 4] middle = values[1:3] mapped = values.map(doubled) filtered = values.filter(is_even) mut ascending = values.copy() ascending.sort() mut descending = values.copy() descending.sort(key=descending_key) squares = [value * value for value in values] even_squares = [value * value for value in values if value % 2 == 0] remainders: set[int32] = {value % 3 for value in values} labels = {value: value * 10 for value in values if value >= 3} pairs = [ left * 10 + right for left in values if left < 3 for right in values if right < 3 ] assert 0 in remainders assert 1 in remainders assert 2 in remainders print(middle) print(mapped) print(filtered) print(ascending) print(descending) print(values) print(squares) print(even_squares) print(labels) print(pairs) ``` ## Grammar The normative productions for literals, comprehensions, constructors, indexing, slicing, indexed assignment, method calls, and loop ownership modes are in [Grammar](/manual/grammar). The first colon in a non-empty brace literal selects dictionary syntax. `{}` is a dictionary literal. ## Typing Rules Collection specializations are invariant and homogeneous. Empty literals need an expected type. Mutating methods and indexed assignment require a mutable collection place. Direct dictionary reads follow the value ownership contract; `get` provides an optional cloned read for clone-safe `V`, and `remove` transfers any stored `V`. List callbacks use exact shared function types. `map` requires `def(T) -> U`, `filter` requires `def(T) -> bool`, and keyed sorting requires `def(T) -> K` with `K: Ord`. The callback must be repeatable. ## Runtime Semantics All collection expressions evaluate once from left to right. Lists and dictionaries preserve their specified order. Sets collapse equal duplicates. Comprehensions are eager and execute as nested loops. A trap cleans up any partially created collection. ## Ownership And Evaluation Order Collection storage positions own non-Copy elements, keys, and values. Shared lookups and searches retain their inputs. `append`, `insert`, `extend`, `add`, `update`, indexed assignment, and comprehension output transfer owned values. Slices and `copy` produce independent storage. No collection operation inserts a hidden clone. ## Diagnostics `AU2001` reports unknown collection types and members. `AU2002` reports type, arity, homogeneity, and callback mismatches. `AU3001` reports use after move; `AU3002` reports conflicting access; `AU3003` reports mutation through an immutable place; `AU3005`, `AU3006`, `AU3007`, and `AU3009` report ownership or clone-safety violations. `AU4003` reports invalid positions and missing direct dictionary keys. `AU4005` reports allocation and capacity failures. `AU4008` reports a missing value for list `remove`/`index` and set `remove`. ## Backend Support The MIR and direct backends implement the same collection types, methods, ordering, ownership, evaluation, rendering, and diagnostic behavior. Compiler analysis and the language server consume the same builtin signatures. ## Limits And Implementation-Defined Behavior Mutable set iteration and direct dictionary iteration are unavailable. Set algebra and relations are outside this surface. Arbitrary user-defined iterables, generator expressions, slice steps, slice assignment, views, and `str` integer indexing are unavailable. Set order is not an API contract. Allocation is limited by available host resources and maintained runtime caps. ## Status The collection contract in this chapter is Accepted by ADR-0044 and is the canonical Aura 0.3 surface. ## Source: docs/manual/concurrency.md # Concurrency Aura provides pinned-worker scheduler-backed lightweight tasks, structured task groups, queues, task handles, cancellation checks, sleeping, and typed single- and multi-source wait helpers. Scheduler waits use a persistent event reactor: descriptors stay registered, deadlines live in a timer heap, and Queue, task-completion, and blocking-pool events notify the responsible worker directly. The maintained model is structured by default: child tasks should live inside a `TaskGroup`, and leaving the group scope waits for the children. Queue and task waits participate in the scheduler so a blocked task does not block the whole runtime. The runtime creates one pinned worker per unit of available parallelism reported by the host by default. The provisional `AURA_WORKERS=` environment override selects an explicit worker count. A child receives a stable worker assignment when it is spawned; its coroutine stack never migrates and the runtime performs no work stealing. This contract is shared by MIR execution and direct native execution. A positive override may exceed the host-reported default. `AURA_WORKERS=1` preserves single-worker cooperative execution through the same worker-thread architecture. Empty, zero, signed, whitespace-padded, nonnumeric, and overflowing values are rejected before execution with `AU4006`. ## Duration Values Scheduler APIs use `Duration`. This executable example covers every literal unit plus the computed surface: ```aura def main() -> int32: attempt: int64 = 3 print(10ms) print(1s) print(2m) print(attempt * Duration.ms(125)) print(1ms // attempt) print(Duration.minutes(-1) < 0ms) print(Duration.seconds(2).to_ms()) print(Duration.ms(1500).to_seconds()) return 0 ``` Durations are signed i128-nanosecond copy values. Use `Duration.ms(value)`, `Duration.seconds(value)`, or `Duration.minutes(value)` when the count is an `int64` expression rather than a literal. Checked `+`, `-`, multiplication by an `int64` in either order, `// int64`, and all comparisons make computed backoff and deadline selection expressible; for example, a runtime attempt count can use `attempt * 1ms`. `to_ms()` and `to_seconds()` convert the exact rational unit value to the nearest representable IEEE-754 binary64 value, ties-to-even, and may round. Printing and f-string interpolation instead render the exact decimal millisecond value with at most six fractional digits and an `ms` suffix. A negative Duration is representable but is not a valid sleep, timeout, or backoff. Scheduler APIs on this page have no `io.Error` or `process.Error` carrier, so a negative value, host-timer overflow, or deadline overflow traps with `AU4001`. Overflow never changes the operation into an unlimited wait. The exact host-timer classification is accepted under ADR-0019. ## TaskGroup Construct a group with `TaskGroup()` and normally bind it with `with`: ```aura with group = TaskGroup(): task = group.start(work, 1) ``` | API | Signature | Contract | | --- | --- | --- | | constructor | `TaskGroup()` | Creates a task group resource. | | `start` | `start(function, own ...) -> Task[T]` | Requires every capture and result to be `Transfer`, starts the specialized target, and returns its handle. | | `start_soon` | `start_soon(function, own ...) -> None` | Requires every capture and result to be `Transfer` and starts the specialized target without returning a handle. | | `start_with_stack` | `start_with_stack(bytes: int64, function, own ...) -> Task[T]` | Applies the same Transfer rules with an explicit guarded stack-capacity request and returns the handle. | | `start_soon_with_stack` | `start_soon_with_stack(bytes: int64, function, own ...) -> None` | Applies the same Transfer rules with an explicit guarded stack-capacity request and no returned handle. | | `cancel` | `cancel() -> None` | Signals cancellation to child tasks. | All four start methods accept capture-free function values, which are copy values and satisfy `Transfer`, plus closure values whose complete captured environment is Transfer. Existing direct named-function and associated-method-without-`self` targets remain accepted, including explicit generic targets written as `function[Types]` or `Type.associated_method[Types]` in the callable slot. Associated methods do not thereby become general first-class method values. Every target argument is copied or moved into task-owned capture storage. A bare shared target parameter borrows from that storage for the child call; an `own` parameter consumes it. `mut` targets are rejected because detached mutable capture has no caller-visible writeback. When a function-value contract retains default availability, omitted task arguments evaluate the runtime-selected target's own default expression. Task start therefore follows the same default-binding rule as an ordinary indirect call. Ordinary `start` and `start_soon` request the 524,288-byte (512 KiB) default. The two `_with_stack` methods take an exact `int64` byte count before the callable target. Accepted requests are 262,144 through 67,108,864 bytes inclusive (256 KiB through 64 MiB). Values outside that range are rejected, not clamped. An accepted request is rounded upward to the host page size and the platform stack allocator adds guard-page protection; guard pages are not part of the requested writable capacity. The separate method names avoid stealing a keyword that could belong to the target's own arguments. This surface is Provisional under ADR-0032. The 256 KiB lower bound is an opt-in minimum for a task whose shallow stack use has been measured; it is not the generally safe default. During integration, the complete compiled Aura HTTP example faulted when 256 KiB was used as the global task default and succeeded with the 512 KiB default. An isolated runtime-level HTTP regression does succeed when only its protocol-calling children are forced to 256 KiB: that test proves deep host protocol frames stay on the service workers, but it excludes the compiled program's MIR/direct language-execution frames. Keep the ordinary default unless measurement of the complete task justifies a custom size. ```aura with group = TaskGroup(): parser = group.start_with_stack(512 * 1024, parse_document, source) group.start_soon_with_stack(2 * 1024 * 1024, deep_worker, jobs) ``` On normal scope exit, the runtime joins children that continue making bounded progress. It cancels a child in an unbounded group-owned wait only when the live wait graph has no reachable waker. A queue wait therefore remains joinable while another live task can send, receive, or close the relevant queue; the task currently performing the join does not count as a waker for its own children. A failure already observed through its `Task` result is not raised a second time; an unread child failure aborts the group scope and wakes dependent queue/task waits. ## Task[T] `Task[T]` is a transferable handle to a child task result. Under Accepted ADR-0033 it is copyable only when `T` is repeatable, so aliases cannot duplicate one result right. | API | Signature | Contract | | --- | --- | --- | | `result` | `result(timeout: Duration = ...) -> TaskResult[T]` | Waits for completion and returns a structured outcome; a non-repeatable `T` consumes the observation right on this call. | | `result_or_none` | `result_or_none(timeout: Duration = ...) -> Option[T]` | Returns `Some(value)` on success and `None` on task failure, timeout, or cancellation; a non-repeatable `T` consumes the observation right even when `None` is returned. Without an explicit timeout, this helper performs an immediate check. | | `result_or` | `result_or(default: own T, timeout: Duration = ...) -> T` | Returns the task value or `default` on task failure, timeout, or cancellation; a non-repeatable `T` consumes the observation right. Without an explicit timeout, this helper performs an immediate check. | `TaskResult[T]` variants: | Variant | Meaning | | --- | --- | | `Ready(value: own T)` | The task returned normally. | | `Error(message: own str)` | The task failed with a runtime error. | | `TimedOut` | The wait timed out. | | `Cancelled` | The wait was interrupted by cancellation. | Use `result` when the program needs to distinguish failure, timeout, and cancellation. Use `result_or_none` or `result_or` only when those outcomes are intentionally equivalent. The completed value is stored by the task. Under Accepted ADR-0033, `Task[T]` is copyable only when `T` is copyable, `T` is a `Queue[...]` handle, or `T` is a recursively repeatable `Task[...]`. For every other transferable result, `result`, `result_or_none`, and `result_or` consume the unique observation right on any outcome. The consumption is conservative: timeout, cancellation, failure, and a collapsed `None` do not restore it. `wait_any` and `wait_all` consume the whole task list for such a `T`; `wait_any` abandons the unchosen observation rights. ## Queue[T] `Queue[T]` moves values between tasks. Queue handles are copy values. ```aura jobs = Queue[str]() bounded = Queue[str](capacity=8) ``` | API | Signature | Contract | | --- | --- | --- | | constructor | `Queue[T](capacity: int32 = ...)` | Creates an unbounded queue when omitted or a bounded queue for a positive capacity; requires `T: Transfer`; zero or negative capacity traps with `AU4001`. | | `put` | `put(value: own T, timeout: Duration = ...) -> Result[None, SendError[T]]` | Sends a `Transfer` value, waiting for capacity when needed. Returns the unsent value in the error variant. | | `try_put` | `try_put(value: own T) -> Result[None, SendError[T]]` | Attempts to send a `Transfer` value without waiting. Returns `Full(value)` when a bounded queue is full. | | `get` | `get(timeout: Duration = ...) -> QueueReceive[T]` | Receives one structured queue outcome. | | `get_or_none` | `get_or_none(timeout: Duration = ...) -> Option[T]` | Returns `Some(value)` for an item and `None` for closed, timed-out, or cancelled receives. Without an explicit timeout, this helper performs an immediate check. | | `get_or` | `get_or(default: own T, timeout: Duration = ...) -> T` | Returns an item or `default` for closed, timed-out, or cancelled receives. Without an explicit timeout, this helper performs an immediate check. | | `close` | `close() -> None` | Closes the queue and wakes blocked senders and receivers. | `SendError[T]` variants: | Variant | Meaning | | --- | --- | | `Closed(value: own T)` | The queue was closed before the value could be sent. | | `Cancelled(value: own T)` | Cancellation interrupted the send. | | `TimedOut(value: own T)` | The send timeout expired. | | `Full(value: own T)` | `try_put` found a bounded queue at capacity. | `QueueReceive[T]` variants: | Variant | Meaning | | --- | --- | | `Item(value: own T)` | A value was received. | | `Closed` | The queue is closed and no value was available. | | `TimedOut` | The receive timeout expired. | | `Cancelled` | Cancellation interrupted the receive. | Queue iteration: ```aura for value in jobs: print(value) ``` Queue iteration receives values: every `Item(value)` arrives already owned by the loop binding. The Queue handle is a copy value, so ownership modifiers have nothing to modify. The bare `for value in jobs` form above is accepted; `for value in own jobs` and `for value in mut jobs` are rejected. The bare form evaluates and copies the Queue handle once at loop entry. It does not freeze the source binding: rebinding `jobs` in the body is permitted, but later receives continue through the captured handle rather than switching to the newly bound Queue. This source-selection timing is accepted in ADR-0017; ADR-0006's receive ownership and modifier carve-out are unchanged. The receive loop ends when the queue closes, cancellation interrupts it, or the relevant producers in the active task group complete. Closing queues explicitly is still the clearest program shape. ## Top-Level Concurrency Builtins | API | Signature | Contract | | --- | --- | --- | | `cancelled` | `cancelled() -> bool` | Returns `true` when the current task has been asked to cancel. | | `yield_now` | `yield_now() -> None` | Voluntarily yields the current lightweight task so other runnable work can proceed. | | `sleep` | `sleep(duration: Duration) -> None` | Suspends the current task for at least `duration`, unless cancellation wakes it first. | | `select` | `select(source, ...) -> SelectOutcome[Q, T]` | Waits on one or more positional `Queue[Q]`, `Task[T]`, or relative-`Duration` sources; cancellation wins, otherwise the lowest ready source index wins. | | `wait_any` | `wait_any(tasks: list[Task[T]], timeout: Duration = ...) -> WaitAny[T]` | Waits for the first task outcome or timeout. For non-repeatable `T`, consumes the list and abandons unchosen observation rights. `wait_any([])` returns `TimedOut` immediately. | | `wait_all` | `wait_all(tasks: list[Task[T]], timeout: Duration = ...) -> WaitAll[T]` | Waits until every task is ready, one task errors, timeout expires, or cancellation interrupts the wait. For non-repeatable `T`, consumes the list. | ### Explicit Cooperative Yielding `yield_now()` places the current lightweight task back in the scheduler ready set and returns `None` when that task is selected to run again. It gives other runnable tasks assigned to the same pinned worker an opportunity to proceed, but it does not migrate the task, search another worker for work, guarantee that another task runs before it returns, or specify which runnable task is selected. If there is no current schedulable lightweight task, the call returns without effect. The call does not sleep, wait for an event or deadline, or inspect or change cancellation state. Use it between bounded chunks of CPU work when an explicit cooperative scheduling point is useful. Use `cancelled()` separately when the task must also respond to cancellation. `WaitAny[T]` variants: | Variant | Meaning | | --- | --- | | `Ready(index: own int64, value: own T)` | Task at `index` returned normally. | | `Error(index: own int64, message: own str)` | Task at `index` failed. | | `TimedOut` | No task completed before the timeout. | | `Cancelled` | Cancellation interrupted the wait. | ### Typed Heterogeneous Selection `select(source, ...)` waits without polling over any positional mixture of `Queue[Q]`, `Task[T]`, and relative `Duration` sources. At least one source is required and named arguments are rejected. All Queue sources in one call use one payload type `Q`, and all Task sources use one result type `T`; the two categories are independent. An absent category is represented by `None`, so selecting a `Queue[str]` with a deadline returns `SelectOutcome[str, None]`, while selecting a `Task[int32]` with a deadline returns `SelectOutcome[None, int32]`. `SelectOutcome[Q, T]` variants: | Variant | Meaning | | --- | --- | | `Queue(index: own int64, outcome: own QueueReceive[Q])` | The Queue at the original zero-based source index produced an item or closed outcome. | | `Task(index: own int64, outcome: own TaskResult[T])` | The Task at the original zero-based source index produced a ready, error, or child-cancelled outcome. | | `Deadline(index: own int64)` | The relative Duration at the original zero-based source index expired. | | `Cancelled` | Cancellation of the selecting task interrupted the wait. | Queue sources have no individual timeout, so `select` never produces `QueueReceive.TimedOut`; selecting-task cancellation uses the outer `SelectOutcome.Cancelled`. Task sources likewise never produce `TaskResult.TimedOut`. A child task that is itself cancelled still produces the nested `TaskResult.Cancelled` outcome. Source expressions are evaluated exactly once from left to right. All durations use one common base instant after evaluation and validation. Zero is immediately ready; a negative or host-range-overflowing duration traps with `AU4001`. Current-task cancellation has priority over every source. Otherwise, if several sources are ready at the same arbitration point, the lowest original argument index wins. A selected Queue removes exactly one item; losing Queue sources remain unchanged. A closed Queue is ready, with buffered items received before `Closed`. Selecting a repeatable Task leaves the handle reusable. Every non-repeatable Task observation right is consumed at call entry, even when another source wins; losing rights are deliberately abandoned, matching `wait_any`. Repeating the same non-repeatable Task in one call is rejected with `AU3009`. Queue handles, repeatable Tasks, and Duration values may be repeated, and the lowest ready occurrence wins. Selection uses one composite check-subscribe-recheck registration and removes every losing registration before returning, trapping, or propagating cancellation. Once a source has been atomically claimed, that winner is committed: cancellation or another readiness event observed later does not replace it. Index priority is deterministic, not fair. A persistently ready lower-index source can starve a higher-index source. Rotate argument order between calls when round-robin service is required. ```aura def main() -> int32: messages = Queue[str]() messages.put("ready") print(select(messages, 0ms)) return 0 ``` `WaitAll[T]` variants: | Variant | Meaning | | --- | --- | | `Ready(values: own list[T])` | Every task returned normally. Values are in the same order as the input tasks. | | `Error(index: own int64, message: own str)` | Task at `index` failed before all tasks completed. | | `TimedOut` | Not every task completed before the timeout. | | `Cancelled` | Cancellation interrupted the wait. | ## Cancellation Semantics Cancellation is cooperative. `group.cancel()` marks child tasks as cancelled. Tasks observe that state through: - `cancelled()`; the check is also a cooperative scheduler yield point - `yield_now()` is a scheduling point but does not itself inspect cancellation - `sleep(...)` - queue send and receive waits - task result waits - `select(...)` - `wait_any(...)` and `wait_all(...)` - scheduler-aware process, network, and I/O waits where supported Long CPU loops should check `cancelled()` directly: ```aura while not cancelled(): do_step() ``` Cancellation interrupts Aura's wait for scheduler-aware or worker-backed operations. For the generic blocking-I/O pool, insertion into the pending job queue is the acceptance boundary. Cancellation or deadline expiry while a caller is still waiting for admission prevents submission. After acceptance, Aura cannot forcibly stop the pending or running host operation; it executes once, may still perform its side effect, and has any late result discarded. A configured queue bound limits accepted pending work, but cannot guarantee unrelated blocking-I/O progress while all blocking workers remain stuck. Aura 0.3 task scheduling is cooperative across pinned workers. The compiler inserts a scheduling check on every loop backedge, including the ordinary body tail and `continue`, so a tight loop eventually lets ready timers, Queue operations, and socket work on the same worker proceed. `break` and `return` leave the loop without taking that check. A single long loop body or long straight-line CPU work can still delay siblings pinned to that worker. The inserted check does not inspect cancellation; tasks that must stop on request still call `cancelled()`. Each ordinary lightweight task requests a guarded 512 KiB coroutine stack; the two explicit stack-start methods may request up to 64 MiB. When a worker has no ready task, its event reactor blocks until a notification, descriptor event, or deadline; it does not wake on a periodic scheduler tick. ## Detached Work Aura does not currently expose a `spawn detached` language form. Keep lightweight task work under `TaskGroup` so scope exit has a clear join and cleanup boundary. For operating-system child processes, use the `process` module and decide explicitly whether the child should be supervised, waited on, or closed. ## Grammar Concurrency introduces no `async`, `await`, or detached-spawn grammar. `TaskGroup`, `Task`, `Queue`, `yield_now`, `sleep`, `cancelled`, `select`, `wait_any`, and `wait_all` use ordinary construction and call syntax; structured groups use the ordinary `with` statement. Stack overrides are ordinary member calls, not new task or spawn grammar. Queue iteration uses only `for item in queue:`. Duration literal spelling is defined in [Lexical Structure](/manual/lexical-structure) and the relevant statement and call productions are in [Grammar](/manual/grammar). ## Typing Rules `Queue[T]` is a copy handle; `Task[T]` is conditionally Copy under Accepted ADR-0033; `TaskGroup` is a managed move resource. Queue sends, fallback values, task captures, and returned outcome payloads use the exact owned positions shown in the API tables above. Task targets may be capture-free function or Transfer closure values. The existing direct named-function and associated-method-without-`self` forms remain accepted; generic targets may infer every type argument or use explicit `function[Types]` and `Type.associated_method[Types]` specialization in the callable slot. Bare shared and `own` target parameters are supported, while `mut` targets are rejected. An explicit stack capacity must have exact type `int64`; the first callable argument and every capture retain the same typing rules as an ordinary start. Queue iteration yields `T` by ownership transfer: the bare form is accepted, while the `own` and `mut` modifiers are rejected. Timeout and capacity expressions must have the documented exact types. Queue receive operations transfer one owned value and do not recheck payload Transfer. A supplied Queue capacity must be greater than zero. Every `select` source must be exactly `Queue[Q]`, `Task[T]`, or `Duration`. Queue payload types agree with one `Q`, Task result types agree with one `T`, and a missing category is inferred as `None`. A non-repeatable Task source is an owned observation and is moved at call entry; a repeatable Task and every Queue or Duration source is read without consuming its source binding. The Provisional Phase 5.6 boundary adds a structural `Transfer` obligation to every captured argument and target result for all four task-start methods, after generic specialization and before scheduling. A fully concrete generic call is checked after inference; an unresolved type parameter is rejected rather than becoming a deferred Transfer contract. The obligation applies to the owned capture even when the target declares a bare shared parameter and borrows that child-owned storage during its call. Queue construction, `put`, and `try_put` likewise require `T: Transfer`; handle copies, receive/fallback methods, and `close` do not recheck the payload. Copy types, `str`, recursively transferable collections/tuples/classes/enums, and `Queue`/`Task` handles pass; capability views, `random.Rng`, `TaskGroup`, and live host resources do not. `Transfer` is compiler-derived rather than a user trait. Reading a Copy value through shared or mutable access for a task argument captures an owned snapshot rather than the access capability, so that snapshot is permitted when its type is Transfer. A non-copy access cannot be captured this way because the child would need ownership. Explicit generic task targets may use `function[Types]` or `Type.associated_method[Types]` in the callable-target slot. Brackets remain ordinary indexing elsewhere. A bare target is accepted when its declarations and defaults already resolve complete concrete types. ## Runtime Semantics Aura tasks run on cooperative pinned workers. The default worker count is the available parallelism reported by the host, while provisional `AURA_WORKERS=` selects an explicit count. Starting a child stores its captures in task-owned storage and gives it a stable worker assignment. The child's coroutine stack remains on that worker for its entire lifetime; there is no migration or work stealing. Group exit observes or joins children, cancels an unbounded group-owned wait only when the live wait graph has no reachable waker, and propagates an unread child failure. Host elapsed time and machine load are not evidence that a wait is unreachable. Queue send and receive transfer one value by copy or move according to `T`; bounded queues suspend senders when full, close wakes waiters, and bare iteration repeatedly receives until its documented terminal condition. Timeout, cancellation, closure, and task failure are distinct enum outcomes. A nonpositive Queue capacity traps before a queue is constructed. Scheduling order, completion order among independent tasks, and program-output order are not specified. Descriptor waits use persistent reactor registrations, deadlines use a timer heap, and Queue, task-completion, and blocking-pool readiness is delivered by direct notification to the responsible worker. With no ready local work, a worker blocks until work, an event, or a deadline rather than polling on a fixed tick. Typed selection uses the same persistent wait machinery. One waiter subscribes to all Queue, Task, cancellation, and earliest-deadline sources, rechecks them before parking, and re-arbitrates in source order after a wake. Notifications do not choose the winner themselves. Committing a winner atomically consumes only that Queue item or selected Task result, then idempotently removes every losing subscription. Selection does not create helper tasks, migrate the selecting task, or introduce a periodic scheduler tick. Queue and Task handles are the maintained cross-worker communication surface. Their runtime state is synchronized so a Queue operation or task completion can wake a task pinned elsewhere. Every other captured argument and task result must be owned `Transfer` data, preserving a share-nothing boundary. Live host resources and capability views remain on their owning task. Cancellation and diagnostic state remain isolated per task: running or trapping on one worker does not replace another task's current cancellation or diagnostic context. A running child may create a nested `TaskGroup`, start grandchildren, and immediately wait on their returned handles on both backends. Child preparation allocates the guarded stack and task state before the handle is returned; a preparation failure is synchronous and admits no child. Successful nested starts are transferred through the scheduler's internal admission broker rather than mutating the scheduler through a second live reference. The broker preserves request FIFO internally, but ready-task and child execution order remain deliberately unspecified. Dynamic `json.parse` uses a separate process-global codec service with two 2 MiB-stack workers and total in-flight capacity two. The runtime reserves one of those slots before it makes the fallible owned source copy. A saturated lightweight task parks on a scheduler-aware availability notification rather than spinning. Once admitted, synchronous `json.parse` waits for codec completion; cancellation is deferred to the task's next ordinary cancellation boundary. The bounded `json.is_valid` and `json.parse_string_map` operations remain caller-side and do not use the service. The service is distinct from the protocol and generic blocking-I/O pools and lives until process exit. The remaining stack-safety and backend rules are in [Execution Model](/manual/execution-model) and [JSON Module](/manual/json). ## Ownership And Evaluation Order Call arguments are evaluated before a task can use its captured values; every non-copy capture moves into child-owned storage and a copy capture is copied. For a stack override, the capacity expression is evaluated once before the callable target and its captures. The child then borrows or consumes that storage according to the target's declaration-stable parameter mode. `put` owns its offered value and returns it inside `SendError` when no send occurs. Queue iteration captures the copyable handle once at loop entry, produces already-owned items, and never freezes or borrows the source binding. Task result observation clones a stored value only when the result is repeatable. A non-repeatable result instead carries one statically enforced observation right, and no alias may produce a second value. A `select(...)` call evaluates all source expressions once from left to right. It copies Queue, repeatable Task, and Duration sources, but consumes every non-repeatable Task observation right at call entry and deliberately abandons any such right that loses. ## Diagnostics `AU1101` reports malformed concurrency syntax, including unavailable spawn forms. `AU2001` reports unknown concurrency types, functions, or members. `AU2002` covers generic, duration, capacity, task-list, stack-byte, argument, and outcome type mismatch. `AU2004` reports invalid constructor or method argument binding. `AU2006` reports an explicit or inherited trait method that collides with a builtin `Queue[T]`, `Task[T]`, or `TaskGroup` member. `AU2999` covers unsupported targets, method-reference misuse, and remaining static concurrency rejections. `AU3001` reports use after a value moves into task or queue storage. `AU3002` reports invalid borrowed capture/storage use and the rejected `mut` task-target boundary. `AU3003` reports a mutating call through an immutable place, and `AU3004` reports the forbidden `own` and `mut` Queue-iteration modifiers. `AU3007` reports a task-result or multi-task observation whose produced value contains or may contain non-cloneable `random.Rng` state. Timeout, cancellation, closure, fullness, and an observed task error are typed values, not diagnostics. An unread child trap retains its original code. `AU4001` reports a general runtime trap, including zero or negative Queue capacity. `AU4001` also reports a negative, unrepresentable, or overflowing scheduler deadline because these APIs have no typed InvalidInput carrier. `AU4002` reports arithmetic overflow or underflow, `AU4003` a bounds or lookup violation, `AU4004` a zero divisor, and `AU4005` a resource or I/O failure. `AU4006` reports invalid pinned-worker or blocking-I/O runtime configuration. `AURA_WORKERS`, `AURA_BLOCKING_WORKERS`, and `AURA_BLOCKING_QUEUE_CAPACITY` each require a positive decimal integer; the diagnostic names the setting, renders the supplied invalid value, and is issued before user code. A non-Unicode value is displayed lossily. `AU2002` rejects an out-of-range literal stack request during checking. `AU4005` reports the exact same range violation for a dynamic request and reports task-stack allocation or platform-size failure; neither path clamps or falls back to the default. `AU3008` is reserved by the Provisional Phase 5.6 contract for a value that cannot cross a task or Queue boundary. The diagnostic identifies the boundary, then names the nested field, element, or payload path to the non-transferable leaf. Guidance recommends passing owned transferable data instead of a capability view, or keeping a host resource or `random.Rng` on its owning task and sending transferable input/output data. It never suggests implementing `Transfer`, because there is no user implementation surface. `AU3009` is different: the value has already passed the boundary, but a clone, clone-producing collection read, or implicit aggregate copy would duplicate a single-consumer task-result right. After a direct observation consumes that right, a second use of the same task binding is ordinary moved-value `AU3001`; attempting consumption through shared access is `AU3002`. The runtime's atomic defense rejects a second claim of a non-repeatable result with `AU4001`: `task result has already been observed; non-repeatable task results allow exactly one observing attempt`. A correctly checked Aura program should be stopped earlier by the static ownership diagnostics. For `select(...)`, `AU2004` reports an empty call or named source, `AU2002` reports an invalid source or inconsistent Queue/Task category type, `AU3002` reports a non-repeatable Task supplied without owned access, and `AU3009` reports the same visible non-repeatable Task twice. Dynamic invalid deadlines and runtime observation-claim failures remain `AU4001`. ## Backend Support Structured groups, task targets and captures, Queue operations and iteration, typed heterogeneous `select`, wait helpers, sleep, cancellation, compiler-inserted loop safepoints, and user-trait dispatch on `Queue[T]`, `Task[T]`, and `TaskGroup` for noncolliding method names are maintained on both MIR execution and direct native generation. Default and explicit guarded stack requests use the same scheduler allocation path on both backends. MIR checks each backedge and yields every 8 backedges. Native code uses 4,096 units of function-local fuel between yields when sibling tasks are possible and elides the check when the program proves that no sibling task can exist. Builtin handle member names retain builtin dispatch on both backends. The scheduler/runtime surface and complete diagnostics are parity-pinned. Both backends therefore share the persistent reactor, timer heap, and direct runtime-event notification behavior. MIR and direct-native traps capture the same typed Aura call frames and task ancestry once, before cleanup resets task-local state. Human output derives call-chain and parent-task notes from those records, while JSON and the LSP preserve the frame arrays directly. ## Limits And Implementation-Defined Behavior Task execution is cooperative, pinned-worker, and non-preemptive. Loop backedges have compiler-inserted scheduling checks, but one long loop body or long straight-line computation can still delay siblings assigned to the same worker. The checks do not inspect cancellation. Scheduling, independent task completion, and output order are deliberately unspecified. The worker count defaults to the available parallelism reported by the host and may be selected provisionally with a positive `AURA_WORKERS` value. Assignments never migrate and work is not stolen. Aura exposes no worker-index or affinity-introspection API. Ordinary lightweight tasks request 512 KiB of writable coroutine stack; an explicit request is limited to 64 MiB. Requests are page-rounded and guard-protected. The MIR/direct entry thread reserves 64 MiB. The scheduler keeps descriptor registrations persistent and blocks until an event or deadline when idle; it does not use a periodic readiness scan. Nested Aura calls stop at 256 frames. The process-wide blocking-I/O pool derives a default of 2 through 8 host threads from host parallelism (fallback 4), or uses an exact positive `AURA_BLOCKING_WORKERS` value without clamping. `AURA_BLOCKING_QUEUE_CAPACITY` optionally bounds pending accepted jobs; omitting it leaves the queue unbounded. The first runtime preflight reads this configuration once without starting the pool, and the configuration remains immutable for the process lifetime. First submission creates the complete worker set; production reuses it until process exit and has no Aura shutdown/join surface. Non-repeatable transferable task results have one statically enforced observation right. Cancelling after a blocking job is accepted cannot retract an OS side effect. If the scheduler itself stops with tasks still suspended, it disarms their waits, publishes cancellation to their handles and observers, and reclaims scheduler-owned and direct-runtime host state. That abandonment path does not run arbitrary Aura cleanup thunks; direct generated stacks may be reset because they cannot safely be unwound through Cranelift frames. Detached lightweight tasks are unavailable. ## Status Scheduler-backed lightweight tasks, structured `TaskGroup`, generic task handles and outcomes, bounded and unbounded queues, bare receive iteration, sleep, cooperative cancellation, task-result observation, multi-task waits, computed Duration arithmetic, and compiler-inserted loop-backedge safepoints are implemented. Phase 5.1 adds persistent reactor registrations, heap-managed deadlines, and direct Queue, task-completion, and blocking-pool wakeups; Phase 5.3 adds the automatic loop checks. Phase 5.4 moves deep HTTP, TLS, and maintained Unix WebSocket library steps to a distinct bounded protocol service with two named 2 MiB-stack workers and a 64-job queue, then makes ordinary coroutine stacks guarded 512 KiB requests and adds the Accepted ADR-0032 override methods. HTTP URL/request/response construction, head parsing, and chunk decoding; rustls construction, handshake, I/O, and close notification; and Unix WebSocket construction, handshake, framing, and close run there. Protocol state is owned by one bounded, nonblocking service step at a time and is returned before the coroutine observes cancellation or resumes reactor waiting. The process-global pool is initialized lazily, shared by all lightweight schedulers, remains alive until process exit, and intentionally has no 0.3 runtime shutdown/join API. Non-Unix WebSocket fallback retains its compatibility path. Plain socket/reactor operations remain scheduler-side; resolver, listener-bind, and file-read work uses the generic blocking-I/O pool. TLS asset bytes are read there, while PEM parsing and rustls construction run on protocol workers. Phase 5.4 also adds the bounded dynamic-`json.parse` service and scheduler-aware admission described above; the JSON flat-dictionary operations stay caller-side. The host-timer policy recorded by ADR-0019 is Accepted. Phase 5.5 gives the scheduler driver unique mutable ownership, routes nested starts through an owned internal broker, makes preparation failure synchronous, and contains scheduler teardown across MIR and direct tasks. Phase 5.7 makes Queue and Task handle state cross-worker safe and runs task bodies on spawn-time pinned workers on both backends. Task execution is multicore; work stealing and preemption are unavailable, task/output order is unspecified, and speedup depends on the workload. Accepted ADR-0034 implements the typed heterogeneous `select(source, ...)` builtin on both backends, using the shared persistent wait machinery for atomic registration, deterministic one-winner arbitration, cross-worker wakeups, and loser cleanup. It adds no statement syntax. Preemptive scheduling, `mut` task targets, and detached task syntax are unavailable. On the clean Mac14,9 Phase 5.10 measurement at `181204b`, 10,000 parked sleepers used 207,798,272 bytes of worst whole-process RSS and 198,787,072 bytes above their same-process pre-spawn baseline, passing the 512 MiB gate. The runtime accepts larger task counts; 10,000 sleepers is the maintained memory-capacity bound. The final Phase 5.10 100,000-sleeper plus 1,000-timer repetitions peaked at 1,170,735,104, 1,921,531,904, and 2,001,305,600 bytes, so two of three exceeded the 1.5 GiB gate. On this 16 KiB-page host, one resident page for each of the 101,000 stackful child coroutines alone requires 1,654,784,000 bytes before task metadata or the root runtime. The earlier Phase 5.9 pass depended on memory compression and reclaim behavior. The contractual 10,000-sleeper bound plus the timer, idle, starvation, and multicore controls all pass at Phase 5.10. The four-worker control has a `1.039673x` paired median wall-time ratio and `396.73%` median four-task process CPU on the measured Mac14,9 host. The Queue capacity boundary is pinned by `crates/aura-compiler/tests/fixtures/run-fail/queue_zero_capacity.au` and `crates/aura-compiler/tests/fixtures/run-fail/queue_negative_capacity.au` on both backends. Accepted ADR-0033 specifies the implemented Phase 5.6 contract: structural Transfer checks for task captures, task results, and Queue payloads, plus static repeatable/single-consumer task results. Phase 5.7 retains that share-nothing boundary while allowing Queue and Task handle identity to communicate between pinned workers. ## Source: docs/manual/conformance.md # Conformance Aura keeps the language reference and implementation aligned through executable conformance layers. This page identifies which tests substantiate each part of the specification and what a conforming implementation is expected to do. ## Conforming Programs And Implementations A **conforming Aura program** uses only syntax and APIs defined by this Manual and satisfies all static rules. A **conforming Aura implementation**: - accepts every conforming program within documented implementation limits - rejects programs that violate a MUST-level lexical, grammatical, name, type, ownership, or entrypoint rule - preserves the observable evaluation and cleanup behavior defined by the Manual - produces the specified typed outcomes or runtime failures - provides the maintained public API surface - does not expose proposal-only constructs as accepted 0.3 language features Exact diagnostic prose is normative only where a fixture or this Manual explicitly requires it. A conforming implementation otherwise needs a clear diagnostic with an accurate source location and the same stable `AU####` code; message wording may differ without changing that code's documented meaning. ## Executable Reference dict | Reference area | Primary executable evidence | | --- | --- | | UTF-8, indentation, tokens, literals, escapes | `crates/aura-compiler/src/lexer_tests.rs` | | Delimiter continuation, ignored continuation indentation, expression-match layout islands, trailing-comma/backslash/single-line-string boundaries, and pairing diagnostics | focused lexer/parser tests; `newline_continuation*` and delimiter parse/run fixtures; `examples/basics/multiline_expressions.au`; compiler-bridge and extension indentation tests; and the MIR/direct parity matrix | | Parenthesized tuple values/types, recursive assignment/loop targets and patterns, function returns, left-to-right capture, recursive Copy, whole-source non-Copy moves, shared leaf provenance, copy-only constant indexing, canonical rendering, same-type recursive structural `==`/`!=` with non-consuming reads, retained ordering rejection, and mutable-writeback rejections | focused lexer/parser/sema/MIR/native/runtime tests; the `tuple_structural_equality`, `tuple_equality_contextual_literals`, and retained `tuple_ordering_rejected` fixtures; other `tuple_*` parse/check/run fixtures; `examples/basics/tuples.au`; the executable `docs/manual/tuples.md` fence; the compiler-bridge tuple equality/ordering regression; and the MIR/direct parity matrix | | grammar and parser limits | `crates/aura-compiler/src/parser_tests.rs`, `tests/fixtures/parse-pass`, `tests/fixtures/parse-fail` | | FFI v0 package authorization and root dependency reports; bodyless extern/opaque grammar; fixed-width scalar, null-empty and pointer-length view ABI; same-length mutable copy-in/out and post-call writeback; opaque ownership and non-Transfer rules; direct-call-only process-global lookup; Unix MIR/direct parity; LSP/editor support; and reserved callbacks/raw pointers/variadics | focused lexer/parser/package/sema/analysis/MIR/native/runtime tests; `crates/aura-compiler/tests/ffi_frontend.rs`; `crates/aura/tests/ffi_acceptance.rs`; `examples/packages/ffi_getpid`; the executable [FFI v0](/manual/ffi) block; language-server recovery/compiler-bridge tests; extension grammar/snippet tests; and the forced backend parity matrix | | Conditional-expression precedence, exact-bool conditions, arm unification/context, lazy condition-first selection, conservative branch moves, analysis, and backend parity | focused conditional parser/sema/MIR/analysis tests; `conditional_expression_*` check/parse fixtures; `conditional_expressions` run fixture and example; compiler-bridge coverage; and the MIR/direct parity matrix | | Membership containers and delegation, chained-comparison precedence, at-most-once operand evaluation, short-circuiting, conservative chain checking, and backend parity | `comparison_chains_keep_every_operator_at_one_precedence_level`, `membership_tests_read_supported_containers_and_reject_the_rest`, and `comparison_chains_evaluate_each_operand_once_and_short_circuit`; `membership_*` check-fail and `membership_and_comparison_chains` check-pass fixtures; the `membership_and_comparison_chains` run fixture and example; five Python-shaped acceptance fixtures; and the MIR/direct parity matrix | | `enumerate`/`zip` loop-form recognition, operand domain, bare shared default, `int64` positions, shortest-operand `zip` termination, shadowing, function-wide per-loop binding-slot isolation under heterogeneous binding-name reuse, and backend parity | `enumerate_and_zip_iterate_in_lockstep_over_the_bare_loop_default`, `heterogeneous_ordinary_for_bindings_use_distinct_scoped_typed_slots`, `every_ordinary_for_form_uses_a_fresh_scoped_target_slot`, and `ordinary_for_target_scope_starts_after_iterable_evaluation`; `enumerate_requires_indexable_iterable` and `zip_rejects_ownership_modifiers` check-fail fixtures; the reversed heterogeneous `enumerate_and_zip` fixture; `tuple_for_pattern_queue` for recursive target reuse; `list_mut_iteration` for fallthrough/`continue`/`break`/explicit-return writeback; the maintained example; and the MIR/direct parity matrix | | `len` delegation domain; shared `int64` type and value across `len(x)` and `x.len()` for str, list, dict, and set; `str.byte_len()` as an `int64` UTF-8-byte count; `str` rendering equality with `print` and f-strings; and the reservation of both names | focused semantic, call-surface, MIR, native-codegen, runtime, analysis, and language-server tests; the `len_requires_a_len_member` check-fail fixture; the `len_and_str` run fixture and example; two Python-shaped acceptance fixtures; and the MIR/direct parity matrix | | Accepted ADR-0043 unified `int64` index domain: direct indices, slices, range bounds and yields, enumerate positions, Array coordinates, selection indices, collection lengths/search positions/capacity arguments, scoped lossless widening of fixed-width integer position values, exact `list[int64]` coordinate containers, and target-stable rejection outside that scope | `index_domain_positions_contextually_type_literals_as_int64`, `index_domain_accepts_default_int64_variables`, and focused semantic/MIR/native/runtime/analysis tests; `index_domain_int64_contract`, `index_domain_lossless_widening`, `index_domain_rejects_uint64`, and boundary fixtures; `index_domain_zero_cast_idioms`, which pins `values[values.len() - 1]`, `range(values.len())`, and enumerate-index-back-into-list without casts; `examples/collections/list_polish.au`; and the forced MIR/direct parity matrix | | Accepted ADR-0044 canonical `list`/`dict`/`set`/`str` surface: homogeneous literals, exact constructors and method signatures, first-match list search, stable natural/key sorting, typed dictionary absence and eager snapshots, loud/silent set removal, capacity control, exact ownership and evaluation order, `AU4008`/`AU4003`/`AU4005` failures, analysis/editor exposure, and backend parity | focused parser, call-surface, semantic, MIR, native, runtime-value, analysis, language-server, and extension tests; `canonical_collection_surface`, collection capacity, obligation, missing-value, and ownership fixture families; `examples/collections/list_basics.au`, `list_polish.au`, `dict_basics.au`, and `set_basics.au`; executable Types and Collections Manual blocks; the clean-surface identity gate; and the forced MIR/direct parity matrix | | names, types, calls, traits, patterns, moves, and borrows | `crates/aura-compiler/src/sema_tests.rs`, `tests/fixtures/check-pass`, `tests/fixtures/check-fail` | | integer `/` rejection, floor division/remainder, exact float-context integer literals, `.to_float()`, and shortest-roundtrip float printing | lexer/parser/integer/runtime-value unit tests plus `integer_true_division_*`, `floor_division_and_modulo`, `float_context_integer_literals`, `integer_to_float_rounding`, and `float_shortest_roundtrip_printing` fixtures | | Accepted ADR-0047 decimal separators and hexadecimal/binary/octal integer literals; exact-width bitwise operators; checked, wrapping, and saturating shifts; exact count typing/ranges; compound-store ordering; and MIR/direct parity | focused lexer/parser/sema/integer/MIR/native/runtime tests; literal and shift failure fixtures; the `bitwise_power` runtime fixture; `examples/numbers/bit_packing.au`; the executable Expressions Manual block; language-server and extension coverage; and the forced MIR/direct parity matrix | | Accepted ADR-0048 scalar math constants and functions: exact immutable `float64` constant bits, generic once-initialized module storage, exact function signatures, checked `int64` rounding conversions, IEEE-754 identity and exceptional-value classification, finite overflow and domain diagnostics, left-to-right once-only evaluation, and MIR/direct parity | `math_namespace_exposes_exact_generic_float64_constants`, `math_namespace_exposes_the_exact_float64_function_contract`, `math_host_builtins_follow_the_ratified_finite_contract`, `math_host_builtins_classify_every_exception_family`, `math_analysis_exposes_qualified_and_aliased_constant_details`, `math_analysis_completes_and_hovers_the_exact_public_surface`, and focused semantic tests; the `math_module_constants`, `math_module_functions`, and `math_log_domain` fixtures; `examples/numbers/scalar_math.au`; the executable [Math Module](/manual/math) block; compiler-bridge coverage; and the forced MIR/direct parity matrix | | Accepted ADR-0048 power, `round`, and `divmod`: right-associative precedence, exact numeric types, checked integer results, ties-to-even conversion, paired floor quotient/remainder, runtime classification, one-time evaluation, and MIR/direct parity | focused parser/sema/integer/runtime-value/MIR/native tests; `numeric_round_divmod`, `bitwise_power`, and numeric failure fixtures; `examples/numbers/bit_packing.au`; the executable Expressions Manual block; language-server and extension coverage; and the forced MIR/direct parity matrix | | Accepted ADR-0046 exact triple-quoted and raw string forms plus static f-string format specifications: delimiter and escape rules, no whitespace normalization, Unicode-scalar width and string precision, sign-aware numeric zero padding, type-directed numeric codes, binary32 identity, left-to-right evaluation, focused raw/triple-f diagnostics, formatter preservation, and MIR/direct parity | focused lexer/parser/sema/runtime-value/MIR/native tests; `string_literal_forms_and_format_specs`, `fstring_zero_padding`, and `fstring_*` rejection fixtures; `examples/strings/literal_forms_and_formatting.au`; executable lexical and expression reference blocks; compiler-bridge and extension grammar/snippet tests; and the forced MIR/direct parity matrix | | Accepted ADR-0049 match guards, or-patterns, and top-level catch-all bindings: exact-`bool` guards, left-to-right alternatives, identical alternative bindings, guarded-arm reachability and exhaustiveness, delayed `match own` extraction, mutable candidate writeback before false continuation/failure/trap and every selected-arm exit, complete-scrutinee binding capabilities, and MIR/direct parity; class patterns remain a provisional unimplemented disposition | focused parser/sema/MIR/analysis tests; the `match_guard*`, `match_own*`, `match_mut_guard*`, `match_root_binding_patterns`, `or_pattern*`, guarded-exhaustiveness, and focused class-pattern-rejection fixtures; `examples/enums/match_guards_and_or_patterns.au`; the executable Enums and Match Manual block; compiler-bridge tests; and the forced MIR/direct parity matrix | | Accepted ADR-0050 module constants: inferred/annotated/public declarations, declaration-order scope, dependency-first and source-ordered eager once-only initialization, one defining storage identity, Copy versus shared non-Copy reads, move/mutation rejection, guarded re-entry, package visibility, analysis/editor exposure, cleanup, and MIR/direct parity | focused parser/sema/MIR/native/runtime/analysis tests; `module_constant*` check/run fixtures; multi-module and package dependency tests; `examples/modules/constants.au`; executable Names and Scopes, Statements, and Ownership Manual blocks; language-server compiler-bridge tests; and the forced MIR/direct parity matrix | | signed-i128-nanosecond Duration literals, exact two-limb direct ABI, constructors, checked arithmetic, `FloorDiv`, comparison, conversion, rendering, and invalid host timers | `duration_literals_scale_to_nonnegative_i128_nanoseconds_at_each_unit_boundary`, parser/MIR/native-codegen/runtime unit tests, `docs/manual/concurrency.md#aura-1`, Duration run/failure fixtures, native-runtime FFI tests, and the MIR/direct parity matrix | | persistent descriptor registrations, heap-ordered deadlines, direct Queue/task-completion/blocking-pool wakeups, wait-epoch race containment, one-winner cleanup, and event-or-deadline idle blocking without a periodic tick | `runtime_reactor` unit tests including `timers_fire_at_the_earliest_deadline_and_preserve_equal_deadline_order`, `one_persistent_fd_registration_aggregates_waiters_and_narrows_interest`, and `waker_coalescing_does_not_lose_inbox_entries_and_ready_is_deduplicated`; runtime-value direct-wakeup and cleanup tests; `scheduler_model` lost-wake/stale-epoch/one-winner state-space tests; `scheduler_mixed_wakeups_complete_in_mir_and_direct_backends`; `scripts/stress-scheduler.sh`; the contractual `scripts/bench-scalable-runtime.py` after-reactor run; and the MIR/direct parity matrix | | `yield_now` cooperative scheduling, zero-argument typing, explicit ready-set requeue, unit result, and backend parity | focused call-surface, semantic, MIR-runtime, native-codegen, analysis, language-server, and extension tests; the `yield_now` check/run fixtures; `examples/concurrency/yield_now.au`; and the forced MIR/direct parity matrix | | compiler-inserted scheduling checks on every ordinary and `continue` loop backedge; exit-path bypass; no implicit cancellation check; amortized function-local MIR/native fuel; sequential-program elision; and timer/Queue/socket progress on both backends | focused MIR lowering, MIR-runtime, native-codegen, and validation tests; `loop_backedge_safepoints_prevent_timer_and_queue_starvation`; the loopback-socket safepoint regression; the `sleeper_vs_hot_loop.au` scalable-runtime workload; the contractual starvation benchmark; and the forced MIR/direct parity matrix | | Accepted ADR-0032 guarded 512 KiB default task stacks, exact `int64` 256 KiB..64 MiB collision-free overrides with 256 KiB reserved for measured shallow tasks, page rounding without clamping, and off-coroutine HTTP/TLS/WebSocket protocol steps | focused call-surface, semantic, MIR, native-codegen, scheduler-allocation, protocol-service, recursion, language-server, and both-backend CLI tests; maintained loopback HTTP, TLS, and WebSocket round trips; the scalable-runtime same-process baseline/parked-task measurements; and the MIR/direct parity matrix | | unique mutable scheduler ownership; owned nested-start admission with synchronous preparation failure and safe immediate waits; internal FIFO admission without a public scheduling-order promise; teardown cancellation and observer wakeup; MIR/Rust unwind; and exact-once direct child/root stack-reset containment | `nested_spawns_are_fifo_and_an_immediate_child_wait_is_safe`, `nested_stack_allocation_failure_is_synchronous_and_does_not_enqueue_a_task`, `lightweight_scheduler_teardown_cancels_abandoned_tasks_and_runs_cleanup_once`, `pure_rust_abandoned_task_unwinds_owned_values_once_at_teardown`, `direct_cleanup_can_spawn_a_child_before_the_parent_is_retired`, and `generated_root_cleanup_runs_once_on_forced_exit_and_not_on_normal_return` in `runtime_value_tests`; the direct-root, unstarted-task, started-task, and normal-completion ownership tests in `native_runtime_tests`; the event-multiset oracle in `scheduler_nested_spawns.au`; `nested_scheduler_spawns_preserve_outcomes_cleanup_and_backend_parity`; and the raw-scheduler-alias rejection in `scripts/check-hygiene.sh` | | Accepted ADR-0033 structural Transfer, owned Copy snapshots, explicit/concrete generic task targets, Queue constructor/send payload enforcement, conditional Task Copy, static single-consumer observation, `AU3008` boundary diagnostics, `AU3009` duplication diagnostics, and atomic one-winner runtime defense | `task_boundaries_accept_structurally_transferable_values_and_results`, `task_boundary_diagnostics_explain_the_exact_nested_non_transfer_reason`, `task_transfer_checks_use_the_concrete_generic_specialization`, `queue_transport_requires_transfer_payloads_but_handle_only_methods_do_not`, `owned_builtin_snapshots_are_transfer_but_live_authority_is_not`, `task_target_explicit_specialization_and_contextual_defaults_are_concrete`, `task_capture_materializes_copy_snapshots_but_not_noncopy_shared_views`, `task_result_observation_rights_follow_repeatability`, and `clone_producing_operations_cannot_duplicate_task_observation_rights`; runtime-value, MIR-runtime, and native-runtime claim tests; the `task_transfer_*`, `queue_transfer_*`, and `task_result_*` check fixtures; `task_transfer_runtime_matrix.au`; its MIR/direct CLI parity test; compiler-service/LSP evidence; and the forced backend parity matrix | | Pinned-worker multicore task execution, available-core default, provisional positive `AURA_WORKERS` override and exact `AU4006` rejection, stable spawn-time affinity across yield/timer/Queue waits, no migration or work stealing, cross-worker Queue/Task wakeups, per-task cancellation/diagnostic isolation, and MIR/direct parity with unspecified scheduling/output order | `lightweight_worker_count_defaults_and_rejects_invalid_overrides`, `lightweight_tasks_are_pinned_across_yield_timer_and_queue_waits`, `lightweight_workers_make_cpu_progress_concurrently`, task-context isolation tests, the `multicore_queue_task_matrix` run fixture, its MIR/direct CLI parity test, and the forced backend parity matrix | | Accepted ADR-0035 blocking-I/O worker configuration, optional pending-only queue capacity, exact explicit counts, compatible unbounded default, FIFO scheduler-aware admission, pre-acceptance timeout/cancellation, accepted-job abandonment, lazy all-or-nothing startup, fatal pre-user-code `AU4006` validation, resolver-saturation recovery, default-parallel watchdog stability, and MIR/direct/standalone parity | `runtime_config` decoding tests; focused `BlockingIoPool` lifecycle, capacity, FIFO, race, abandonment, and injected-resolver tests in `runtime_value_tests`; forced-backend and standalone configuration/admission tests in `crates/aura/tests/cli.rs`; and the MIR/direct parity matrix | | Accepted ADR-0036 typed runtime call frames and task ancestry, once-only pre-cleanup capture, per-frame source paths, human-note synthesis, additive diagnostic-schema-v1/LSP propagation, native private trap transport, normal-status distinction, and exact MIR/direct parity without frame masking | diagnostic, MIR-runtime, native-runtime, native-codegen, CLI cold/warm/concurrent-cache, LSP bridge, run-fail fixture, and complete forced-backend parity tests | | deterministic xoshiro256** seeding/output, unbiased half-open integers, 53-bit floats, Fisher-Yates writeback, rendering, unavailable equality, direct and transitive no-clone ownership, inferred generic and trait clone-safety contracts, and OS-secure integer/byte boundaries | `src/randomness.rs`; `random_rng_clone_safety_defers_generic_obligations_to_use_sites`, `imported_rng_clone_obligations_and_qualified_wrapper_identity_survive_namespaces`, and focused trait/operator/`From` semantic tests; `random_deterministic_sequences`, `random_projected_shuffle`, `random_render`, `equality_rng_direct`, `random_transitive_clone_rejected`, `random_secure_smoke`, `random_invalid_*`, `random_secure_bytes_request_ceiling`, and `random_secure_bytes_request_ceiling_i64_max` fixtures; verified clone-safety examples in `docs/manual/generics-and-traits.md`; native-runtime FFI and language-server tests; and the MIR/direct parity matrix | | `list[uint8]` bytes, strict UTF-8 conversion, lowercase/mixed-case hex, canonical padded base64, typed malformed-input offsets, raw SHA-256, shared inputs, and output-size preflights | `src/bytes_codec.rs` unit tests; `bytes_codecs_and_hashing`, `bytes_typed_errors`, and reserved-encoding fixtures; `examples/bytes/codecs_and_hashing.au`; the executable `docs/manual/bytes.md` fence; language-server tests; allocation-boundary tests; and the MIR/direct parity matrix | | Provisional ADR-0045 assertion introspection: exact operand types, once-only left-to-right comparison and membership evaluation, shared-dispatch eligibility, bounded typed operand captures, lazy once-only messages, exact default/custom/empty/whitespace text, `AU4001` keyword span, operand and cleanup precedence, schema-1 structured fields, top-level scripts, and no stripping | `assert_*` parse/check/run fixtures and compiler unit tests; assertion CLI tests for forced MIR/direct execution and function-level `aura test`; `examples/basics/assertions.au`; the executable `docs/manual/assertions.md` fence; language-server and extension packaging tests; and the MIR/direct parity matrix | | Provisional ADR-0045 test runner: source-order function and file-level discovery, canonical names, literal case-sensitive `-k` after expansion, zero-match success, per-case isolated setup/case/teardown ordering, teardown-after-trap and structured secondary failure, one-time ordered parameter registration with capture-free functions, output capture, timeouts, normalized paths, and schema-versioned ordered JSON records | the `aura_test_*` CLI family covering discovery, filters, JSON, hooks, checked-module reuse, FFI, parameter registration, capture rejection, labels, and timeouts; `aura_test_maintained_assertions_example_pins_the_runner_contract`; `examples/basics/assertions.au`; Tutorial 23; the normative CLI and Tooling testing contract; and ADR-0045's completion matrix | | Application-level HTTP retry composition: retry only `503`, deterministic seed-42 jitter, exponential `Duration` backoff, final-attempt no-RNG/no-sleep behavior, explicit deadlines, and scoped resource cleanup | `examples/agents/retrying_network_worker.au` and `retrying_network_worker_runs_with_computed_backoff_on_both_backends` in `crates/aura/tests/cli.rs`, which pin the exact seven-request loopback trace on the MIR and forced-direct backends | | Callable-powered list algorithms: stable mutable natural/key sorting, once-only left-to-right key evaluation before mutation, trap-before-mutation, eager shared map/filter traversal, owned results, source retention, exact shared callback capabilities, and filter clone safety | `list_algorithm_callbacks`, `list_sort_requires_mut_receiver`, `list_sort_rejects_non_orderable`, `list_map_callback_requires_shared`, `list_filter_rng_clone_safety`, and builtin-collision fixtures; focused semantic, MIR, runtime, native, analysis, and LSP tests; `examples/collections/list_algorithms.au`; the executable Collections Manual block; and the MIR/direct parity matrix | | `control.retry`: immediate first attempt, every-Err retry policy, exact attempt budget, doubling Duration backoff, zero-delay sleep elision, no post-final sleep/multiply, exact last-error return, worker-trap/overflow/cancellation propagation, and both-backend behavior | `control_retry_surface` parse/check fixtures; `control_retry_basics` run fixture; focused compiler/runtime tests; `crates/aura/tests/control_retry.rs`; `examples/agents/retry_with_backoff.au`; the executable Control-Plane Manual block; and the MIR/direct parity matrix | | Accepted ADR-0037 expression closures: contextual bare/`mut`/`own` parameters, expression-only bodies, Copy snapshots, non-Copy move-at-creation, repeatable reads, consuming single-use calls, read-only environments, shared-capability rejection, structural Transfer, cleanup, and MIR/direct parity | lambda parser/check/run/failure fixture families; focused semantic, MIR, runtime, native-codegen, analysis, completion, language-server, and extension tests; `examples/basics/closures.au`; the executable Closures Manual block; and the forced MIR/direct parity matrix | | Accepted ADR-0039 eager owned comprehensions: list/set/dictionary forms, exact-Boolean filters, progressive non-leaking target scope, nested outer-major order, left-to-right filters, dictionary key-before-value replacement, every bare iterable including Queue receive ownership, explicit clone/move behavior, ADR-0037 capture interaction, generator/modifier teaching diagnostics, cleanup, and MIR/direct parity | comprehension parser/check/run/failure fixture families; focused semantic, MIR, runtime, analysis, completion, language-server, and extension tests; `examples/collections/comprehensions.au`; the executable Collections Manual block; and the forced MIR/direct parity matrix | | Accepted ADR-0040 owned list/str slices under the unified ADR-0043 index domain: all omitted endpoint forms, `int64` endpoints with scoped lossless widening, once-only negative normalization, no clamping, invalid/reversed `AU4003`, Unicode-scalar str O(n) behavior, list clone-safety and task-repeatability, retained evaluate-once order, source/result independence, reserved step/assignment `AU2005`, str integer-index rejection, and MIR/direct parity | slice parser/check/run/failure fixture families; focused semantic, MIR, runtime, analysis, completion, language-server, and extension tests; `examples/collections/slices.au`; the executable Collections Manual block; and the forced MIR/direct parity matrix | | Accepted ADR-0041 contiguous numeric Arrays and explicit integer modes: four exact dtypes, rank-at-least-one row-major owned storage, zero dimensions, constructor/count checks, multidimensional indexing, first-axis copy slices, exact-shape/scalar kernels, float-only division, deterministic row-major map/reductions, `float64` mean, checked/wrapping/saturating integer behavior, `AU4002` checked Array overflow and `AU4004` floating Array zero-divisor failures, `AU4003` coordinate/slice bounds, `AU4005` allocation/element-count failures, `AU4007` shape/rank/count/empty-reduction failures, and no array-shape broadcasting/views/promotion/accelerator | numeric-array semantic/MIR/runtime/native/parity fixture families, including synchronized checked-overflow and floating-zero-divisor diagnostics; compiler analysis, language-server, and extension protocol tests; `examples/numbers/numeric_arrays.au`; the executable Numeric Arrays Manual block; the tested `bench-numeric-arrays.py` raw/summary evidence protocol; and the forced MIR/direct parity matrix | | recursive JSON parse/dump semantics, exact numeric classification, typed parse errors, deterministic formatting, accessors, ownership, and resource limits | JSON codec/runtime-value unit tests, including exact materialized-node boundaries and deterministic allocation-failure injection; `json_dynamic_values`, JSON ownership and run-fail fixtures; `examples/json/dynamic_values.au`; the executable `docs/manual/json.md` fence; language-server tests; and the MIR/direct parity matrix | | dict duplicate-key replacement, key-before-value effects, indexed-read/simple-write ownership, and missing-key traps | `dict_literal_duplicate_keys`, `dict_index_non_copy_requires_explicit_clone`, `dict_index_assignment_consumes_noncopy_key`, and `dict_index_missing_key` fixtures plus the MIR/native parity matrix | | Supplied/default order and named enum-argument source order with declaration-slot binding | `explicit_and_default_argument_order` plus the MIR/native parity matrix | | Copy-value capture, immediate f-string rendering, and receiver-before-argument effects | `left_to_right_value_snapshotting` plus the MIR/native parity matrix | | Compound binary dispatch for root/projected targets, copy-target capture, retained non-copy `AU3002`, and copy-only list/dict indexed targets | `operator_traits`, `left_to_right_value_snapshotting`, `compound_noncopy_target_rejects_rhs_mutation`, `list_compound_assignment_noncopy_element_rejected`, and `dict_compound_assignment_noncopy_value_rejected` fixtures plus the MIR/native parity matrix | | Dedicated `AU3005`/`AU3006` indexed ownership codes, `AU3003` mutable-receiver classification, and `AU2005` str-constructor guidance | `list_index_non_copy_requires_explicit_clone`, `dict_index_non_copy_requires_explicit_clone`, `list_compound_assignment_noncopy_element_rejected`, `dict_compound_assignment_noncopy_value_rejected`, `immutable_mutating_method`, and `string_constructor_not_supported` fixtures plus the compiler-bridge tests | | Clone-safety-aware `AU3005` indexed-read guidance, so the recommended recovery is never rejected in turn by `AU3007` | `random_list_index_requires_transfer`, `random_transitive_dict_index_requires_transfer`, `generic_list_index_clone_safety_guidance`, and `generic_dict_index_clone_safety_guidance` fixtures, the `random_index_remove_transfers_ownership` transfer fixture, and the compiler-bridge propagation test | | Dedicated `AU2007` builtin function redefinition code, distinct from the `AU2006` builtin method collision | `builtin_function_names_cannot_be_redefined` fixture | | Access-kind-specific `AU3002` recovery help, naming the read, mutation, or consumption that actually conflicts | `nested_consume_and_borrow_same_call`, `call_own_then_projected_copy_read_rejected`, and `binary_left_borrow_rejects_later_mutation` fixtures | | Current class-field-default callable limit | `class_field_default_user_function_not_supported` fixture | | Retained non-copy binary/index/method-receiver/call-argument/indexed-assignment borrows, nested-consumption containment, `AU3002` overlap rejection, and no hidden deep clone | `binary_left_borrow_rejects_later_mutation`, `projected_binary_left_borrow_rejects_later_mutation`, `index_base_borrow_rejects_index_mutation`, `indexed_assignment_target_rejects_index_mutation`, `method_receiver_borrow_rejects_nested_argument_mutation`, `retained_receiver_nested_consumption_repro`, `retained_argument_nested_consumption_repro`, `method_receiver_rejects_nested_argument_consumption`, and `retained_parameter_rejects_nested_argument_consumption` fixtures | | Declaration-stable call/operator passing, directional exclusive-access checks, and the distinct task-capture boundary | `generic_borrow_specialization_retains_copy_argument`, `call_borrow_mut_then_copy_read_rejected`, `call_own_then_projected_copy_read_rejected`, `trait_operator_borrow_mut_receiver_requires_mutable`, `trait_operator_copy_left_retains_borrow`, `trait_operator_own_receiver_moves_value`, `trait_operator_own_receiver_rejects_rhs_read`, `trait_operator_own_rhs_moves_value`, `trait_unary_operator_own_receiver_moves_value`, `operator_trait_value_receiver_snapshot`, `task_capture_snapshots_copy_arguments`, and `task_group_receiver_rejects_owned_variadic_capture` fixtures plus the MIR/native parity matrix | | Accepted ADR-0017 one-time list/set own-iteration selection and Queue handle capture without source-binding retargeting | `own_iteration_captures_collection`, `queue_iteration_captures_handle`, and the MIR/native parity matrix | | Queue receive-item ownership, accepted bare iteration, rejected `own`/`mut` iteration modifiers, and progress after an earlier producer completes while CPU burners exceed the default worker count | `queue_bare_iteration_ownership`, `queue_own_iteration_rejected`, the maintained `mut` rejection fixtures, `check_and_direct_backend_reject_queue_iteration_modifiers`, and `queue_iteration_consumers_complete_with_more_cpu_burners_than_default_workers` on MIR and direct | | Accepted ADR-0034 typed heterogeneous `select`: exact inference, ownership, cancellation-first/lowest-index arbitration, atomic registration, one winner, loser cleanup, cross-worker wakeups, nested generic payload typing, and MIR/direct parity | `typed_select_*` compiler/runtime tests, `select_*` fixtures and CLI tests, the maintained select example, compiler-bridge/editor coverage, and the forced-backend parity matrix | | Builtin trait-method no-shadowing across every builtin target, inherited-default containment, and direct builtin precedence | `builtin_queue_trait_method_collision`, `builtin_task_inherited_trait_method_collision`, `builtin_task_group_trait_method_collision`, `builtin_list_trait_method_collision`, `builtin_string_trait_method_collision`, and `builtin_file_trait_method_collision` fixtures plus `builtin_method_names_cannot_be_shadowed_on_any_builtin_target` and `direct_backend_prefers_builtin_handle_member_if_collision_reaches_mir` | | Fixed 256 MiB filesystem, 64 MiB stream/TLS-configuration, and 16 MiB incoming HTTP limits | injectable-limit and sparse-file tests in `src/runtime_value_tests.rs` plus MIR/forced-direct filesystem and HTTP tests in `crates/aura/tests/cli.rs` | | module and package resolution, module aliases, per-entry from-import aliases, canonical target identity, alias visibility/collisions, and alias-aware analysis | `crates/aura-compiler/tests/modules.rs`, `tests/packages.rs`, `src/package_tests.rs`, the `import_aliases` parity fixture, `examples/modules/import_aliases.au`, and compiler-service/LSP alias tests | | MIR semantics and runtime behavior | `src/mir_tests.rs`, `src/mir_runtime_tests.rs`, `tests/fixtures/run-pass`, `tests/fixtures/run-fail` | | native semantics and resource ABI | `src/native_codegen_tests.rs`, `src/native_runtime_tests.rs`, `tests/native_runtime_ffi.rs` | | MIR/native observable equivalence | `crates/aura/tests/backend_parity.rs` | | CLI, entrypoints, diagnostics, and installed builds | `crates/aura/tests/cli.rs`, `crates/aura/tests/packages.rs` | | analysis, completion, hover, definitions, invalidation | `tools/aura-language-server/test` | | maintained examples | compiler example smoke tests and CLI product tests | The exact repository gate is `npm run ci`. It runs formatting, Rust tests, backend parity, language-server and extension tests, compiler and LSP coverage gates, this reference check, the documentation build, dependency audits, Clippy with warnings denied, and repository hygiene. ## Fixture Classes The compiler fixture directories have distinct contracts: - `parse-pass`: source MUST form a valid AST; later static checking is not implied. - `parse-fail`: source MUST be rejected during lexing or parsing with the stored diagnostic. - `check-pass`: source MUST parse and satisfy the static semantics. - `check-fail`: source MUST parse and then be rejected by static checking with the stored diagnostic. - `run-pass`: source MUST check and produce the stored standard output through the maintained execution path. - `run-fail`: source MUST check far enough to reach the intended runtime failure and produce the stored diagnostic behavior. Regression tests supplement fixtures when a case needs multiple files, temporary packages, local sockets, processes, timing, cancellation, or comparison of execution backends. ## Backend Equivalence Aura 0.3 has two maintained semantic runtime representations: - `aura run` lowers checked source to MIR and executes it in the MIR runtime. - `aura build --backend direct` lowers checked source to native code through the direct backend and links the native runtime. - the default `aura build --backend auto` first attempts direct emission and may instead build a native launcher containing serialized MIR plus the MIR runtime. For the maintained source subset, the paths MUST agree on: - standard output and integer exit status - return values and pattern results - checked arithmetic and collection failures - move/borrow-sensitive mutation and writeback - eager comprehension order, ownership, target scope, and partial-result cleanup - owned list/str slice endpoint order, bounds failures, Unicode scalar selection, clone safety, and source/result independence - `with` cleanup order and primary runtime diagnostics - complete structured runtime diagnostics, including typed call frames and task ancestry - task, queue, cancellation, process, filesystem, and network outcomes within platform constraints The parity matrix executes every eligible runtime fixture through both paths and compares complete diagnostics without masking backend-specific frame notes. A fixture may be excluded only through the explicit exclusion list, with a reason that corresponds to an intentional harness or platform boundary rather than an unexplained semantic divergence. ## Documentation Conformance Reference changes are checked by `npm run check:reference`. The gate retains the normative-page, navigation, grammar-anchor, execution-order, canonical surface, and deleted-evaluator guards. It inventories every fenced block in `docs/manual`. Fences labeled `aura` are Aura source. Bash, EBNF, JSON, text, and TOML fences require an explicit contract, as does every other fence language. A `python` fence means Python and is never interpreted as Aura. Every fenced block has a source-hash-pinned contract in `scripts/reference-integrity.json`: | Contract | Gate behavior | | --- | --- | | `check` | extract the exact block and require `aura check` to succeed with the pinned output | | `run` | extract the exact block and require `aura run` to produce the exact pinned standard output and standard error | | `check-fail` | require rejection with the pinned exit status and diagnostic fragment | | `package-check` | place the exact Aura block in a metadata-pinned local package layout and require `aura check` to succeed without network access | | `command` | parse one exact Bash command without a shell and execute only the gate's allowlisted side-effect-free `aura check`/`aura run` form for a maintained `examples/*.au` path, with pinned output | | `illustrative` | do not execute the block; require a specific reason explaining why it is notation, output, a dependent fragment, an unsafe command, or otherwise not a standalone executable unit | The command contract never invokes a shell, follows pipes or continuations, or runs build, network, dependency-update, server, or recursive repository-gate commands. A documented `cargo run -p aura -- ...` prefix is normalized to the already-built `aura` binary before the allowlisted subcommand runs. The proof is therefore about the displayed Aura CLI behavior, not Cargo itself. Unsafe or orchestration-only command blocks remain illustrative with their boundary stated explicitly. The source hash makes changes fail closed: editing, replacing, or reordering fenced blocks requires an explicit review of their contracts. Adding a Manual page also requires classifying it as a feature page or as a structural page with a reason. Structural pages organize cross-cutting contracts. Every feature page MUST contain these non-empty level-two sections: - `Grammar` - `Typing Rules` - `Runtime Semantics` - `Ownership And Evaluation Order` - `Diagnostics` - `Backend Support` - `Limits And Implementation-Defined Behavior` - `Status` The `Diagnostics` section MUST name each applicable stable `AU` code. If a feature introduces no feature-specific diagnostic, it states exactly `No feature-specific diagnostics.` instead. This is an explicit audited claim, not permission to omit general diagnostics that apply to examples on the page. Every feature page MUST also contain at least one verified fenced example in a non-illustrative mode. A page cannot satisfy that rule with a stale source hash or with an explanation-only fragment. This ensures that all current feature chapters have a live compiler, package, or safe CLI proof rather than relying only on prose. The gate reports the total page and all-language fence inventory, verified-versus-illustrative counts, per-page example counts, every missing normative section, and every feature page without a verified example before failing. Its focused Python tests pin all-language fence extraction, stale-metadata rejection, illustrative-reason enforcement, the feature-section and executable-example contracts, safe command/package preparation, and compiler outcome matching. The documentation build separately checks links and rendering. Language-facing changes still require compiler fixtures or maintained examples as directed by `AGENTS.md`; a checked Manual block proves the documented example's stated outcome, not every edge of the underlying rule. ## Adding Or Changing A Rule A language or tooling behavior change is complete only when the same pass updates, where relevant: 1. a failing compiler, runtime, CLI, or LSP test 2. the implementation 3. the normative Manual page and grammar when syntax changes 4. the API Index when public APIs change 5. Current Limits when a boundary is added or removed 6. categorized examples and Learn/tutorial material 7. the task board and dated work note Syntax expansion is frozen for the 0.3 technical-preview release. A new construct therefore needs an explicit compatibility decision rather than being accepted solely because it is easy to parse. ## Deriving A Book A book may treat this reference as its factual source. It may introduce concepts in a different order, add motivation, diagrams, exercises, and larger examples, or omit advanced details from early chapters. It must preserve these constraints: - every taught syntax form appears in the complete grammar - every claimed type or ownership behavior agrees with the static semantics - every runtime/API claim links back to a maintained contract - proposal-only features are labeled as future design, not current Aura - examples are compiled or run as part of the maintained repository surface This division lets the reference remain precise while the book remains readable. ## Source: docs/manual/control-plane.md # Control-Plane Modules Aura 0.3 includes a small, typed host/control-plane surface intended for service launchers, workers, evaluation harnesses, and agent infrastructure. These modules behave the same through `aura run` and direct native binaries. ## System And Path Import `sys` for process arguments, environment access, the current directory, and clocks: | API | Signature | | --- | --- | | `sys.args` | `args() -> list[str]` | | `sys.env` | `env(name: str) -> Option[str]` | | `sys.current_dir` | `current_dir() -> Result[str, io.Error]` | | `sys.unix_time_ms` | `unix_time_ms() -> int64` | | `sys.monotonic_time_ms` | `monotonic_time_ms() -> int64` | Pass program arguments after a separator: ```bash aura run worker.au -- --model small --port 8080 ./worker --model small --port 8080 ``` `sys.args()` excludes the executable name. In `aura run`, arguments after `--` are passed explicitly into the MIR execution context and inherited by child tasks. A built program reads its real host command line; ambient environment variables cannot override it. On hosts that permit non-Unicode argv bytes, built programs replace invalid byte sequences with the Unicode replacement character. `sys.env` returns `None` both when a variable is missing and when its host value is not valid Unicode. `sys.current_dir` and the string-producing `path` helpers convert non-Unicode host paths lossily. `unix_time_ms` is milliseconds since the Unix epoch; `monotonic_time_ms` is milliseconds since the first call to that function in the current process and is suitable for elapsed-time comparisons, not wall-clock timestamps. `path` provides host-aware `join`, `parent`, `file_name`, `extension`, and `is_absolute` operations. Components that may not exist return `Option[str]`. ## JSON And TOML JSON supports an arbitrary recursive tree through `json.Value` and typed parse failures through `json.Error`: | API | Signature | | --- | --- | | `json.parse` | `parse(text: str) -> Result[json.Value, json.Error]` | | `json.dumps` | `dumps(value: json.Value, indent: Option[int64] = None) -> str` | Exact inspecting and consuming accessors are listed in [JSON Module](/manual/json), which is the normative contract for number classification, source positions, ordering, formatting, and resource limits. The flat `dict[str, str]` helpers provide a typed data API alongside the dynamic JSON tree. TOML uses the same typed top-level dictionary boundary: | API | Signature | | --- | --- | | `json.is_valid` | `is_valid(text: str) -> bool` | | `json.stringify_map` | `stringify_map(value: dict[str, str]) -> Result[str, str]` | | `json.parse_string_map` | `parse_string_map(text: str) -> Result[dict[str, str], str]` | | `toml.is_valid` | `is_valid(text: str) -> bool` | | `toml.stringify_map` | `stringify_map(value: dict[str, str]) -> Result[str, str]` | | `toml.parse_string_map` | `parse_string_map(text: str) -> Result[dict[str, str], str]` | JSON compact output has sorted object keys. `json.is_valid` accepts any valid JSON value, while `json.parse_string_map` succeeds only for an object whose values are all strings. TOML output is a sorted top-level string dictionary; `toml.is_valid` accepts any valid TOML document, while `toml.parse_string_map` rejects nested tables and non-string values. Aura 0.3 has no derived class/enum schemas or generated codecs. ## Logs, Metrics, And Traces `log.debug/info/warn/error(message, fields)` and `trace.event(name, fields)` emit one compact JSON record to standard error. `fields` is a `dict[str, str]`. Every record has the shape `{ "kind": "log" | "trace", "level": str, "message": str, "fields": Object }`; for trace events, `level` is `event` and `message` is the event name. `metrics.increment(name, value)`, `metrics.get(name)`, and `metrics.reset()` provide process-global signed `int64` counters shared by Aura tasks in that process. A missing counter reads as zero. Incrementing past either `int64` bound is a runtime diagnostic and leaves the checked operation incomplete. These counters are useful for worker and test instrumentation; Aura 0.3 has no metrics exporter, export protocol, or scoped trace span API. ## Network Boundary The HTTP client accepts `http://` and certificate-validated `https://` URLs using the platform-independent Web PKI root set. HTTP request and response bodies support content length, connection-close framing, and chunked transfer encoding. The 0.3 parser keeps a 16 MiB incoming wire-message limit, accepts at most 64 headers, and rejects conflicting framing headers. Its `dict[str, str]` header boundary cannot represent repeated equal header names losslessly. For custom certificate authorities and TLS servers, use the lower-level `net.tls_connect*` and `net.tls_listen` APIs documented in [Network Module](/manual/network). ## Example This self-contained validation example is safe to run without host files, network access, or environment assumptions: ```aura import json def main(): print(json.is_valid("{\"ready\":\"yes\"}")) ``` See `examples/agents/control_plane_foundations.au` for path operations, JSON/TOML metadata, counters, and structured events. ## Retry Import `control` for the eager retry helper: | API | Signature | | --- | --- | | `control.retry` | `retry[T, E](worker: def() -> Result[T, E], max_attempts: int32 = 3, initial_backoff: Duration = 0ms) -> Result[T, E]` | The first attempt runs immediately. Every `Result.Err` is retryable while an attempt remains. `max_attempts` must be at least one and counts the immediate attempt. `initial_backoff` must be non-negative and representable by the host timer. Both arguments are validated before the worker is invoked. Before the second attempt the current task waits for `initial_backoff`; each later retry uses twice the preceding delay. A zero delay skips sleeping. Once the final permitted attempt returns `Err`, that exact error is returned without another sleep or delay multiplication. The worker may be a capture-free function value or a repeatable value-capturing closure. A consuming closure is rejected because retry may invoke the worker more than once. Traps from the worker, delay overflow, and invalid runtime operations are not converted to `E`. Current-task cancellation propagates through the retry operation instead of returning the most recent `Err`. ```aura import control import metrics def eventually_succeeds() -> Result[int32, str]: metrics.increment("attempts", 1) if metrics.get("attempts") < 3: return Result.Err("not ready") return Result.Ok(42) def main(): metrics.reset() match control.retry( eventually_succeeds, max_attempts=3, initial_backoff=0ms ): case Result.Ok(value): print(value) case Result.Err(error): print(error) print(metrics.get("attempts")) ``` See `examples/agents/retry_with_backoff.au` for both eventual success and exact last-error behavior. ## Grammar These modules add no source-language grammar. They are imported and called with the ordinary import, call, member-access, named-argument, collection, `Result`, and `Option` forms defined elsewhere in this reference. Module and member names are case-sensitive. The `--` separator that supplies `sys.args()` belongs to the CLI protocol, not Aura syntax. ## Typing Rules The function signatures in the tables above are normative. `sys.args()` produces owned `str` values in a `list`; environment and path components that may be absent use `Option`; fallible current-directory access uses `Result[..., io.Error]`. Dynamic JSON parsing returns `Result[json.Value, json.Error]`. Bounded JSON and TOML dictionary operations retain their `Result[..., str]` contracts. Telemetry fields are `dict[str, str]`. Metric names are `str`, increments and results are signed `int64`, and reset returns `None`. Passing any other type, using an unknown member, or binding an unsupported argument shape is rejected statically. `control.retry` infers `T` and `E` from the exact shared callback type `def() -> Result[T, E]`. A callback with parameters, a non-`Result` return type, or a different function-value contract is rejected with `AU2002`. `max_attempts` is exactly `int32`; `initial_backoff` is exactly `Duration`. ## Runtime Semantics `sys.args()` returns program arguments without the executable name. `aura run` uses arguments after the CLI `--`; a built program uses its host argument list. Environment lookup returns `None` for a missing or non-Unicode value. Path operations use host path rules, and their string results use the lossy Unicode policy stated above. Dynamic JSON object output and JSON/TOML top-level dictionary output are sorted by key. Dynamic JSON parse and dump follow the recursive value, strict-number, and formatting rules in the JSON chapter. Validation accepts the broader source format, but each `parse_string_map` operation accepts only its documented flat-string dictionary subset. Logging and trace calls synchronously emit one compact JSON record to standard error. Metric operations address one process-global, task-shared dictionary; a missing counter is zero, reset clears the dictionary, and checked overflow leaves the attempted increment unapplied. `control.retry` invokes its worker sequentially. It never overlaps attempts. An immediate first attempt is followed only as needed by the current delay and the next attempt. Every `Err` has the same retry policy. On exhaustion the helper returns the final worker's exact `Err`; it performs no terminal sleep and does not compute an unused next delay. A zero current delay skips the scheduler sleep. A worker trap, backoff overflow, or current-task cancellation propagates through the helper. ## Ownership And Evaluation Order Call arguments are evaluated left to right. Inputs to these host helpers are shared for the duration of the call and are not retained as Aura values after it returns. Returned lists, dictionaries, strings, options, and results are fresh owned values. The metrics implementation copies the metric name into process-global host state; it does not keep an Aura borrow alive. Telemetry emission and metric updates are observable side effects and occur at the call's position in source evaluation order. Concurrent tasks share standard error and the metric dictionary. Each individual metric operation is synchronized, but a sequence such as `get` followed by `increment` is not one atomic transaction. The retry helper reads a capture-free function value or repeatable capturing closure and invokes it under ordinary call rules. The helper can therefore reuse one repeatable capturing closure across all attempts without consuming its environment. Each `Result.Ok` or `Result.Err` owns its payload. Intermediate errors are consumed by the retry decision; the final error is returned without cloning. Attempt calls and delay waits occur in the stated sequence. ## Diagnostics Unknown modules or members use `AU2001`; type and callback-contract mismatches use `AU2002`; invalid argument binding uses `AU2004`; remaining static rejections use `AU2999`. Incrementing a metric beyond either `int64` bound, or doubling a retry delay beyond the exact `Duration` range, produces `AU4002` and does not wrap. A retry attempt budget below one uses `AU4003`; a negative or host-unrepresentable initial backoff uses `AU4001`. Invalid JSON or TOML data is ordinary program data: validation returns `false`, dynamic JSON parsing returns `Result.Err(json.Error)`, and bounded flat-dictionary operations return `Result.Err(str)` as documented. JSON dumping has the runtime traps and limits specified by the JSON chapter. A missing environment variable returns `Option.None`, and current-directory failure returns `Result.Err(io.Error)`; none of those typed outcomes is a language diagnostic. ## Backend Support All APIs on this page are implemented by both the MIR runtime used by `aura run` and the direct native backend. Argument injection differs only at the host boundary described above. Recursive JSON parse/dump behavior, JSON/TOML ordering, time units, telemetry record shape, and checked metric arithmetic are backend-parity contracts. Retry attempt order, backoff, exact final-error return, trap propagation, and cancellation are also MIR/direct parity contracts. The HTTP client summarized here has the same MIR/direct support as the full [Network Module](/manual/network). Host-dependent path and environment results may differ with the host while preserving their Aura types and error policy. ## Limits And Implementation-Defined Behavior Host argument and path bytes that are not Unicode are handled with the lossy or absent-value policies stated above. Path separators, roots, case sensitivity, and absolute-path rules follow the host. Unix time reflects the host clock and may move; monotonic time is process-local, millisecond-granularity elapsed time whose zero is the first call in that process. Dynamic JSON has the fixed tree, numeric, depth, node-materialization, and byte boundaries documented in [JSON Module](/manual/json); TOML and the bounded flat-dictionary helpers retain their limits. There are no derived codecs or streaming encoders. Telemetry has no exporter, batching, delivery guarantee beyond the standard-error write, scoped spans, or metric labels. Concurrent standard-error records are individually emitted but ordering between tasks follows scheduling. HTTP limits are the 16 MiB incoming wire-message cap, 64-header cap, framing checks, and repeated-header loss described above and in [Current Limits](/manual/current-limits). `control.retry` is an eager sequential helper, not a policy engine: it has no error classifier, jitter, attempt callback, retry budget shared between calls, or detached execution. Every `Err` is retryable. Applications that need status-specific retry policy or jitter must express it explicitly. Its `Duration` delays remain bounded by the host timer range described in [Current Limits](/manual/current-limits). ## Status The system, path, JSON, TOML, logging, trace-event, metrics, retry, and summarized HTTP contracts on this page are implemented and maintained in Aura 0.3. Recursive JSON gap-fill semantics are accepted under ADR-0021. The summarized fixed HTTP cap is Accepted under ADR-0018. Nested TOML data models, derived codecs, telemetry exporters, metric labels, scoped tracing spans, and richer HTTP header representations are unavailable. Mentions of those facilities are future, non-normative direction rather than accepted language behavior. ## Source: docs/manual/current-limits.md # Current Limits This page documents known current limits of the Aura compiler and runtime. ## Language - Identifiers are ASCII; Unicode is supported in string contents, not identifier spelling. - A physical tab outside a triple-quoted string is rejected. Inside a triple-quoted string it is exact content. Use `\t` in an ordinary string. - Source lists do not accept trailing commas except the required comma in singleton tuple values, types, targets, and patterns. Multi-element tuples still reject a trailing comma. - Parser nesting/postfix/binary-chain guards are limited to 128 operations; deeper input is rejected with a diagnostic. - Integers are fixed-width. Aura has no arbitrary-precision integer, implicit width promotion, rotate operator, literal suffix, hexadecimal floating literal, or distinct unsigned-right-shift operator. Power is builtin-only; `round` has no digit-count overload. - Non-numeric casts are not implemented. - Direct recursive fields require `indirect`. - Return values are always owned. Copy results are ordinary copies; a non-copy result must be constructed, cloned when clone-safe, moved from owned input, or produced by an owner operation. - Empty list, dictionary, and set literals need an expected collection type. - Class field defaults cannot call user-defined functions in the current compiler. Compute the value before construction and pass it as an explicit field argument. - `str(...)` is not a constructor; use string literals and string methods. - Ordinary and triple-quoted strings may use single or double quotes. Raw strings are single-line. Raw triple strings, raw f-strings, and byte-string literals are not implemented. F-strings remain double-quoted and use static format specifications; dynamic width, nested fields, conversion flags, locale formatting, and the `g`, `G`, `n`, `c`, `#`, `0`, and `=` forms are not implemented. - `str` has scalar-count `len()`, UTF-8 `byte_len()`, and owned Unicode-scalar slicing, but no integer indexing, `chars()`, `ord()`, or `chr()`. - One concatenated or formatted `str` result is limited to 64 MiB. Aura preflights the next append and reports `AU4005` without committing an oversized partial result. - `list[uint8]` is the bytes type. UTF-8 conversion is explicit; the reserved `encoding` argument, non-UTF-8 text codecs, byte-string literals, URL-safe or unpadded base64, streaming codecs, incremental hashes, and HMAC are not implemented. - Physical newlines continue a logical line only while `(`, `[`, or `{` remains open. Continuation indentation is visual; delimiter kinds must match. - Backslash continuation is not implemented. Ordinary, raw, and f-strings remain single-line; triple-quoted ordinary strings may span physical lines. - Tuples have fixed structural types, recursive unpack targets and patterns, copy-only constant indexing, and non-consuming recursive `==` and `!=` for operands of the same static tuple type. There is no empty tuple, multi-element trailing tuple comma, tuple iteration or methods, tuple ordering, named/rest unpacking, mutable tuple-target writeback, dynamic/negative tuple indexing, or tuple-to-collection conversion. Unpack a tuple to take ownership of a non-copy element. - Statement match arms cannot be inline. Expression match arms may use a same-line expression after `case pattern:` or an indented expression body. - `for` loop bindings cannot shadow names already visible in the same scope. - Duration literals have only the integral `ms`, `s`, and `m` suffixes; there is no `ns` or fractional Duration literal and no unary `-Duration`. Associated constructors and checked Duration arithmetic provide signed and sub-millisecond results instead. - Capture-free named functions are copy, `Transfer` values. They may be stored and called through `def(T1, mut T2, own T3) -> R` types and used as task targets; bare function-type parameters are shared. Instance, associated, and trait method values remain unavailable; the task API retains its direct associated-method-without-`self` target carve-out. - Lambdas with parameters require complete expected parameter types; a zero-parameter lambda may infer `def() -> R` from its expression body. Captures are by value and closure environments are read-only in Phase 6.3. Shared/mutable capability capture, inline parameter types, defaults, generics, statement bodies, and capture lists remain unavailable. A consuming closure is single-use. Capturing closures cannot pass through arbitrary written-`def` parameters, fields, collections, or annotated returns because those boundaries describe capture-free code pointers. Compiler-known repeatable callback sites preserve closure metadata; task start accepts a qualifying closure by move for one call. Conditional and `match` expressions cannot merge capturing closures from multiple branches because Phase 6.3 has no closure-union type; invoke the closure within each branch or use capture-free lambdas or named functions. - List, set, and dictionary comprehensions are eager and always return fresh owned collections. Their clauses use bare-loop iteration only; there is no comprehension `mut`/`own` source form, early `break`/`continue`, or lazy result. Nested clauses are outer-major and Queue comprehensions receive until ordinary Queue iteration ends. Generator expressions remain unavailable and report `AU2005` with an eager-comprehension or an explicit loop. - list and str slices accept one contiguous half-open range and return fresh owned copies. Written endpoints use `int64`; negatives normalize once, and invalid or reversed ranges trap with `AU4003`. Endpoints are not clamped. str endpoints count Unicode scalar values and slicing is O(n). Slice steps and slice assignment remain reserved `AU2005` forms; arbitrary sliceable types, zero-copy views, str integer indexing, grapheme slicing, and Python-style endpoint clamping are unavailable. List slicing requires clone-safe, repeatably observable elements. - Numeric `Array[T]` is CPU-only, contiguous, row-major, and specialized only by `int32`, `int64`, `float32`, or `float64`. Shape is runtime metadata and rank is at least one; zero dimensions are allowed. Same-dtype scalar broadcast is implemented, but there is no array-shape broadcasting, mixed promotion, equality, views, reshape, transpose, matrix multiplication, multidimensional or step slicing, slice assignment, autograd, accelerator placement, distributed storage, or foreign-buffer aliasing. First-axis slices are fresh owned copies. Maintained NumPy comparisons record exact post-reboot workloads and provenance. Shape elements, coordinates, and element counts use `int64`; practical Array size is bounded by address space, allocation limits, element size, and available memory. - FFI v0 is package-only and requires `[package] allow_ffi = true`; a root package also reports every reachable FFI-enabled dependency under exact `[ffi] dependencies`. Calls resolve already-loaded process-global symbols and are synchronous on the current worker. The accepted ABI is limited to fixed-width scalars, temporary str/byte pointer-length views, same-length mutable byte scratch copy-in/out, and non-null opaque handles. Empty views use `(NULL, 0)`. There is no library-loading/link-name syntax, callback, variadic, raw-pointer arithmetic, returned view, nullable handle, foreign aggregate layout, automatic handle destructor, or async offload. Process-global lookup is currently supported on Unix-family hosts. A false C signature or misbehaving native function remains outside Aura's safety guarantees and may terminate or corrupt the process. - Callable-powered list algorithms are eager. `map` and `filter` return owned lists; `filter` requires clone-safe elements. Built-in natural sorting covers all integer types, `float32`, `float64`, and `Duration`; `str` has no built-in `Ord[str]`. Preserve insertion order, use `sort(key=callback)` with an orderable key/index, or define a nominal type with an application-specific `Ord` implementation when text records require ordering. Keyed `sort`, `map`, and `filter` accept only their exact bare/shared callback parameter capabilities. There is no comparator-form sort, lazy map/filter, parallel traversal, or algorithm callback with mutable/owned element access. - `TaskGroup.start(...)` and `start_soon(...)` support bare shared and `own` target parameters; `mut` targets are rejected because child tasks cannot write back through the starting call frame. - Detached lightweight tasks are not a language form; use `TaskGroup`. - `for value in mut set:` is not currently supported. ## Runtime - MIR and native direct-backend traps carry the same typed Aura call frames and task ancestry. Call frames are innermost first, task ancestry is youngest first, and every frame retains its own defining or spawning source path. - Human diagnostics synthesize the compact call-chain, task-entry, and task-ancestry note lines. Structured schema-version-1 diagnostics expose `call_frames` and `task_ancestry` arrays instead; generated frame prose is not duplicated in `notes`. - Aura does not expose host Rust/Cranelift backtraces, debugger stack reflection, exception catching, or a standalone-binary JSON switch. - Aura task code executes on pinned cooperative scheduler workers. The default count is the available parallelism reported by the host; the `AURA_WORKERS=` override selects an explicit count. Assignment happens when a child is spawned and remains stable for its lifetime: coroutine stacks never migrate and the runtime does not steal work between workers. - A positive `AURA_WORKERS` value may exceed the host's available-core count. Empty, zero, signed, whitespace-padded, nonnumeric, and overflowing values are rejected before execution with `AU4006` and ``invalid AURA_WORKERS value ``: expected a positive integer``. - Scheduling is cooperative, not preemptive. The compiler checks every loop backedge and eventually yields from a tight loop, but only to runnable work assigned to that task's worker. One long loop body or long straight-line computation can still delay siblings pinned to the same worker. The automatic checks do not inspect cancellation. - Queue and Task handles are the maintained cross-worker communication surface. All other task captures and results must be owned `Transfer` values, preserving a share-nothing boundary. A task's cancellation and diagnostic context remain isolated from work executing on other workers. - Task scheduling, cross-worker completion, and program-output order are unspecified. There is no worker-index or affinity-introspection API. - MIR is the checked development path, not the performance path. In the Batch-4 multicore control, four MIR tasks took about `2.1x` the wall time of one task: interpreter work and synchronization increase the per-task cost when several workers execute MIR concurrently. Use the direct native backend for performance measurements. - Pinned task execution is maintained on the MIR and direct native backends. Work stealing, preemption, and detached tasks are unavailable. Parallel speedup depends on the workload, and automatic parallelism applies only to task execution. - Ordinary lightweight tasks request 512 KiB of writable coroutine stack. `TaskGroup.start_with_stack` and `start_soon_with_stack` accept exact `int64` requests from 256 KiB through 64 MiB inclusive. Accepted requests are rounded upward to the host page size and guard-protected; smaller and larger requests are rejected rather than clamped. The MIR/direct runtime entry thread reserves 64 MiB, and maintained execution paths stop with a friendly recursion-depth diagnostic after 256 nested Aura calls. The override API is Provisional under ADR-0032. The 256 KiB lower bound is an opt-in minimum for measured shallow tasks, not the generally safe default; the complete compiled Aura HTTP example faulted when 256 KiB was the global default and succeeds at 512 KiB. An isolated runtime protocol round trip succeeds with 256 KiB callers because it excludes compiled language-execution frames; it proves the service offload boundary, not a 256 KiB whole-program default. - On the clean Mac14,9 Phase 5.10 measurement at `181204b`, 10,000 parked sleepers used 207,798,272 bytes of worst whole-process RSS and 198,787,072 bytes above the same-process pre-spawn baseline, passing the maintained 512 MiB gate. - The runtime accepts larger task counts; 10,000 sleepers is the maintained memory-capacity bound. The final Phase 5.10 100,000-sleeper plus 1,000-timer repetitions peaked at 1,170,735,104, 1,921,531,904, and 2,001,305,600 bytes. Two of three exceed the 1.5 GiB gate. On this 16 KiB-page host, one resident page for each of the 101,000 stackful child coroutines alone requires 1,654,784,000 bytes before task metadata or the root runtime. The Phase 5.9 passing observation depended on compression and reclaim behavior. - The contractual 10,000-sleeper bound plus the timer, idle, starvation, and multicore controls all pass at Phase 5.10: the standalone timers had a 6 ms worst arm span and 1 ms p99 overshoot, idle CPU was below 2%, starvation latency was 14 ms, and the four-worker control had a `1.039673x` paired median wall-time ratio with `396.73%` median four-task process CPU on the measured Mac14,9 host. - The scheduler uses persistent reactor registrations for nonblocking descriptors, a timer heap for deadlines, and direct Queue, task-completion, and blocking-pool notifications. When idle it blocks until an event or deadline and has no periodic scheduler tick. - Deep HTTP, TLS, and maintained Unix WebSocket library frames run on a distinct protocol-step pool with two 2 MiB-stack workers and a 64-job queue. Each submitted job is a bounded, nonblocking step and returns owned protocol state before cancellation or reactor waiting resumes. The non-Unix WebSocket fallback does not use this Phase 5.4 service. The pool is process-global, lazily initialized, shared by all lightweight schedulers, and intentionally process-lifetime; it has no 0.3 runtime shutdown or join API. File reads, resolver work, and listener binding remain on the generic blocking-I/O pool; TLS asset bytes are read there before PEM parsing and rustls construction run on protocol workers. - Filesystem one-shot reads and `fs.File` whole-file reads are capped at 256 MiB of remaining content. Aura 0.3 has no chunked file-read API. - Process-pipe and captured-output reads plus TCP, Unix, and TLS whole/bounded reads remain capped at 64 MiB. TLS certificate, private-key, and CA-file loading uses the same independent 64 MiB ceiling. A bounded byte count of zero is invalid. - UDP receives accept `max_bytes` from 1 through 65,535. - Incoming HTTP parsing accepts at most 64 headers and 16 MiB of wire data per message, including the start line, headers, transfer framing, trailers, and body. Outbound HTTP writers have no separate size cap. The high-level dictionary header model cannot preserve repeated equal field names losslessly. - WebSocket messages are capped at 64 MiB; individual frames and write buffers are capped at 16 MiB. - TLS handshakes have a 10-second hard cap even when the caller supplies no shorter timeout. - Duration is a signed i128 nanosecond language value, but host timer ranges are narrower. Negative values, out-of-range host conversions, and overflowing deadline calculations are invalid input rather than unlimited waits. The exact error classification is accepted under ADR-0019. - High-level HTTP clients support HTTP/1.1 over `http://` and validated `https://`, including content-length, chunked, and close-delimited responses; redirects, pooling, HTTP/2, proxy configuration, decompression, and high-level custom CA arguments are not implemented. - Byte-codec inputs have no separate byte-count cap, but byte conversions and hex/padded-base64 codecs preflight each fresh destination against a fixed 2,147,483,647-byte safety ceiling. Crossing this codec output/resource cap or failing allocation traps with `AU4005`. This ceiling is independent of the public str and `list` length domains. SHA-256 always returns 32 raw bytes. - JSON supports the recursive `json.Value` tree, typed `json.Error` parse failures, deterministic dumps, a 128-container depth limit, a shared root-inclusive 262,144-value materialization limit, and independent 64 MiB parse-input and dump-output caps. Exceeding the node limit or encountering a controlled parse/conversion allocation failure traps with `AU4005`; it is not a `json.Error` variant. Dynamic `json.parse` uses a separate process-global service with two 2 MiB-stack workers and total in-flight capacity two; capacity is reserved before the fallible source copy, and saturated lightweight tasks park through the scheduler. Once admitted, synchronous parse defers cancellation until codec completion. Runtime materialization, JSON-aware clone/render, and dumping use iterative traversals. The service is process-lifetime and has no 0.3 sizing or shutdown API. The bounded `json.is_valid` and `json.parse_string_map` helpers retain their bounded caller-side paths and do not use that service; JSON flat-dictionary and TOML helpers remain restricted to typed `dict[str, str]`. JSON has no arbitrary-precision number, streaming codec, or derived class/enum schemas. - `random.Rng` provides one fixed deterministic stream with integer, floating, and mutable-list shuffle operations. There is no global generator, state serialization, reseeding, jump/substream operation, distribution library, choice helper, public direct or transitive clone route, secure floating function, or `random.Error`. Clone-producing collection operations are rejected with `AU3007` when their produced value contains or may contain an `Rng`. An owned generator may move within one owning task, but it is not `Transfer`: it cannot be a task result or Queue payload. Queue handle copies remain valid; a Task handle is copyable only for a repeatable result. Generic clone-safety requirements are inferred from callable bodies, propagated through generic calls and imports, and checked after specialization; there is no source annotation for them. Trait defaults may establish this contract, but an explicit implementation may not strengthen it. Recursive nominal inspection terminates conservatively when safety cannot be proved. `secure_bytes` accepts at most 2,147,483,647 bytes as a fixed per-request resource and safety ceiling, independently of the public `list` length domain. Larger counts fail with `AU4005` before allocation or entropy. Within that request ceiling, unsatisfied allocation or OS entropy requests also trap with `AU4005`. - Metrics are process-global counters within one running program; log and trace APIs emit structured stderr records and do not yet include exporters or scoped spans. - `control.retry` is a sequential eager helper for a repeatable `def() -> Result[T, E]` worker. The worker may be a capture-free function value or a repeatable capturing closure. Every `Err` is retryable. It has no error classifier, jitter, attempt hook, shared retry budget, or detached/parallel mode. Attempt budgets below one and negative or host-unrepresentable backoffs trap before the worker runs. Backoff overflow traps, worker traps propagate, and task cancellation is not converted to the worker's `E`. - Floating-point `/`, `//`, or `%` by zero traps at runtime instead of producing IEEE 754 infinity or NaN. - `float32` literals that overflow may currently become infinity; prefer `float64` when large literal validation matters. - Unix domain sockets require a Unix host. - TLS APIs require PEM certificate/key assets. - Package support has local path and git dependencies, but no registry publish/install flow. - `fs.read_dir` silently skips an individual directory entry that fails after the directory itself was opened. - High-level HTTP header conversion may expose duplicate equal dictionary keys when the wire message repeats a header name; repeated headers are not a lossless 0.3 contract. - Accepted ADR-0033 rejects non-Transfer task captures, task results, and Queue payloads with `AU3008`. Every other non-repeatable transferable task result has one statically enforced observation right: direct result methods consume it on every outcome, and multi-task waits consume the complete task list. A second runtime claim that reaches the atomic containment check traps with `AU4001` rather than returning or cloning the stored value. - Cancelling filesystem and other blocking-worker I/O cancels Aura's wait, not an accepted operating-system call. Before insertion into the pending queue, timeout or cancellation prevents submission; after insertion, the host operation runs exactly once and external side effects may still complete while its late result is discarded. - The process-wide blocking-I/O pool defaults from host parallelism with fallback `4` and a derived `2..=8` clamp. `AURA_BLOCKING_WORKERS=` instead requests that exact count without clamping. `AURA_BLOCKING_QUEUE_CAPACITY=` bounds pending accepted jobs only; running jobs and admission waiters do not consume it, and omission preserves an unbounded queue. Full-queue admission is FIFO and scheduler-aware. The first runtime preflight reads the settings once and keeps them immutable for the process lifetime without starting workers. First submission creates the complete worker set; production reuses it until process exit and has no Aura shutdown/join surface. This bounds accepted pending backlog, but not admission waiters, and cannot interrupt a stuck accepted call or guarantee unrelated blocking-I/O progress while every worker remains occupied. - `WebSocketListener` has no explicit `close()` method, and WebSocket cancellation/error propagation is not yet fully aligned with TCP and UDP. ## Tooling - `build` requires a host C compiler. Source-checkout builds may use Cargo to refresh the native runtime; release archives carry that runtime and do not require Rust or the source checkout. - Native `run` cache entries larger than 512 MiB are not retained. The just-built program still runs, but a later invocation rebuilds it instead of using the cache. - The direct backend is the maintained native backend for the implemented language surface. - The default `--backend auto` first tries direct emission and may package an embedded-MIR launcher when direct emission is unavailable. Use `--backend direct` when fallback is unacceptable. - Editor tooling uses a persistent compiler service. If that process is unavailable, recovery is lexical only and intentionally has no semantic diagnostics or member inference. - `aura fmt` currently normalizes line endings, trailing whitespace, and final newlines; it is not yet a syntax-reflowing formatter. - `aura test` discovers each parameterless `def test_*()` function as a separate result and retains file-level execution for files with no such function. Discovery is name-prefix based; annotations, parameterized tests, and fixture/teardown protocols are not implemented. - A timed-out `aura test` stops waiting but cannot terminate its worker thread; the timed-out program may continue host side effects until the process exits. - Recursive `aura fmt` and `aura test` traversal follows directory symlinks without cycle detection in 0.3. ## Source: docs/manual/diagnostics.md # Diagnostics Aura diagnostics are part of the language and tooling contract. Lexing, parsing, static checking, ownership checking, lowering, building, and runtime traps all use the compiler-owned diagnostic structure described here. A typed library failure such as `Result.Err`, `Option.None`, a timeout, cancellation, or an `io.Error` value is ordinary program data, not a diagnostic. ## Stable Diagnostic Codes Every diagnostic has a stable code of the form `AU####`. The first digits name the phase that owns the failure: | Band | Phase | Current codes | | --- | --- | --- | | `AU10xx` | lexical analysis | `AU1001` invalid lexical input; `AU1002` invalid f-string delimiter | | `AU11xx` | parsing | `AU1101` invalid syntax | | `AU20xx` | names and types | `AU2001` name resolution; `AU2002` type mismatch; `AU2003` unsupported operator; `AU2004` argument binding; `AU2005` unsupported syntax or feature; `AU2006` builtin method collision; `AU2007` builtin function redefinition; `AU2008` equality unavailable; `AU2999` general compile-time rejection | | `AU30xx` | ownership, borrows, and transfer | `AU3001` moved value; `AU3002` borrow violation; `AU3003` mutability violation; `AU3004` ownership mode; `AU3005` non-copy indexed read; `AU3006` non-copy indexed compound assignment; `AU3007` non-cloneable state duplication; `AU3008` non-transferable task/Queue boundary; `AU3009` single-consumer task-result duplication | | `AU40xx` | runtime-checked traps | `AU4001` general runtime trap; `AU4002` arithmetic overflow or underflow; `AU4003` bounds or lookup violation; `AU4004` zero divisor; `AU4005` resource, allocation, or I/O failure; `AU4006` invalid runtime configuration; `AU4007` numeric Array shape or reduction violation | `AU1001` also owns source-delimiter pairing. An unexpected closer is primary at that closer. A mismatched closer names the expected kind and labels its opener as related information. EOF with an unclosed delimiter reports the expected closer and labels the opener. These locations and labels are preserved by analysis JSON and the LSP bridge. The registry is append-only. Once published, a code MUST NOT be reused, renumbered, or silently reassigned to a different diagnostic category. If a diagnostic becomes obsolete, its number remains reserved. New categories receive new numbers. Message wording and attached guidance may become more specific without changing a code when the failure category is unchanged. `AU2999` is the maintained catch-all for compile-time rejections that do not yet have a narrower public category. It is a stable code, not permission for a tool to omit the code. `AU2006` identifies an explicit or inherited trait method whose name would shadow a builtin member of the implementation's builtin target. The rule covers every builtin target, from the runtime handles `Queue[T]`, `Task[T]`, `TaskGroup`, `random.Rng`, `fs.File`, and the `net` and `process` handles, to the builtin value types such as `str`, `list[T]`, `dict[K, V]`, `set[T]`, `Duration`, and the scalar types. Its guidance requires the trait method to be renamed; backend dispatch is never selected by which implementation happens to run first. `AU2007` rejects a module-level function declaration whose name is already a builtin function name, such as `len`, `str`, `abs`, `print`, or `select`. The builtin surface is closed, so the declaration must be renamed. This rejection is distinct from the `AU2006` method collision: it covers free functions rather than trait methods on a builtin target. `AU2008` reports an unmet equality obligation. It covers direct `==` and `!=` and every collection operation that depends on equality: membership, `list.remove`, `list.index`, `list.count`, set element insertion, and dictionary-key use. Named function values, closures, `random.Rng`, opaque FFI handles, and values containing any of those types do not define equality. The diagnostic names the unavailable relation before execution can reach a backend identity comparison. `AU4007` is the numeric Array structural runtime diagnostic. It reports rank-zero or negative-dimension construction, `from_list` count mismatch, exact-shape operator mismatch, direct coordinate-count/runtime-rank mismatch, and empty `min`, `max`, or `mean`. Shape-product/element-count overflow and allocation failure remain `AU4005`. Out-of-range coordinates and invalid first-axis slice bounds remain `AU4003`. Optional `get` absence is ordinary `None`; method `set` traps on an invalid coordinate or rank. `AU2002` reports an exact callback-contract mismatch for callable-powered builtins. List `map`, `filter`, and keyed `sort` require the documented shared `def(T) -> ...` parameter capability; a `mut` or `own` callback is not silently adapted. The same code reports a `control.retry` worker that is not exactly a zero-parameter `def() -> Result[T, E]`. `AU2002` also reports a `sort` element or keyed `sort` key type without the required natural ordering. At the FFI boundary, `AU1101` provides dedicated parser guidance for malformed extern bodies, defaults, type parameters, callbacks, variadics, and raw-pointer syntax. `AU2002` rejects types outside the FFI v0 scalar/view/opaque table; `AU2005` rejects reserved raw-pointer/callback contracts or opaque construction; `AU2999` covers package authorization, root dependency reports, and direct-call-only policy; and `AU3004` reports an invalid FFI capability. Opaque handle moves and task/Queue boundaries retain `AU3001` and `AU3008`. A non-canonical C boolean result (a returned byte other than `0` or `1`) traps with `AU4001`. `AU4005` reports a missing process-global symbol, null opaque-handle result, or runtime marshalling failure. Native aborts, signals, memory faults, and foreign unwinds may terminate the process and are not Aura diagnostics. See [FFI v0](/manual/ffi). `AU3005` rejects a direct `list` or `dict` indexed read that selects a non-copy element or value, and constant tuple indexing that selects a non-copy element. For collections its guidance is clone-safety aware, classified exactly as the rejection is: a clone-safe type is directed to the explicit cloned `get` surface; a type carrying non-cloneable `random.Rng` state is directed to `remove` alone, because `get` on it would be rejected in turn by `AU3007`; and an unresolved generic type is told that `get` requires a clone-safe type, with `remove` offered unconditionally. For tuples, unpack the whole tuple to move its non-copy elements. `AU3006` rejects the corresponding `list` or `dict` indexed compound assignment because read-modify-write would otherwise require a hidden clone or destructive move of the stored value. `AU3007` rejects an operation that would duplicate non-cloneable state. Protected values include `random.Rng`, opaque FFI handles, and capturing closure environments, and the check follows them through collections, user classes, enum payloads, and other value wrappers. A generic definition over unresolved types records an inferred clone-safety obligation; `AU3007` is emitted at an unsafe concrete specialization, when a concrete requirement cannot be proved, or when an implementation would strengthen its trait method's contract. Because `list.filter` clones accepted source elements into a fresh result, it establishes the same obligation and rejects `list[random.Rng]` or a transitive wrapper. Under Accepted ADR-0033, `random.Rng` is not Transfer: a task returning it and a Queue carrying it are rejected with `AU3008`, and the task handle is not copyable. Moving or removing a generator within one owning task remains valid because it leaves one owner. Accepted ADR-0033 reserves `AU3008` for a captured argument, task result, or Queue payload that cannot cross a task-worker boundary. The diagnostic names the failed boundary and follows the specialized type to the first non-transferable leaf, including its field, tuple element, collection component, or enum payload path. For example, it explains that a `Job` cannot cross because `Job.source` contains `fs.File`; it does not stop at “`Job` is not Transfer.” `AU3008` guidance recommends passing owned transferable input/output data instead of a non-copy shared or mutable capability, and keeping live host authority or `random.Rng` on its owning task. It may explain that reading Copy data materializes an owned snapshot; it must not claim all borrowed Copy captures fail. It never proposes an `impl Transfer` because Transfer is compiler-derived and has no builtin source-level trait surface. An ordinary user trait also named `Transfer` and its implementations do not alter the structural property. `AU3009` rejects an operation that would duplicate an existing single-consumer task-result observation right. It covers explicit clone, clone-producing `get`, and implicit collection or aggregate copy. It is not a Transfer-boundary failure: the contained task handle is Transfer, but is non-copy because its result is non-repeatable. A later use of the same binding after a consuming result observation is `AU3001`; trying to consume the right through shared access is `AU3002`. For `select(...)`, `AU3009` also rejects the same statically visible non-repeatable Task source appearing twice in one call. `AU3002` explains that each non-repeatable Task must arrive through owned access because `select` consumes all such observation rights at entry and abandons losers. Call-shape errors such as an empty call or named source are `AU2004`; an invalid source kind or inconsistent Queue/Task category type is `AU2002`. The atomic runtime containment for non-repeatable results is separate from those static errors. If a backend defect or foreign handle reaches a second runtime claim, Aura traps with `AU4001` and `task result has already been observed; non-repeatable task results allow exactly one observing attempt`; it never returns or clones the stored value. The same defense applies when malformed backend state reaches `select`; an already-claimed or duplicated non-repeatable Task traps with `AU4001` before any result is delivered. ## Diagnostic Structure A diagnostic contains all of the following fields: | Field | Meaning | | --- | --- | | `code` | stable `AU####` identifier | | `severity` | `error`, `warning`, `information`, or `hint` | | `message` | concise primary explanation | | `primary_span` | optional path and source range for the failed operation | | `secondary_spans` | related source ranges, each with a label | | `notes` | contextual facts that do not prescribe a change | | `help` | actionable human guidance | | `edits` | source replacements with an applicability classification | | `call_frames` | Aura call frames, ordered innermost first | | `task_ancestry` | structured task parentage, ordered youngest first | The current compiler emits errors; the additional severity values are reserved by the shared schema. A machine-applicable edit is safe for a tool to offer as an automatic source replacement at the stated range. Tools MUST preserve edits and MUST NOT infer an edit from prose alone. Compiler and CLI spans use one-based line and column numbers. Each structured span is a half-open range with `start` and `end`; current token diagnostics may use a one-column primary range. The LSP bridge converts those ranges to the zero-based line and character coordinates required by the Language Server Protocol. ## Human-Readable Form The default CLI form begins with the stable code: ```text error[AU2001]: unknown name `missing` --> path/to/file.au:2:11 | 2 | print(missing) | ^ ``` Related spans follow as `related` records. Context appears as `note`, proposed actions as `help`, and source replacements as `fix`. A source-backed operation uses the path and source context where the diagnostic was detected, including an imported module rather than its importer. If no valid source line is available, the renderer still emits the code, message, and best available location. The compiler normally reports one primary failure for an operation instead of inventing speculative follow-on errors. A conforming implementation MUST reject invalid source rather than silently reinterpret it. ## JSON Form `aura check --format json` writes one JSON document. `aura run --format json` and `aura build --format json` use the same document for compile failures. The top-level `schema_version` is currently `1`, and `diagnostics` is an array. For `check`, `run`, and `build`, the current compiler emits at most one diagnostic per invocation: the pipeline stops at the first failure. On failure the schema-version-1 `diagnostics` array therefore contains exactly one entry; on successful `check` it is empty. The array is retained for schema compatibility and future recovery, and tools must not infer that the source contains no additional errors. ```json { "schema_version": 1, "diagnostics": [ { "code": "AU2001", "severity": "error", "message": "unknown name `missing`", "primary_span": { "path": "path/to/file.au", "start": { "line": 2, "column": 11 }, "end": { "line": 2, "column": 12 } }, "secondary_spans": [], "notes": [], "help": [], "edits": [], "call_frames": [], "task_ancestry": [] } ] } ``` `primary_span` is `null` when no source location exists. A secondary span has the same `path`, `start`, and `end` fields plus a string `label`. Each edit has `path`, `start`, `end`, `replacement`, and `applicability`. Successful `aura check --format json` emits schema version 1 with an empty diagnostics array. Successful `run` and `build` retain their ordinary program-output and artifact contracts; `--format` selects their diagnostic representation, not the program's data format. A direct run that performs long native work may also write one schema-version-1 status document on standard error. Its `progress` array contains the exact wait/rebuild notices. If `auto` falls back successfully, the same document contains `"fallback":{"from":"direct","to":"mir","reason":"..."}`. If the fallback then fails, its progress and direct failure are retained as notes in the one diagnostic document. Every diagnostic entry contains both frame arrays, including compile-time and pre-user-code failures where they are empty. A call-frame record contains `function` and a `span`. A task-ancestry record contains `task_function`, `task_entry_span`, `parent_function`, and `spawn_span`. Each frame span carries its own `path`, `start`, and `end`, so a frame defined or spawned in an imported module is never mislabeled with the entry module's path. The arrays are an additive schema-version-1 extension. Schema-version-1 readers MUST ignore unrecognized object members while continuing to validate the fields they use. The compiler-service semantic-interface version is `5`. The process exits unsuccessfully after emitting a JSON error report. Tools MUST parse standard error as one JSON document in JSON mode and MUST NOT scrape the human renderer. ## LSP Contract The compiler service owns editor diagnostics. Its analysis record carries the same code, severity, message, secondary spans, notes, help, edits, call frames, and task ancestry. Frame spans use zero-based `file_path`, `line`, `start_character`, and `end_character` coordinates in this editor shape. The JavaScript language-server bridge maps the primary span to the LSP range, maps secondary spans to `relatedInformation`, places the code in `Diagnostic.code`, and preserves the remaining metadata in `Diagnostic.data`. There is no independent semantic-diagnostic implementation in the language server. If the compiler service is unavailable, lexical recovery may keep basic editor navigation usable, but it MUST NOT invent semantic success or fabricate compiler diagnostics. ## Ownership Diagnostics Ownership diagnostics use the `AU30xx` band. When the checker has both sites, the primary span identifies the invalid later operation and a labeled secondary span identifies the earlier move or borrow that made it invalid. Applicable guidance names the smallest explicit repair: change a parameter to `own`, clone at a deliberate ownership boundary, use the appropriate borrow loop form, add `mut`, or declare a mutating receiver as `mut self`. When a repair is a local, unambiguous source replacement, the diagnostic also carries a machine-applicable edit. Guidance is not a relaxation of ownership rules. In particular, Aura never inserts a hidden clone or converts a borrow into ownership to recover from an error. For `AU3007`, guidance offers the two explicit single-owner exits: move or remove the existing value, or construct an independent generator from an explicit seed. It does not offer `.clone()` on any type whose value contains or may contain `random.Rng`. Clone-producing aliases—including collection reads and task-result observations—are subject to the same rule as a direct clone. When a binary left operand, index base, method receiver, or indexed-assignment target retains a non-copy borrow through later inputs, an overlapping later mutable borrow or consumption is `AU3002`. The conflicting later access is the primary span and the retained selection is a labeled borrow-origin secondary span. Guidance may suggest an explicit clone when the type supports it or a separate earlier mutation, but the compiler does not deep-clone implicitly. For example, consuming a bare shared parameter reports that parameter `x` is borrowed and recommends declaring it as `own str` to take ownership or cloning the value before consuming it. The parameter name and concrete type in that message come from the rejected declaration. ## Python-Shaped Source Guidance `AU2005` identifies focused guidance where Python-looking source has an Aura spelling. Maintained hints cover `True`/`False`, `.append(...)`, `is` and `is None`, and `try`/`except`. Eager list, set, and dictionary comprehensions are accepted. A generator expression, whether parenthesized or used as a call argument, receives this exact `AU2005`: generator expressions are unavailable; use an eager owned list comprehension or an explicit loop `mut` or `own` in a comprehension clause is malformed syntax and receives `AU1101` with the exact teaching message: comprehensions use bare iteration; remove `mut` or `own` and write `for name in values` The bare form preserves the iterable's ordinary contract, including owned receive items for Queue. Related diagnostics cover missing `mut`, consuming calls, integer `/`, typed `self: Type`, tab indentation, and single-quoted f-strings. String literal and format diagnostics use the smallest proving location. `AU1001` reports malformed or unterminated ordinary, triple-quoted, and raw strings, including a later physical line that contains an invalid escape. `AU1002` gives focused supported-form guidance for raw and triple-quoted f-string prefixes. `AU1101` reports malformed static format grammar, nested fields, unsupported codes, and width or precision above `1_000_000`. `AU2002` reports a valid specification that is incompatible with the interpolation's static type. Constructed string output above the 64 MiB limit reports `AU4005` before the oversized append mutates the partial result. Python permits decimal grouping without an explicit type code, as in `f"{n:,}"`. Aura requires the numeric code: use `f"{n:,d}"`, `f"{n:,f}"`, or `f"{n:,%}"`. This keeps grouping validation tied to a statically selected numeric rendering contract. Owned list and str slicing is implemented, but step syntax and slice assignment remain reserved. They use `AU2005` with these exact messages: slice steps are unavailable; use an explicit loop to select a stride slice assignment is unavailable because slices are owned copies; mutate the source by index or build a new value Written slice endpoints use the `int64` index domain; a mismatched bound uses `AU2002`. Fixed-width narrower integers widen losslessly at that position. A list slice that would duplicate `random.Rng`, an opaque FFI handle, or a capturing closure environment uses `AU3007`; one that would duplicate a non-repeatable Task result right uses `AU3009`. An endpoint outside `0..=len` after one negative normalization, or a start greater than its end, traps with `AU4003`. Unlike Python, Aura never clamps a slice endpoint. `in`, `not in`, chained comparisons, `len(...)`, `str(...)`, and contextually typed expression lambdas are accepted forms and their fixtures assert those spellings. Hints MUST name an available spelling when one exists. For an unavailable form, they MUST name a working expression or statement form. The complete hint family is pinned under `crates/aura-compiler/tests/fixtures/python-hints/`. `AU2005` also identifies `str(...)` constructor-shaped source and directs the caller to Aura string literals. ## Runtime Traps And Backtraces Runtime diagnostics use `AU40xx` and preserve the source span embedded during lowering. Output produced before a trap is not discarded: `aura run` leaves program standard output intact, renders the diagnostic on standard error, and exits unsuccessfully. A failed assertion is `AU4001` at the `assert` keyword location. The message is exactly `assertion failed` when omitted and otherwise exactly the evaluated str, including an empty or whitespace-only value. A failure while evaluating the condition or message remains primary. Active cleanup still runs, but a cleanup failure cannot replace an already established assertion diagnostic. The MIR and direct runtimes attach the same typed Aura frames to every trap. Call frames name the Aura function and its defining source span, ordered innermost first. If the trap occurs in a task, task-ancestry records also identify that task's entry, its parent function, and the exact source location from which each task was started, ordered youngest first. These are Aura frames, not host Rust, Cranelift, scheduler, or service-worker frames. Frame records are captured once when the primary trap is established, before cleanup or task-state reset. Propagation through callers, Task results, task groups, or workers does not append observer frames. A child starts a new call chain; its relationship to the parent is represented by task ancestry. Human rendering synthesizes the established `Aura call chain`, `Aura task entry`, and `Aura task ancestry` note lines from the typed records after ordinary notes. Those generated strings are not stored in structured `notes`, so JSON and LSP clients consume the frame arrays without parsing or deduplicating prose. JSON-mode direct runs transport a native trap to the `aura` parent through a private fixed-marker pipe and a separate bounded JSON-data pipe. Native initialization hides and marks both descriptors close-on-exec before user code. The parent emits one schema-version-1 document, including any buffered native-build progress in ordinary `notes`. An Aura trap is distinct from a successful `main() -> int32` returning a nonzero status; a signalled missing/malformed record is a host failure, and `auto` never falls back to MIR after launch. Human-mode direct runs and standalone direct binaries continue to render the complete diagnostic themselves. Checked overflow, zero division, bounds failure, recursion-depth failure, and an explicitly trapping invalid runtime state are diagnostics. File, process, network, timeout, cancellation, and protocol operations normally return typed values instead; the feature page for an API states any trapping exception. A negative, host-unrepresentable, or deadline-overflowing Duration returns the documented `InvalidInput`/process error when that API has a compatible typed carrier. A timer API without one traps with `AU4001`; deadline overflow never means an unlimited wait. This classification is accepted under ADR-0019. `control.retry` doubles a `Duration` backoff only when a later attempt can use the doubled value. If that required doubling exceeds the exact signed `Duration` range, it traps with `AU4002`; it does not wrap, clamp, return the most recent `Err`, or compute an unused post-final delay. Worker traps and current-task cancellation likewise propagate instead of being converted to the worker's `E`. `AU4003` rejects `max_attempts < 1`; `AU4001` rejects a negative or host-unrepresentable `initial_backoff`. These inputs are validated before the worker runs. The random module returns plain values rather than a `random.Error` enum. `AU4003` reports an empty or reversed `next_int`/`secure_int` interval and a negative `secure_bytes` count. `AU4005` reports a `secure_bytes` count above the fixed per-request ceiling of `2147483647` before allocation or entropy is requested, secure operating-system entropy failure, or allocation failure. A secure operation never recovers by substituting bytes from the deterministic generator. An explicit task-stack request has exact type `int64` and an inclusive 262,144..67,108,864-byte range. `AU2002` rejects an out-of-range literal during checking. A dynamic value outside that range and a stack-allocation or platform-size failure trap with `AU4005`; Aura never clamps the request or silently substitutes the default. `AU4006` reports invalid process runtime configuration. `AURA_WORKERS`, `AURA_BLOCKING_WORKERS`, and `AURA_BLOCKING_QUEUE_CAPACITY` each require a positive decimal integer. Empty, zero, signed, whitespace-padded, non-decimal, non-Unicode, and overflowing values are rejected before user code; the diagnostic names the setting and renders the supplied value, using a lossy display for a non-Unicode value. Failure to create the configured blocking-I/O worker set also uses `AU4006` and does not silently use fewer workers or synchronous execution. JSON input-data failures are typed `json.Error` values rather than diagnostics. Parse allocation failure or exceeding the shared 262,144-value materialization limit uses `AU4005` instead. `json.dumps` uses `AU4003` for an indent outside `0..=16` or a value deeper than 128 containers, `AU4001` for a NaN or infinite `json.Value.Float`, and `AU4005` when conversion exceeds the same node limit, encoded output would exceed 67,108,864 bytes, or a controlled conversion/output allocation fails. No failed dump returns a partial str. Malformed UTF-8, hexadecimal, and base64 input returns `bytes.Error`, including the relevant zero-based byte offset or odd input length, when that metadata fits the retained `int32` payload. A required offset or length above `2147483647` uses `AU4005` rather than truncating or wrapping the typed error. A fresh bytes conversion or codec destination above the fixed 2,147,483,647-byte safety ceiling, destination-size arithmetic overflow, or allocation failure also uses `AU4005`; the ceiling is independent of the public str and `list` length domains, and no failed operation returns a partial str or byte list. Unrecoverable host or dependency-internal out-of-memory termination remains outside the catchable diagnostic contract. ## CLI Exit Status | Status | Meaning | | --- | --- | | `0` | command succeeded, help/version was requested, or a `None`-returning program completed | | `1` | compile, package, build, test, or runtime operation failed | | `2` | command usage or option parsing was invalid | For `aura run`, an `int32` result from the entry module's `main` becomes the requested process exit status; a `None` result completes successfully. Host operating systems may restrict how exit values are represented after the value leaves Aura. `aura test` succeeds only when every selected `.au` program checks and runs within its timeout and every integer `main` result is zero. ## Internal Errors An `internal error` message indicates an implementation invariant failure or a defensive check for malformed internal input. Valid, statically checked Aura source must not produce one. Panics, host crashes, memory-safety failures, and hangs are never conforming diagnostic behavior and must be treated as compiler or runtime bugs. ## Source: docs/manual/enums-and-match.md # Enums And Pattern Matching Enums define nominal sum types. Each value contains exactly one declared variant and, when that variant has payloads, one value for each payload position. Pattern matching evaluates a scrutinee once, selects the first matching arm, and binds payload values for that arm. Aura uses enums for user data and for maintained runtime outcomes including `Option`, `Result`, queue operations, task waits, process status, supervisor events, and I/O errors. ## Enum Declarations ```aura enum Status: Ready(count: int32) Failed(str) Empty ``` A variant has one of three shapes: - no payload, written without parentheses: `Empty` - positional payloads, written as types: `Failed(str)` or `Pair(int32, int32)` - named payloads, written as `name: Type`: `Ready(count: int32)` One variant cannot mix positional and named payload declarations. Empty parentheses are not a payload-free declaration; omit them. Variant names must be unique within the enum. Every payload type must exist with the correct arity. Enums may be generic and bounded: ```aura enum Load[T: Named]: Ready(T) Failed(message: str) Empty ``` Type parameters must be unique, substitutions are invariant, and every bound must be satisfied. See [Generics And Traits](/manual/generics-and-traits). An enum is private to its defining module unless declared `public enum`. Individual variants do not carry separate visibility modifiers; importing a public enum exposes its variant constructor surface. Imported payload types must themselves be usable in the importing context for construction and matching to type-check. The complete declaration and pattern syntax is in [Grammar](/manual/grammar#enums) and [Grammar](/manual/grammar#patterns-and-statement-matches). ## Construction Use the enum type and variant name: ```aura ready = Status.Ready(count=3) failed = Status.Failed("disk full") empty = Status.Empty ``` A payload-free variant is a value and is not called. A payload variant is called with its exact payload shape: Every payload slot is owned. A declaration such as `Failed(str)` therefore has the constructor contract `Failed(own str)`, and a named `Ready(value: T)` slot is constructed as `Ready(value: own T)`. This rule also applies to builtin variants such as `Option.Some(own T)`, `Result.Ok(own T)`, and `Result.Err(own E)`. - positional variants accept positional arguments in declaration order; a single positional payload also accepts `value=` - named variants accept either positional arguments in declaration order or their declared payload names - every payload must be supplied exactly once - unknown, duplicate, missing, or excess payload arguments are rejected - each payload expression must have the exact substituted payload type - a non-copy payload expression is consumed by its `own` payload slot Do not mix positional and named construction styles in one variant call. User-defined named variants should use their declared names for clarity; multi-payload positional variants cannot be constructed with arbitrary named arguments. Named payload expressions evaluate in the order written at the call site. Their captured results then bind by payload name to declaration-order payload slots; declaration order does not reorder expression evaluation. Pattern payload positions continue to correspond to that declaration order. ## Generic Construction And Inference Explicit specialization fixes generic arguments: ```aura ok = Result[int32, str].Ok(7) missing = Option[str].None ``` Generic enum arguments may instead be inferred from payloads or an expected annotation, argument, or return type: ```aura ok: Result[int32, str] = Result.Ok(7) missing: Option[str] = Option.None ``` Every type parameter must resolve. A payload-free generic variant such as `Option.None` usually needs an expected type or explicit specialization because it carries no value from which to infer `T`. Bare builtin constructor names such as `Some(...)`, `Ok(...)`, `Err(...)`, or `None` are accepted only where the expected enum identity is unambiguous. Qualified constructors are the normative reference style. ## Copy And Move Behavior A user enum is copyable when every payload type declared by every variant is statically copyable. Otherwise the enum is a move type. This classification is structural across all variants, not based on the variant held at runtime. `Option[T]`, `Result[T, E]`, `SendError[T]`, and `QueueReceive[T]` follow the same payload-copy rule. `TaskResult[T]`, `SelectOutcome[Q, T]`, `WaitAny[T]`, and `WaitAll[T]` remain move outcome types in Aura 0.3 even for copy payloads. An unconstrained generic payload is not assumed copyable. See [Types](/manual/types#copy-and-move-categories). ## Statement Matches Statement-form `match` executes a statement suite: ```aura match ready: case Status.Ready(count): print(count) case Status.Failed(message): print(message) case Status.Empty: print("empty") ``` The scrutinee is evaluated exactly once. Arms are considered in source order and only the selected arm executes. Payload subpatterns are positional even when construction uses named payload arguments: they correspond to payload declaration order. An enum match must cover every variant and all relevant nested payload patterns, or finish with `_`: ```aura match ready: case Status.Ready(count): print(count) case _: print("not ready") ``` The wildcard binds nothing. An unguarded wildcard may appear only once and must be the final arm. A guarded wildcard may appear earlier because its guard can be false. Duplicate and provably unreachable unguarded arms are rejected. ## Match Expressions A match expression produces a value: ```aura def status_label(status: own Status) -> str: return match status: case Status.Ready(count): f"ready: {count}" case Status.Failed(message): message case Status.Empty: "empty" ``` Each arm contains exactly one expression, not a general statement suite. All arm results must have one compatible exact type, using an expected surrounding type when available. Only the selected arm expression is evaluated. Expression arms may also use the inline grammar `case Pattern: expression`; statement-match arms must put a suite on following indented lines. See [Grammar](/manual/grammar#match-expressions) for the exact layout forms. ## Pattern Forms At the top level of a match arm, Aura 0.3 supports: - an enum variant pattern for an enum scrutinee - a recursively nested fixed-arity tuple pattern for a tuple scrutinee - a supported literal pattern for a scalar scrutinee - a lowercase name that binds the complete scrutinee - `_` `case value:` is an unguarded catch-all, binds the complete scrutinee, and must be the final arm. `case value if condition:` makes that binding visible to the guard and body; because the condition may be false, another unguarded catch-all is still required for an open or otherwise uncovered domain. Use `_` when the complete value is intentionally ignored. Variant payload patterns must match the exact payload arity. A payload-carrying variant must bind or structurally match all payload positions; a payload-free variant accepts no subpatterns. Nested variant patterns are supported when their payload types are enums. Tuple patterns use `(left, right)` or singleton `(value,)` syntax and may nest other supported patterns: match ((1, 2), true): case ((left, right), flag): print(left + right) print(flag) The tuple arity and recursive shape must match exactly. Empty tuple patterns, multi-element trailing commas, and rest/star patterns are rejected. Each pattern binding is local to that arm and cannot shadow a name already visible there. `_` never introduces a binding. See [Names And Scopes](/manual/names-and-scopes#pattern-scope). ## Guards And Alternative Patterns Add `if` after a pattern when structural matching needs one exact Boolean condition. Join alternatives with `|` when one arm accepts several shapes: match response: case Result.Ok(value) if value > 0: print("positive") case Result.Ok(value) | Result.Err(value): print(value) Alternatives are probed from left to right. The first structural match supplies the guard and body bindings, and the guard runs once. Every alternative must bind exactly the same names with identical types and capabilities. Duplicate or subsumed alternatives are rejected. A guard must have exactly type `bool`. A false guard continues with the next arm. A guarded arm contributes no exhaustiveness coverage, including `case _ if condition`. A trap or propagated failure remains primary. `match own` probes before extracting a non-copy payload. Candidate bindings may be inspected in the guard but cannot move until the guard commits the arm. `match mut` publishes guard mutations before false continuation, `try` propagation, or trap cleanup, so later arms and cleanup observe the update. ## Qualified And Short Variant Patterns The fully qualified style is always valid: ```aura match result: case Result.Ok(value): print(value) case Result.Err(message): print(message) ``` When the scrutinee type supplies one unambiguous enum identity, the enum prefix may be omitted: ```aura match result: case Ok(value): print(value) case Err(message): print(message) ``` Use the qualified form in public examples and reference material when ambiguity is possible. A qualified pattern must name the scrutinee's actual enum and an existing variant. ## Match Capabilities `match own` consumes a non-copy scrutinee place and yields owned payload bindings: ```aura def main(): result: Result[str, str] = Result.Ok("hello") match own result: case Result.Ok(message): print(message) case Result.Err(error): print(error) ``` Use bare `match` to retain the scrutinee and expose shared non-copy payload bindings: ```aura result: Result[str, str] = Result.Ok("hello") match result: case Result.Ok(message): print(message) case Result.Err(error): print(error) print("result is still owned") ``` `match mut` requires a mutable place scrutinee. It gives mutable-borrowed payload bindings and reconstructs/writes the enum value back on normal arm exit, `return`, `break`, `continue`, and `try` propagation. Overlapping nested mutable matches are rejected. A payload binding becomes stale if the exact matched place, its root, or an ancestor field is reassigned; a proven-disjoint sibling-field write remains valid. For tuple patterns, `match own` consumes a non-copy tuple as one whole value and gives owned leaf bindings. Bare `match` retains the tuple and gives shared leaf provenance. `match mut` with a tuple pattern is rejected; the minimal tuple surface has no recursive reconstruction and writeback rule. Borrowed payloads cannot be moved as owned values. Copy payloads are ordinary copies. The complete place and provenance rules are in [Ownership And Borrowing](/manual/ownership-and-borrowing#borrowed-pattern-matching). ## Literal Matches Literal patterns are supported for `bool`, integer, floating-point, and `str` scrutinees: ```aura match code: case 200: print("ok") case 404: print("missing") case _: print("other") ``` The literal must have the scrutinee's exact scalar type after contextual literal checking. Duplicate literals and arms after a covering wildcard are unreachable and rejected. Boolean matching is exhaustive when both `true` and `false` are covered by unguarded arms. Integer, floating-point, and string domains are open-ended and therefore require a final unguarded wildcard. Class patterns are deferred; use an explicit enum/tag representation or a wildcard and ordinary code. ## Builtin Enum Shapes These builtin generic enums are available without a module prefix. The table shows constructor contracts, so `own` makes their implicit payload ownership visible; enum declarations themselves continue to write only the payload type: | Type | Variants | | --- | --- | | `Option[T]` | `Some(value: own T)`, `None` | | `Result[T, E]` | `Ok(value: own T)`, `Err(error: own E)` | | `SendError[T]` | `Closed(value: own T)`, `Cancelled(value: own T)`, `TimedOut(value: own T)`, `Full(value: own T)` | | `QueueReceive[T]` | `Item(value: own T)`, `Closed`, `TimedOut`, `Cancelled` | | `TaskResult[T]` | `Ready(value: own T)`, `Error(message: own str)`, `TimedOut`, `Cancelled` | | `SelectOutcome[Q, T]` | `Queue(index: own int64, outcome: own QueueReceive[Q])`, `Task(index: own int64, outcome: own TaskResult[T])`, `Deadline(index: own int64)`, `Cancelled` | | `WaitAny[T]` | `Ready(index: own int64, value: own T)`, `Error(index: own int64, message: own str)`, `TimedOut`, `Cancelled` | | `WaitAll[T]` | `Ready(values: own list[T])`, `Error(index: own int64, message: own str)`, `TimedOut`, `Cancelled` | Module-qualified builtin enums are specified by their API chapters: | Type | Reference | | --- | --- | | `io.Error` | [I/O Module](/manual/io) | | `process.ExitStatus`, `process.Wait`, `process.RestartPolicy` | [Process Module](/manual/process) | | `process.Error`, `process.SupervisorEvent`, `process.SupervisorWait` | [Process Module](/manual/process) | Treat every documented timeout, cancellation, closure, and error variant as semantically distinct. Use `_` only when all remaining outcomes genuinely share one policy. ## Grammar The normative enum declaration, generic parameter, variant payload, construction, statement-match, expression-match, pattern, and match-capability productions are in [Grammar](/manual/grammar#enums), [Grammar](/manual/grammar#patterns-and-statement-matches), and [Grammar](/manual/grammar#match-expressions). Payload-free variants omit parentheses. Variant declarations use either positional or named payloads and cannot mix the two forms in one variant. ## Typing Rules Enums are nominal, substitutions are invariant, and every payload has one exact declared type after generic substitution. A constructor must identify one existing variant and bind its complete payload shape. Generic arguments come from explicit specialization, payloads, or expected type; every parameter must resolve and satisfy its bounds. A match pattern must agree with the scrutinee type and payload arity. Enum and Boolean matches are exhaustive; open scalar literal domains require a final wildcard. Match-expression arms produce one compatible exact result type. ## Runtime Semantics An enum value stores one variant and its payloads. Constructor payload expressions evaluate in source order. For named construction, captured results then bind by payload name to declaration-order slots. Equality compares nominal enum identity, variant, and payload values. A match evaluates its scrutinee exactly once, tests arms in source order, and executes only the first matching arm. A match expression evaluates only its selected result expression. `match mut` reconstructs and writes the selected enum value back to its mutable place on every arm exit. ## Ownership And Evaluation Order Every variant payload is an owned destination. `match own` consumes a non-copy scrutinee and gives owned non-copy payload bindings. Bare `match` retains the scrutinee and exposes shared payload borrows; `match mut` requires one exclusive mutable place and exposes mutable payload borrows. Copy payloads copy normally. Pattern bindings are arm-local, and reassigning a matched place or ancestor invalidates dependent mutable bindings while a proven-disjoint sibling write does not. Aura performs no hidden payload clone. ## Diagnostics `AU1101` reports malformed enum, variant, match, arm, guard, or or-pattern syntax. `AU2001` reports unknown enum types, variants, and payload types. `AU2002` covers generic inference or bounds, constructor/payload type mismatch, literal-pattern type mismatch, a non-Boolean guard, alternative binding type mismatch, and incompatible match-expression results. `AU2004` reports invalid variant-constructor argument binding. `AU2999` covers duplicate variants, invalid payload shapes, missing or unreachable arms, non-exhaustive matches, mismatched alternative bindings, duplicate/subsumed alternatives, class patterns, unsupported pattern forms, and remaining enum/match rejections. `AU3001` reports use after `match own`, a payload move, or moving an owned candidate before its guard commits. `AU3002` reports moving through a shared match, overlapping mutable matches, or requiring a mutable match place. `AU3003` reports mutation or reassignment through an immutable enum/payload place. Operations in the selected arm retain their runtime code: `AU4001` for a general trap, `AU4002` for arithmetic overflow or underflow, `AU4003` for a bounds or lookup violation, `AU4004` for a zero divisor, and `AU4005` for a resource or I/O failure. ## Backend Support User and builtin generic enums, structural enum equality, construction and inference, statement and expression matches, exhaustiveness, nested patterns, short variants, scalar literal patterns, guards, or-patterns, top-level catch-all bindings, owned/shared/mutable matching, and borrowed matching are implemented for MIR execution and direct native generation. Both backends receive the same checked arm decision tree and are forced to agree on selected arms, payload values, writeback, and primary diagnostics. ## Limits And Implementation-Defined Behavior Aura has no range/rest patterns, named-payload patterns, class/collection destructuring, arbitrary predicate pattern, Duration/f-string pattern, or inline suite for statement matches. Expression arms contain exactly one expression. `TaskResult`, `SelectOutcome`, `WaitAny`, and `WaitAll` remain move outcome types regardless of copy payloads. Scrutinee and arm order, exhaustiveness, payload order, and borrowed-match writeback are language-defined rather than implementation-defined. ## Status Nominal and generic enums, positional and named payloads, qualified and contextual builtin construction, structural copy/move classification, statement and expression matches, exhaustiveness, nested enum patterns, scalar literal patterns, top-level catch-all bindings, wildcards, short variants, and borrowed matching are implemented for the post-Phase 1.5 surface. Match expressions, like every expression, produce owned results; a non-copy result must come from an owned source. Tuple patterns are implemented under Accepted ADR-0026. Guards and or-patterns are implemented under Accepted ADR-0049. Class/collection destructuring beyond the tuple kernel and arbitrary predicate patterns are unavailable. ## Source: docs/manual/execution-model.md # Execution Model Aura source is statically checked, lowered, and executed with deterministic single-expression sequencing plus scheduler-controlled concurrency and external I/O. This chapter defines observable behavior shared by `aura run` and built programs. ## Maintained Execution Paths Aura 0.3 maintains one checked source language and two runtime representations: - `aura run` lowers the entry package to MIR and executes it in the MIR runtime. - native direct builds lower MIR-compatible program structure to native code linked with the direct runtime. `aura build --backend direct` requires direct emission and fails if the program cannot be emitted. The default `--backend auto` first attempts direct emission and may fall back to a native launcher containing serialized MIR plus the MIR runtime when direct emission fails. This fallback is a packaging choice, not a third language semantics. Both runtime representations MUST agree on maintained observable behavior. Backend parity tests compare the eligible runtime fixture corpus. Duration values remain signed 128-bit nanosecond counts across both paths. Direct code passes a Duration literal as its exact low and high 64-bit two's-complement limbs, and the native runtime reconstructs the same i128 value. This ABI transport never narrows through milliseconds or a host timer type. ## Entry Module Execution After successful checking, an entry module runs in one of two modes: 1. If it has executable top-level statements, those statements execute in their stored source order. The file cannot also declare a local `main`. 2. Otherwise, a local `main()` is called when present. It returns `None` or `int32`. An imported function named `main` is not an entrypoint. Imported module top-level statements do not execute as import side effects in Aura 0.3. For `aura run`, a returned `int32` is passed to the host process as the requested exit status; `None` means success. A built native program follows the same entry result contract. ## Evaluation Order Except for short-circuit boolean operators and control-flow constructs, subexpressions are evaluated left-to-right: - a binary expression evaluates its left operand before its right operand - collection literal elements are evaluated in source order - a dictionary evaluates each key before its value and entries in source order - f-string interpolations are evaluated from left to right - an index evaluates its base before its index - a slice evaluates its base, written start, and written end exactly once from left to right; omitted endpoints evaluate nothing - a receiver is evaluated before call arguments - every supplied call or constructor argument is evaluated in call-site source order - a lambda copies or moves every by-value capture when the lambda expression is evaluated, before any later sibling expression - a comprehension allocates its result, then evaluates clause iterables and filters in nested outer-major order before evaluating its textually leading output; a dictionary output evaluates its key before its value Evaluating a copy place captures its copied value at that point. A non-copy place selected as a binary left operand, index base, method receiver, or indexed-assignment target remains borrowed until that operation has consumed all of its inputs. A later shared borrow is permitted, but an overlapping mutable borrow or consumption is rejected with `AU3002`; the diagnostic points to both the conflicting access and the retained-borrow origin. This applies to name roots and projected member places, and no backend may insert a hidden deep clone. Each f-string interpolation is converted to its rendered `str` at its own position before the next interpolation begins. Each append preflights the maintained 64 MiB constructed-string limit. An oversized result reports `AU4005`, does not evaluate a later interpolation, and releases the partial output through ordinary failure cleanup. A list or str slice captures its base, then any written start, then any written end. A non-Copy base remains retained while the endpoint expressions run. Negative endpoints are normalized once after those expressions complete; both effective bounds are checked in `0..=len`, followed by the `start <= end` check. A failure traps with `AU4003` and returns no partial value. A successful slice copies the selected range in source order into a fresh owned `list` or `str`. String endpoints count Unicode scalar values and locating them is O(n). No maintained backend may substitute Python-style endpoint clamping or a view into source storage. All supplied arguments complete before any omitted default is evaluated. Each supplied function/method argument or class-field expression is fully evaluated before the next supplied expression begins. Its copy or move result is captured in the destination slot; a borrow-mode selection is established without a clone and remains subject to the retained-borrow overlap rule above. Later side effects cannot cause an earlier captured argument or field value to be re-read. Omitted function parameters and class fields then evaluate their defaults in declaration order when the call or construction occurs. Binding supplied values to declaration slots never reorders them, and a supplied slot suppresses its default. Each omission causes a fresh evaluation; a mutable default value is not a process-global singleton. A shared-borrow parameter's default temporary lives until the call completes. An `own` parameter consumes its fresh default temporary. Mutable-borrow defaults are statically rejected. Enum-variant constructor arguments also evaluate in call-site source order. Named arguments are then bound by name to the variant's declaration-order payload slots; declaration-slot order never reorders their evaluation. When two evaluated keys in one dictionary literal compare equal, the later value replaces the earlier value and the key keeps its first insertion position. `and` evaluates the right operand only when the left value is `true`. `or` evaluates the right operand only when the left value is `false`. Both operands have static type `bool`. A lambda call evaluates arguments under its contextual structural function type. A read-only closure borrows its owned environment for the body and may be called repeatedly. A closure whose body consumes a non-Copy capture is consumed by the call. Never-called and called closure environments are cleaned up exactly once on both maintained backends. A comparison chain evaluates its operand expressions from left to right at most once. It evaluates each adjacent link after obtaining that link's right operand, stops at the first false link, and does not evaluate any remaining operand. This applies equally to chains containing tuple `==` or `!=`: a tuple used as a middle operand is evaluated once and read by both adjacent links. An assertion evaluates its condition exactly once. A true condition skips the optional message and falls through. A false condition evaluates the message exactly once, then establishes the assertion failure before cleanup begins. A trap in either operand occurs first. Assertion-triggered cleanup follows the ordinary reverse-order rule, and a cleanup trap does not replace the assertion diagnostic. ## Calls And Returns A call evaluates and binds arguments, then transfers control to the target body or runtime builtin. Explicitly owned non-copy arguments have been moved at the call boundary; bare shared arguments remain owned by the caller and are constrained for the duration of the call. `return value` evaluates `value`, copies or moves it into the owned result, runs active lexical cleanups, and returns to the caller. Reaching the end of a `None` function returns `None`. A non-`None` function cannot pass static checking if a reachable path falls through. A recursive Aura call consumes one logical call-depth unit. The maintained runtime rejects execution after 256 nested Aura calls with a source diagnostic rather than allowing the host stack to overflow. ## Foreign Calls A direct extern call evaluates its arguments left-to-right, marshals them only after ordinary call binding succeeds, and synchronously invokes the matching process-global C symbol. The current Aura worker remains occupied until the foreign function returns. A missing symbol or pre-call marshalling failure prevents entry. Return validation occurs after the foreign function and cannot roll back native side effects or mutable-byte writeback. Empty `str`, `list[uint8]`, and `mut list[uint8]` views pass a null pointer with length zero. A non-empty shared view passes a valid const pointer and byte length. A non-empty mutable byte view uses a same-length scratch buffer: initial bytes are copied in, then exactly that length is copied back after the foreign function returns, even when later return-value validation produces an Aura runtime error. The callee must not retain a pointer or read/write outside the supplied length. Aura diagnostics do not unwind through a foreign frame. A native abort, signal, memory fault, or foreign unwind is not caught and may terminate the process. The complete type, ownership, package, and safety contract is in [FFI v0](/manual/ffi). ## Operators Arithmetic is checked under the selected concrete numeric type. - integer addition, subtraction, multiplication, power, floor division, remainder, checked left shift, negation, and casts reject overflow - builtin integer `/` and `/=` do not reach execution because static checking rejects them - for integers with nonzero divisor `b`, `q = a // b` is the mathematical quotient rounded toward negative infinity and `r = a % b` satisfies `a == q * b + r`; a nonzero `r` has `b`'s sign - integer `//` or `%` by zero is a runtime failure; an unrepresentable floor quotient, including the signed minimum divided by `-1`, is integer overflow - floating `/` is ordinary true division, except that a zero divisor is an explicit runtime failure rather than IEEE infinity or NaN - floating `//` and `%` use the CPython-compatible divmod correction: start from the host remainder and `(a - remainder) / b`; when a nonzero remainder's sign differs from `b`, add `b` to the remainder and subtract one from the provisional quotient; give a zero remainder `b`'s sign; for a nonzero quotient, take its floor and add one when the provisional quotient minus that floor is greater than `0.5`; preserve the quotient's division-result signed zero when it is zero - floating `//` and `%` by either signed zero are runtime failures - integer `**` is checked, defines `x ** 0` as `1` including `0 ** 0`, and rejects a runtime negative exponent with `AU4001`; floating `**` follows the maintained floating power domain and overflow classification - `&`, `|`, `^`, and `~` operate on the declared fixed-width integer bit representation; all shift counts require `0 <= count < width` - signed `>>` is arithmetic and unsigned `>>` is logical; `<<` is checked, while `wrapping_shl` discards high bits and `saturating_shl` clamps at the declared bounds - `wrapping_shr` and `saturating_shr` have the same result as ordinary `>>` after the common count check - `divmod(a, b)` computes the same corrected quotient and remainder together from one evaluation of each operand; a zero divisor is `AU4004` - `round(integer)` preserves the exact integer type; `round(float)` uses ties-to-even and returns `int64`, with NaN, infinity, and out-of-range results classified as `AU4002` - ordinary floating operations otherwise use host IEEE-754 `float32`/`float64` behavior, including possible runtime NaN results from operations such as square root of a negative value - integer `.to_float()` converts to `float64` with IEEE-754 round-to-nearest, ties-to-even and may round; integer `as float32` or `as float64` retains its exactness check and fails instead of rounding - Duration addition, subtraction, and multiplication operate on signed 128-bit nanoseconds and reject overflow with `AU4002` - `Duration // int64` returns a Duration whose signed nanosecond count is the mathematical quotient rounded toward negative infinity; a zero divisor is `AU4004` and the signed-minimum divided by `-1` is `AU4002` - string `+` creates a new concatenated `str` - numeric Array `+`, `-`, and `*` traverse exact-shape row-major buffers and return fresh owned storage; float Arrays also support `/` - ordinary integer scalar and Array arithmetic remains checked; the explicit `wrapping_*` methods use fixed-width modular arithmetic and `saturating_*` methods clamp to the declared width - Array rank-zero/negative-dimension construction, `from_list` count mismatch, exact-shape/rank mismatch, and empty reductions use `AU4007`; shape-product/element-count overflow and allocation failure use `AU4005`; direct coordinate and first-axis-slice bounds failures use `AU4003` - floating Array reductions visit row-major elements left to right with deterministic dtype rounding and NaN propagation; `mean` accumulates in `float64`, and no reassociation or vectorized reduction order is promised Trait-backed operators invoke the selected trait implementation method with ordinary receiver, argument, move, borrow, and runtime-error behavior. `/` may invoke `Div.div` for an applicable non-numeric user type. `//` and `//=` invoke `FloorDiv.floor_div` when no builtin numeric or `Duration // int64` rule applies. `==` and `!=` perform structural equality for maintained plain values and collections. Resource/handle identity is not a portable substitute for an application identifier; programs should use documented resource data rather than depend on equality of runtime handles. More precisely: - numbers, booleans, strings, durations, ranges, enum values, classes, datagrams, and HTTP responses compare by represented value - tuples compare corresponding element values from left to right using ordinary equality, recursively for nested tuples, and stop at the first unequal element - vectors compare element-by-element in order - maps and sets compare by contents and ignore insertion order - floating equality follows IEEE behavior, so a NaN value is not equal to itself - queue/task handles, random generators, and live file, process, listener, stream, exchange, supervisor, and WebSocket values compare by shared runtime identity Equality is defined only after static typing has established compatible operand types. Tuple equality specifically requires the same static tuple type. It reads both complete operands and consumes neither, including tuples with non-copy elements. Runtime element-type, transport, or backend metadata carried with a tuple value is not compared; it cannot change the recursively determined value result. Operand expressions retain their ordinary ownership effects; the equality operation itself adds no move of the resulting tuples. Tuple `!=` is the logical negation of tuple `==`. Tuple `<`, `<=`, `>`, and `>=` remain static errors; Aura does not define lexicographic or metadata-based tuple ordering. ## Value Rendering `print`, f-string interpolation, and scalar `.to_string()` use Aura's maintained value rendering where applicable. Strings render as their contents without quotes and `None` renders as the empty string. A directly printed `float32` or `float64` uses the shortest decimal spelling that round-trips to the same value in its source type. Integral finite values retain a decimal marker, scientific notation is used when it is shorter, and signed zero remains `-0.0`. A Duration renders as an exact decimal millisecond value with an `ms` suffix, using at most six fractional digits and trimming trailing fractional zeros; for example, `2s` renders as `2000ms` and `1ms // 3` renders as `0.333333ms`. This rendering policy is accepted under ADR-0019. Lists render as `[a, b]`, non-empty sets as `{a, b}`, empty sets as `set()`, and dictionaries as `{key: value}` in their defined order. Class values render as `Class(field=value, ...)`; enum values render as `Enum.Variant(...)`. Nested strings remain unquoted, so this display form is for people and is not a round-trippable serialization format. A deterministic random generator renders exactly `` without exposing or advancing its state. Live resources render opaque labels such as `` or `` without host identifiers. An FFI opaque handle renders as ``, using its canonical Aura type name and never exposing the foreign pointer address. ## Assignment And Mutation A simple assignment ordinarily evaluates the right side before creating or updating the target. Simple dict indexed assignment is the deliberate exception: it evaluates and captures its owned key, consuming it when non-copy, before evaluating and consuming the assigned value. A side effect in the value therefore cannot retarget the write. Simple dict assignment accepts any `V`. Reassignment preserves the target's type. A compound assignment selects its target place once and uses exactly the corresponding binary operator dispatch, including an applicable user-defined operator trait for a root or projected target. For a copy target, it captures the current copied value before evaluating the right operand and stores the operator result into the originally selected place, so right-side effects neither change the left operand nor retarget the store. A non-copy root or projected target remains borrowed across the right operand; overlapping mutable borrow or consumption is rejected with `AU3002`. Direct indexed compound assignment requires a copy `list` element or `dict` value. A non-copy indexed element is rejected rather than implicitly cloned or destructively moved, with `AU3006`. A non-copy direct indexed read is rejected with `AU3005`. Field and index assignment mutate the selected place. List indices are zero-based. Simple dict assignment replaces an equal existing key or adds a new entry; an absent key is therefore not a simple-assignment failure. Compound dict assignment requires an existing key and traps with `AU4003` if its initial read finds none. Failed checked mutation leaves the operation incomplete and produces its documented runtime failure or typed error. Moving a field marks that field unavailable while leaving disjoint fields usable. Reassigning the exact moved place reinitializes it. ## Collections And Iteration `list` preserves element order. `dict` uses insertion order for its `keys()`, `values()`, and `items()` projections. Replacing an equal dict key, including from a later literal entry, retains that key's existing slot. `set` uses an insertion-oriented runtime representation, but its public order is not promised. Algorithms should rely on ordering only where the relevant API promises it. `list.map` and `list.filter` traverse from first element to last and produce their eager result in that order. Their shared receiver remains unchanged. `map` invokes its callback once per source element and owns each returned value in the new list. `filter` invokes its predicate once per source element and clones accepted elements into the new list. Natural and keyed `list.sort` calls mutate their receiver into a stable order: equal elements or keys retain their prior relative order. Keyed sorting evaluates its key function exactly once for every element, from first to last, and stores all keys before moving any receiver element. A trap during key evaluation therefore propagates before mutation and leaves the complete source order unchanged. Bare iteration over a `list` or `set` retains and freezes the selected collection for the loop and yields shared element access. `own` iteration moves the collection into a loop-private source once at entry and yields owned elements; reinitializing the consumed source binding in the body does not switch or truncate the active iteration. That one-time source selection is accepted under ADR-0017 and does not alter ADR-0006's ownership modes. `mut` iteration over a mutable list grants exclusive element access with writeback and retains the collection; mutable set iteration is rejected. Range iteration yields independent `int32` values from `start` inclusive to `end` exclusive. Explicit `mut` and `own` Range modifiers are rejected with `AU3004` because there is no place access or ownership transfer to modify; use the bare form. Queue iteration receives items until the queue closes, cancellation is observed, registered producers complete cleanly with no more items, or an unread sibling-task failure ends the surrounding group. It is a scheduler operation rather than iteration over a snapshot. Each item arrives already owned by the loop binding; explicit `own` and `mut` modifiers are rejected because neither the received value nor the copyable Queue handle has a place-iteration ownership mode to modify. Under accepted ADR-0017, the bare form evaluates and copies its Queue handle once at loop entry. This does not freeze the source binding: rebinding that variable in the body is allowed, but later iterations continue receiving through the captured handle. ADR-0006's ownership carve-out is otherwise unchanged. ### Comprehensions A list, set, or dictionary comprehension creates a fresh empty owned result and executes its clauses like nested bare loops. The first source is selected once. For each selected item, filters execute left to right and stop that item at the first `false`. Each inner source is selected once for every combination that survives the earlier filters. The traversal is outer-major: the complete inner traversal for one outer item finishes before the next outer item begins. At an innermost surviving combination, the list/set element or dictionary key/value is evaluated and inserted. The output is written first in source but executes last. A dictionary captures its key before evaluating its value. Set deduplication and dictionary equal-key replacement use their literal/storage contracts. Every clause inherits the selected bare-loop behavior above. List/set sources remain shared and frozen through downstream filters, sources, and output. Range targets are copy `int64`. `enumerate` and `zip` retain their lockstep rules. Queue copies its handle for the clause and yields each received item owned; a Queue comprehension ends only when ordinary Queue iteration ends. Insertion owns its output. Copy values copy; owned non-Copy values move; a shared non-Copy list/set element needs an explicit clone-safe clone. Each reached lambda creation uses ADR-0037. A trap or `try` propagation destroys the partial result and all active temporary sources exactly once before continuing the ordinary failure or early-return path. ## Pattern Matching The scrutinee is evaluated exactly once. Arms are considered in source order. The first matching arm executes. - `match own` consumes a non-copy scrutinee place - bare `match` leaves the scrutinee owned and exposes shared payload borrows for non-copy data - `match mut` permits payload mutation and writes the reconstructed enum value back on normal arm exit, `return`, `break`, `continue`, and `try` propagation - literal patterns compare against the scrutinee value - `_` always matches and binds nothing A match expression evaluates only its selected arm and produces that arm's value. Static exhaustiveness ensures a checked match has a selected arm for every permitted input. ## Conditional Expressions For `value if condition else alternative`, the runtime evaluates `condition` first and exactly once. A true result evaluates and produces `value`; a false result evaluates and produces `alternative`. The unselected arm performs no calls, moves, mutations, I/O, allocation, or runtime failures. Static checking still analyzes both arms and merges their ownership effects. This conservative merge prevents later use of a non-copy value that may have been moved on the selected path. MIR and direct lowering use an explicit condition branch and a single typed join value; a backend must not eagerly evaluate either arm. When the surrounding operation takes a shared borrow, the join does not consume the selected source value, so both source owners remain available after the borrow ends. ## `try` `try expression` evaluates one `Result[T, E]` value: - `Ok(value)` produces `value` and continues the enclosing expression - `Err(error)` returns immediately from the enclosing function When the enclosing function uses a different error type, the implementation invokes the applicable `From` trait conversion before returning the error. Active `with` scopes are cleaned up during this early return. ## Resource Lifetime And Cleanup `with` creates an active cleanup registration after its resource expression succeeds. Leaving the body invokes `close(mut self) -> None` exactly once through that registration. Cleanup runs on: - normal fallthrough - `return` - `break` or `continue` that exits the scope - `try` error propagation - a maintained Aura runtime failure Nested active cleanups run in reverse registration order. If a body is already failing and cleanup also fails, the original body diagnostic remains primary. Resource-specific `close()` behavior is defined in its API chapter. Explicitly closing a resource before scope exit is permitted only where the resource contract makes repeated close harmless; otherwise programs should let the lexical owner perform cleanup. These cleanup rules apply while Aura control flow or a maintained runtime failure exits the task through the language cleanup machinery. Internal scheduler abandonment is a last-resort containment path used when the whole scheduler stops with a child still suspended, such as after root completion or a fatal reactor failure. It marks the remaining task cancelled and releases scheduler-owned and direct-runtime host state, but it does not invoke arbitrary Aura cleanup thunks. A direct generated stack may be reset on that path because it cannot be safely Rust-unwound across Cranelift frames. Programs must use structured `TaskGroup` scopes rather than depend on scheduler abandonment as a cleanup mechanism. ## Tasks And Scheduler Aura lightweight tasks run on cooperative pinned scheduler workers. The runtime uses the available parallelism reported by the host by default; the `AURA_WORKERS=` environment override selects an explicit count. Each child receives a stable assignment when it is spawned. Its coroutine stack never migrates and the runtime does not steal tasks between workers. Operations such as queue waits, task waits, sleep, nonblocking sockets, and scheduler-integrated I/O yield instead of creating one OS thread per Aura task. A task can also yield explicitly with `yield_now()`. The generic blocking-I/O pool may execute host calls concurrently, but those service workers do not run Aura code. The compiler inserts a cooperative scheduling check on every semantic loop backedge. Reaching the ordinary tail of a `while` or `for` body participates, as does `continue`; `break`, `return`, and another exit that leaves the loop do not traverse its backedge. These checks let a tight loop eventually return to the scheduler so ready timers, Queue operations, and socket work are not starved indefinitely. A loop safepoint is not preemption and does not inspect cancellation. A single long iteration can still delay every sibling pinned to the same worker until the body reaches its backedge, and long straight-line CPU work with no loop or scheduler operation can do the same. Use `cancelled()` when the task must observe cancellation, and use `yield_now()` when the program needs an explicit scheduling point between chosen chunks. Neither automatic nor explicit yielding specifies which ready local task runs next. MIR execution amortizes the cooperative yield with 8 units of function-local loop fuel. Direct native code uses 4,096 units and replenishes the fuel after yielding. A program proven to have no possible sibling Aura task elides the runtime check entirely. These backend strategies may produce different valid interleavings; scheduling order is not observable language order. An ordinary lightweight task requests 512 KiB of writable coroutine stack. The `TaskGroup.start_with_stack` and `start_soon_with_stack` methods accept an exact `int64` request from 256 KiB through 64 MiB inclusive. Accepted requests are rounded upward to the host page size and guard-protected; out-of-range requests are rejected rather than clamped. This surface is Provisional under ADR-0032. The 256 KiB lower bound is an explicit minimum for measured shallow tasks, not the general default. The complete compiled Aura HTTP example, including its MIR/direct language-execution frames, proved unsafe when 256 KiB was the global task default and succeeds with the 512 KiB default. The separate isolated runtime round trip that forces protocol callers to 256 KiB proves that service workers own the deep host protocol frames; it does not measure the full compiled task stack. `yield_now()` places the current lightweight task back in its worker's ready Set and returns when that worker selects it again. It gives other runnable local tasks an opportunity to proceed without waiting for an event or deadline, but it does not migrate the coroutine, steal work, guarantee that a different task runs, or specify a ready-task order. With no current schedulable lightweight task, it returns without effect. The scheduler owns a persistent event reactor. Nonblocking descriptors remain registered across scheduler turns, deadlines are ordered in a timer heap, and Queue, task-completion, and blocking-pool events notify the responsible ready queue directly, including across workers. Registration uses a check-subscribe-recheck protocol with wait epochs, so a readiness edge racing with suspension is not lost and stale wakeups do not resume a later wait. If no task is ready, the scheduler blocks until the next event or deadline; there is no periodic park tick. `select(source, ...)` evaluates its Queue, Task, and relative-Duration sources once from left to right, then uses one composite wait under that same protocol. Current-task cancellation wins; otherwise each arbitration probes sources by their original zero-based index and commits the first ready source. A wake is only a request to arbitrate, so a Queue item lost to another consumer before the selecting task resumes does not create a false outcome. Winner commit consumes at most one Queue item or selected Task result and removes every losing registration. All Duration sources share one base instant established after evaluation and validation. `control.retry` invokes its worker immediately for the first attempt. An `Ok` returns immediately. An `Err` is retained only until the helper determines whether another attempt exists. When one does, the helper waits for the current backoff unless it is zero, invokes the next attempt, and doubles the delay only when another retry could still use it. Every `Err` is retryable. The final permitted `Err` is returned exactly, without an extra sleep or multiplication. Worker traps and checked Duration overflow propagate as runtime diagnostics. Cancellation of the current task propagates through the helper and its scheduler-aware delay instead of being represented as the most recent `Err`. Queue and Task handles are the maintained cross-worker communication surface. Every other capture and result is owned `Transfer` data, preserving the share-nothing boundary. Cancellation and diagnostic context are installed per task and remain isolated across workers. Scheduling order among multiple ready tasks, completion order among independent tasks, and program-output order are not specified. Programs coordinate through queues, task results, cancellation, and other documented synchronization rather than timing assumptions. Aura exposes no worker identity or affinity API. Task execution is multicore; preemption and work stealing are unavailable, and speedup depends on the workload. Starting a child from a running task does not mutate the live scheduler through an alias. The runtime first prepares the child's guarded stack and task state, then transfers that prepared request to the scheduler for admission. If preparation fails, the start fails synchronously before a handle is returned and no child is admitted. A task may immediately wait on a successfully returned child handle, including inside a nested `TaskGroup`. The current admission broker preserves its own FIFO request order, but that is an internal safety property; child execution order remains unspecified. Deep HTTP parsing/construction, TLS operations, and maintained Unix WebSocket protocol steps run on a distinct bounded protocol-step service. Its two named workers have 2 MiB native stacks and share a 64-job queue. A job owns its protocol state for one bounded, nonblocking library step. The coroutine waits for the state to return before it observes cancellation or waits for descriptor readiness again, so there is never an abandoned protocol state with two owners, and no resource mutex remains held across the worker wait. Reactor readiness, absolute deadlines, and cancellation remain scheduler-side concerns. This protocol-step pool is lazily initialized and shared by every lightweight scheduler. Its workers intentionally live until process exit; Aura 0.3 has no protocol-pool shutdown or join surface. The non-Unix WebSocket fallback retains its compatibility path. Resolver, listener-bind, and file reads use the generic blocking-I/O pool. `AURA_BLOCKING_WORKERS=` selects its exact worker count without clamping; otherwise host parallelism is used with fallback `4` and a derived `2..=8` clamp. `AURA_BLOCKING_QUEUE_CAPACITY=` optionally bounds accepted pending jobs. Capacity excludes running jobs and callers waiting for admission, and omission preserves an unbounded queue. TLS asset bytes are read there before PEM parsing and rustls construction run on protocol workers. The generic pool is also process-global. Its settings are read once by the first runtime preflight and remain immutable for the process lifetime; that preflight starts no worker. First blocking submission creates the complete configured set, which production reuses until process exit without an Aura shutdown/join surface. Dynamic `json.parse` uses a third, independent process-global service with two 2 MiB-stack workers and total in-flight capacity two. A task reserves capacity before making the fallible owned copy of its parse source; saturation parks a lightweight task through the scheduler rather than spinning. Once admitted, synchronous `json.parse` waits through codec completion, so cancellation is observed at the task's next ordinary cancellation boundary rather than abandoning the codec job. Its dependency-owned recursive parsing runs on the service stack, while runtime materialization, JSON-aware cloning/rendering, and dump conversion/emission use iterative traversals. The direct backend waits for admission without value-table access, then holds read access only long enough to copy the source and releases it before submission and completion waiting. The bounded `json.is_valid` and `json.parse_string_map` operations remain caller-side and do not use this service. Codec workers are process-lifetime and have no Aura 0.3 shutdown or configuration surface. `Queue[T]` is a copy handle to shared runtime state. Under Accepted ADR-0033, a `Task[T]` handle is copyable only when its result is repeatable; every task handle remains transferable. Copying an allowed handle does not duplicate the underlying task or queue. Starting a task first copies or moves every argument into task-owned capture storage. The child then applies the target's declared parameter capability to that capture: a bare parameter borrows it, and an `own` parameter consumes it. Mutable targets are rejected statically. A task stores its completed result. Copy results, Queue handles, and recursively repeatable Task handles permit repeated observation. Every other transferable result has a unique observation right; each direct result call consumes it on every outcome, and multi-task waits consume the complete task list. `wait_any` abandons the unchosen rights. Task captures, results, and Queue payloads must also satisfy the structural Transfer check before the child is admitted to its spawn-time pinned worker. Queue and Task handle state is synchronized for cross-worker notification and observation; every other value crossing the boundary remains owned and share-nothing. The runtime also protects a non-repeatable stored result with an atomic one-winner claim. A failed second claim traps with `AU4001` and `task result has already been observed; non-repeatable task results allow exactly one observing attempt`. This is defense in depth for backend defects or foreign handles, not a replacement for static ownership diagnostics. TaskGroup scope cleanup joins, abandons, or accounts for a child without observing its successful result: cleanup does not claim the right or make the value available to another observer. ## Task Groups And Failure Observation `TaskGroup` owns children started within its scope. - normal scope exit waits for children that are making bounded progress - a child blocked in an unbounded group-owned wait is cancelled only when the runtime's live wait graph has no reachable waker - explicit `cancel()` signals cancellation and wakes scheduler-aware waits - a task failure observed through its `Task` result does not also abort the group as unread - an unread child failure aborts the group scope and wakes queue iteration/waits that depend on that group Cancellation is cooperative. Pure CPU code observes cancellation through `cancelled()`; `yield_now()` is a scheduling point but does not inspect cancellation. Compiler-inserted loop safepoints likewise do not inspect cancellation. Scheduler-aware blocking operations receive cancellation context directly. Queue reachability is based on live tasks known to hold `Queue` handles, not an elapsed-time threshold. A sender parked on a full open queue remains joinable while a live receiver can drain it; a receiver parked on an empty queue remains joinable while a live sender or another live owner of the open queue can send or close it. The task performing the join is not counted as its own child's waker, because it cannot use its queue handle until the join returns. Cycles made only of mutually blocked waits have no reachable waker and are cancelled. ## Host I/O And Cancellation Socket-backed network resources use nonblocking descriptors with persistent reactor registration. Their timeout and cancellation outcomes are documented per operation. Converting a Duration to a host wait is a checked boundary. Negative values, values outside the host timer range, and durations whose addition to the current instant would overflow are invalid inputs. Deadline overflow never silently becomes an unlimited wait. An API with an `io.Error` carrier reports `InvalidInput`; a process-error carrier reports `process.Error.Io(io.Error.InvalidInput)`; an API without either typed carrier traps with `AU4001`. This host-boundary classification is accepted under ADR-0019. Filesystem operations and some host operations run on the generic blocking-I/O pool under Accepted ADR-0035. When its optional pending-queue bound is full, Aura tasks wait for admission through the scheduler in FIFO order instead of blocking a pinned worker. Cancellation or deadline expiry before queue insertion prevents the operation from being submitted. Once inserted, the operation cannot be retracted: cancelling the Aura task cancels its wait, not an operating-system call already pending or executing. A cancelled write or other side-effecting operation may therefore complete in the host after Aura has stopped waiting, with its late result discarded. Programs requiring transactional cancellation must write to a temporary artifact and commit it explicitly. Bounding accepted pending jobs does not bound admission waiters or guarantee unrelated blocking-I/O progress while every configured worker remains stuck. Process cancellation and close operations signal/terminate according to the process API. Group-enabled processes extend those operations to the maintained host process group behavior. ## Standard Streams `print` and `io.write` preserve call order within one task. Concurrent writes may interleave at operation boundaries; no global record transaction is implied unless the application serializes output. `aura run` streams standard output while the program runs. If a later runtime failure occurs, already written output remains observable and the diagnostic is written to standard error. A broken stdout pipe is treated as clean early termination by the CLI. ## Runtime Limits The maintained resource size, header, frame, timeout, and platform limits are normative for Aura 0.3 and are collected in [Current Limits](/manual/current-limits). An implementation MUST reject or return a typed error when a limit is exceeded; it must not allocate without bound or hang indefinitely where the API supplies a deadline. ## Determinism Pure expression evaluation, ordinary control flow, and collection operations are deterministic for the same values. The following are external or scheduler-dependent and therefore not generally deterministic: - task interleaving among simultaneously ready tasks - wall/monotonic clock readings - process identifiers, exit timing, and host scheduling - network arrival order and peer behavior - filesystem enumeration supplied by the host - operating-system secure random output - the exact wording of host operating-system errors An explicitly seeded `random.Rng` is deterministic. Its xoshiro256** sequence, integer/float mapping, and shuffle order are fixed for Aura 0.3.x and specified in [Randomness Module](/manual/randomness). Secure random calls are external effects and never draw from that stream. Aura converts host effects into typed values and ordering primitives where practical, but does not pretend the host environment is deterministic. ## Source: docs/manual/expressions.md # Expressions An expression evaluates to a value. This chapter defines the reader-facing expression contract: available forms, grouping, precedence, evaluation order, and the main static restrictions. The exact productions and specialization/indexing disambiguation are normative in [Grammar](/manual/grammar#expressions-and-precedence). Type rules are centralized in [Static Semantics](/manual/static-semantics#expression-typing), and runtime behavior is centralized in [Execution Model](/manual/execution-model#evaluation-order). ## Primary Expressions Primary expressions are the atoms from which postfix, prefix, and binary expressions are built: - a name such as `count`, `from`, or `point` - an integer, float, duration, boolean, string, or f-string literal - `None` - a parenthesized expression or tuple - a list, set, or dictionary literal - a list, set, or dictionary comprehension ```aura count 42 3.14 10ms true "text" 'text' f"count={count}" None (left + right) (left, right) (left,) [1, 2, 3] {"ready": 2} {1, 2, 3} ``` The lexical spelling and default literal types are defined by [Lexical Structure](/manual/lexical-structure). A name must resolve under [Names And Scopes](/manual/names-and-scopes). Parentheses without a comma group exactly one expression. `(value)` is a group, `(value,)` is a singleton tuple, and `(left, right)` is a two-element tuple. Tuple value expressions always require parentheses; Aura does not accept a naked comma expression. ## Tuple Expressions A tuple expression evaluates and captures its elements left to right: pair = ("north", 7) nested = (pair, (true,)) Its type is the fixed structural tuple of its element types. A tuple copies if every element type copies; otherwise it moves as one complete value. See [Tuples](/manual/tuples) for unpacking and matching. A postfix tuple index is deliberately narrow: coordinates = (3, 4) vertical = coordinates[1] The index must be a non-negative integer literal known at compile time, must be in bounds, and must select a copy element. The result is a copy. Dynamic, negative, out-of-bounds, and non-copy-element tuple indexing are static errors; unpack a tuple when ownership of a non-copy element is required. Tuple `==` and `!=` require the same static tuple type and compare corresponding element values recursively. They read both operands without consuming either one. Tuple ordering operators remain unavailable. ## Delimiter Continuation An expression may span physical lines while a `(`, `[`, or `{` remains open. This applies uniformly to grouping, function and constructor calls, indexes, owned slices, specialization/type arguments, collection literals, and delimited portions of headers and declarations. The lexer joins those physical lines before the expression grammar runs. Continuation indentation is visual only. It does not create a suite or alter evaluation order. The maintained style indents continued content by one level. A trailing comma is still invalid, and a newline outside an open delimiter still ends the logical line. Backslashes do not continue a line. Ordinary strings and f-strings remain single-line. ## Evaluation Order Except for short-circuit boolean operators and control-flow expressions, evaluation is left-to-right: - a binary expression evaluates its left operand before its right operand - a postfix expression evaluates its base before its suffix inputs - an index evaluates its base before its index - a slice evaluates its base, written start, and written end once from left to right; omitted endpoints evaluate nothing - a receiver is evaluated before call arguments - explicit call and constructor arguments are evaluated in source order, with copy or move results captured before later argument side effects - collection elements are evaluated in source order - each dictionary key is evaluated before its value, and entries are evaluated in source order - a comprehension evaluates its clauses and filters before its textually leading output expression; nested clauses are outer-major, filters are left-to-right, and a dictionary output key precedes its value - f-string interpolations are evaluated from left to right - a conditional expression evaluates its condition first and then exactly one arm - a match scrutinee is evaluated once, before arm selection - a comparison chain evaluates operands left to right at most once and does not evaluate any operand after its first false link Evaluation order matters when an expression moves a value, mutates through a call, performs I/O, or can produce a runtime failure. A copy place contributes the copied value captured at its evaluation point. A non-copy place selected as a binary left operand, index base, method receiver, or indexed-assignment target remains borrowed through the operation's later inputs. Another shared borrow is permitted, but an overlapping mutable borrow or consumption is rejected with `AU3002`, which identifies both the conflict and the retained-borrow origin. Name roots and projected member places follow the same rule, and Aura never deep-clones the selected place implicitly. Each f-string interpolation renders to `str` at its own position before evaluation moves to the next interpolation. Static borrow analysis checks all accesses at one call boundary together even though runtime evaluation remains ordered. ## Precedence And Associativity The following table runs from lowest to highest precedence: | Level | Form | Associativity | | --- | --- | --- | | 1 | `value if condition else alternative` | right | | 2 | `or` | left | | 3 | `and` | left | | 4 | prefix `not` | right | | 5 | `==`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `not in` | chained left to right | | 6 | `|` | left | | 7 | `^` | left | | 8 | `&` | left | | 9 | `<<`, `>>` | left | | 10 | `+`, `-` | left | | 11 | `*`, `/`, `//`, `%` | left | | 12 | prefix `match`, `try`, unary `-`, unary `~` | prefix/right | | 13 | `**` | right | | 14 | specialization, indexing, member access, call, numeric cast | left-to-right postfix chain | | 15 | primary expression | — | Arithmetic, shift, bitwise, and boolean chains are left-folded. Power is the right-associative exception. For example: ```text a - b - c means (a - b) - c not a == b means not (a == b) a + b * c means a + (b * c) 2 ** 3 ** 2 means 2 ** (3 ** 2) -2 ** 2 means -(2 ** 2) ``` Equality, ordering, and membership share one precedence level and chain the Python way. `a < b < c` is one chain, not a comparison of a comparison, and `a == b == c` and `a < b == c` chain likewise. A chain of `n` operators is equivalent to the conjunction of its `n` adjacent comparisons, except that each operand expression is evaluated at most once. Parentheses still make a nested Boolean comparison explicit, so `(a == b) == c` is a distinct form that compares a `bool` against `c`. Parentheses override precedence: ```aura scaled = (left + right) * factor inside = lower < value < upper ``` ## Boolean Operators `and`, `or`, and `not` operate on `bool`; Aura has no general truthiness conversion for numbers, strings, collections, resources, or classes. `and` and `or` short-circuit: - `left and right` evaluates `right` only when `left` is `true` - `left or right` evaluates `right` only when `left` is `false` `not value` evaluates its operand and negates the boolean result. A matching operator trait may provide `not` for a supported user type, as described under [Generics And Traits](/manual/generics-and-traits#operator-traits). ## Conditional Expressions The Python-style form `value if condition else alternative` selects one value. For example, `label = "ready" if ready else "waiting"` chooses one `str`. The condition is evaluated first, exactly once, and must have type `bool`. When it is `true`, only `value` is evaluated; when it is `false`, only `alternative` is evaluated. Both arms must have one static result type. Surrounding expected context flows into both arms, so contextual literals such as integer literals, `None`, and empty collections can adopt that type. This context is structural: an empty collection nested inside a tuple arm adopts the corresponding concrete nested type from the other arm or the surrounding expected type. Contextual typing never implicitly converts an already-bound value. The form has lower precedence than `or` and associates to the right. `a or b if ready else c` means `(a or b) if ready else c`, while `a if first else b if second else c` means `a if first else (b if second else c)`. Both arms are checked even when the condition is a literal. Ownership state is checked independently for each arm and merged conservatively afterward. A non-copy value moved by either arm is therefore unavailable after the conditional expression. The surrounding use determines whether an arm is moved: passing the result to an ordinary shared-borrow parameter borrows the selected arm and preserves both source owners, while assignment, return, or an `own` parameter consumes the selected value. ## Arithmetic And Comparison Built-in arithmetic supports equal integer types or equal floating-point types. `str + str` concatenates strings. Aura does not implicitly widen non-literal numeric values. | Operators | Builtin result | | --- | --- | | `+` | Same numeric type, `str` for string concatenation, or `Duration` for two Duration operands | | `-` | Same numeric type, or `Duration` for two Duration operands | | `*` | Same numeric type; `Duration` for `Duration * int64` or `int64 * Duration` | | `**` | Same exact integer or floating type | | `//` | Same numeric type, or `Duration` for `Duration // int64` | | `%` | Same numeric type | | `/` | Same floating-point type | | unary `-` | Same numeric type | | `&`, `|`, `^`, unary `~` | Same exact integer type | | `<<`, `>>` | Same exact integer type for value and count | | `==`, `!=` | `bool` for equal operand types | | `<`, `<=`, `>`, `>=` | `bool` for equal numeric types or two Duration values | | `in`, `not in` | `bool` for a supported container | `Array[T]` adds exact-shape elementwise `+`, `-`, and `*` for the four maintained numeric dtypes. A same-dtype scalar may appear on either side. Floating Arrays also support `/`; integer Array `/` remains the same `AU2003` static error as scalar integer `/`. Every result is a fresh Array. There is no array-shape broadcasting or mixed promotion. See [Numeric Arrays](/manual/numeric-arrays). For tuple operands, `==` and `!=` require exactly the same static tuple type. They compare corresponding element values from left to right using ordinary equality, recursively for nested tuples. The comparison reads both complete operands and does not move either one, including a tuple that contains non-copy elements. Runtime tuple element-type, transport, or backend metadata does not participate in the value result. A tuple literal on either side may be contextually typed from the other operand's known tuple type, recursively through nested literals. After that symmetric contextual typing, the two static tuple types must still match exactly. Evaluating either operand keeps its ordinary ownership effects; the equality operation adds no move of the resulting tuple. Equality and inequality have one contextual `Option` rule: when either operand has static type `Option[T]`, a bare `None` on the other side denotes `Option.None` of that same specialization. The rule is symmetric. Unit `None == None` is `true` and unit `None != None` is `false`; a qualified `Option.None` with no context for its type argument is rejected. Aura rejects Python identity tests such as `value is None`; use `value == None`, `value != None`, or `match`. Arithmetic and ordering may resolve through the corresponding operator trait. For non-numeric user types, `/` requests `Div.div`; `//` requests `FloorDiv.floor_div` when neither a builtin numeric rule nor the builtin `Duration // int64` rule applies. Builtin equality does not use an equality operator trait in Aura 0.3. Tuple `<`, `<=`, `>`, and `>=` are static errors. Aura has no lexicographic tuple ordering, and an `Ord` implementation cannot add one to a structural tuple type. Builtin integer `/` is a static error, as is integer `/=`. The diagnostic directs callers to `//` for a floor quotient or to `.to_float()` on both operands for floating true division. Integer `//` rounds the mathematical quotient toward negative infinity, and integer `%` is its paired remainder. Floating `//` and `%` use the corresponding CPython-compatible divmod correction. In both numeric domains, a nonzero remainder has the divisor's sign. Integer and floating `//` or `%` by zero, and floating `/` by zero, are runtime failures. See [Execution Model](/manual/execution-model#operators) for the complete runtime contract. Integer power is checked and preserves the exact operand type. Its exponent must be non-negative. `x ** 0` is `1`, including `0 ** 0`. A negative exponent visible in source is rejected with `AU2003`; a negative value discovered at runtime fails with `AU4001`. Overflow fails with `AU4002`. Floating power also requires equal operand types. It returns that type, reports a domain error for zero to a negative exponent or a negative finite base with a non-integral finite exponent, and reports a finite-input overflow with `AU4002`. Bitwise operators use each integer's fixed declared width. `&`, `|`, and `^` combine corresponding bits; `~` flips every bit. Binary operands must have the same exact concrete integer type. A shift's count has the same exact type as the shifted value and must satisfy `0 <= count < width`. Signed right shift is arithmetic and unsigned right shift is logical. Ordinary `<<` is checked and fails with `AU4002` when the mathematical result does not fit. `divmod(left, right)` evaluates both arguments once and returns the same floor quotient and remainder as `(left // right, left % right)` in one tuple. Both arguments have one exact integer or floating type, which is also the type of both tuple elements. A zero divisor fails with `AU4004`. `round(value)` returns an integer unchanged with its exact type. A `float32` or `float64` value rounds to `int64` using nearest-integer ties-to-even. Signed zero becomes integer zero. NaN, infinity, and a rounded result outside the `int64` range fail with `AU4002`. Aura has no digit-count overload. An unsuffixed integer literal may take the type of a `float32` or `float64` operand when the integer value is exactly representable in that floating type. Thus `7.5 // 2` is floating floor division and `-7.5 % 2` is floating remainder. This rule never converts a bound integer variable. An inexact literal is rejected; use an explicit floating spelling when rounding at the literal is intentional, or `.to_float()` for an intentional integer-to-`float64` conversion. Every integer type provides `.to_float() -> float64`. This conversion uses IEEE-754 round-to-nearest, ties-to-even and may lose integer precision: ```aura left: int64 = 9007199254740993 right: int64 = 2 ratio = left.to_float() / right.to_float() rounded = left.to_float() # 9007199254740992.0 ``` Use this method when rounding into the floating domain is intentional. An explicit integer `as float32` or `as float64` cast has the stricter exactness contract below. Every scalar integer type also provides exact-width `wrapping_add`, `wrapping_sub`, `wrapping_mul`, `saturating_add`, `saturating_sub`, and `saturating_mul`. The scalar methods `wrapping_shl`, `wrapping_shr`, `saturating_shl`, and `saturating_shr` take a count of the receiver's exact type and apply the same `0 <= count < width` rule as the shift operators. Wrapping left shift discards high bits; saturating left shift clamps to the integer type's bounds. Both named right-shift modes produce the same value as ordinary `>>` after validating the count. `Array[int32]` and `Array[int64]` provide the add/subtract/multiply named operations with a same-dtype scalar or exact-shape Array right operand. Ordinary arithmetic remains checked. Duration arithmetic operates on the exact signed nanosecond representation. Addition, subtraction, and multiplication are checked. `Duration // int64` rounds the signed nanosecond quotient toward negative infinity; a zero divisor fails with `AU4004`, and an unrepresentable result fails with `AU4002`. Duration equality and ordering compare that signed count. The language has no `Duration / int64`, `Duration % int64`, `Duration * float`, or unary `-Duration` rule. Use `Duration.ms(-1)` when a negative value is needed, and remember that negative values are not valid host waits. ## `len` And `str` `len(value)` and `str(value)` are maintained builtin functions, not syntax. `len(value)` delegates to the value's own `len()` member and produces `int64`. Every type that provides `len()` is accepted — `str`, `list[T]`, `dict[K, V]`, `set[T]`, and `Array[T]` — and a value without that member is rejected with `AU2002`. Their `len()` members also produce `int64`, so `len(value)` and `value.len()` have the same static type and value. `str.byte_len()` likewise produces `int64`, but counts UTF-8 bytes rather than the Unicode scalar values counted by `str.len()`. Neither `len` spelling changes ownership, because `len()` borrows its receiver. `str(value)` produces the same `str` that `print(value)` writes and that `f"{value}"` interpolates. It accepts any value the renderer accepts, so it is total over the maintained surface rather than restricted to scalars. ```aura hosts = ["alpha", "beta"] print(len(hosts)) print(str(len(hosts))) ``` Both names are builtin function names and, like `print` and `abs`, cannot be redefined by a program. ## Membership And Comparison Chains `value in container` and `value not in container` test membership and produce `bool`. The container decides both the member the test delegates to and the type the value must have: | Container | Tests | Delegates to | Value type | | --- | --- | --- | --- | | `list[T]` | element membership | `contains` | `T` | | `set[T]` | element membership | `contains` | `T` | | `dict[K, V]` | key membership | dictionary key lookup | `K` | | `str` | substring containment | `contains` | `str` | Any other container type is rejected with `AU2003`; a value whose type is not the container's element, key, or substring type is rejected with `AU2002`. An unsuffixed numeric literal on the value side may adopt the container's element or key type. `not in` is exactly the negation of `in`, not a separate member. Both operands are read. `in` never moves either operand, because the member it delegates to takes a shared borrow of the container and a shared borrow of the value. The value is evaluated before the container, matching source order. ```aura ports = [80, 443] print(443 in ports) print(8080 not in ports) print("/health" in "GET /health HTTP/1.1") ``` A comparison chain such as `low <= value < high` evaluates its operands left to right, evaluates each operand at most once, and stops at the first link that is `false`. The operands after that link are not evaluated. Every link must be a valid comparison of its two adjacent operands under the rules above, and the chain's result is `bool`. The same rule applies to tuple equality links. In `first == middle != last`, `middle` is evaluated once and reused by both adjacent links, while `last` is skipped when the first link is false. Tuple equality does not consume any evaluated chain operand. ```aura def in_range(value: int32, low: int32, high: int32) -> bool: return low <= value < high ``` Each operand of a chain is checked as if it were evaluated, even where short-circuiting would skip it at runtime. A chain therefore reports an ownership conflict that only one runtime path would reach, which is the same conservative rule the other branching forms use. ## Numeric Casts `expression as NumericType` performs an explicit numeric conversion. Supported target spellings are: ```text int int8 int16 int32 int64 int128 intsize uint8 uint16 uint32 uint64 uint128 uintsize float32 float64 ``` The target spelling `int` is exactly the same target type as `int64`. Casts are postfix and bind more tightly than arithmetic: ```aura whole = 7.9 as int32 widened = 3 as float64 total = left + right as int64 ``` The last example means `left + (right as int64)`. Use parentheses when the cast should apply to a larger expression. Non-numeric casts are not implemented. Conversion must satisfy the checked range and precision rules in [Types](/manual/types#casts). ## Postfix Expressions A primary expression may be followed by specialization, indexing, slicing, member access, calls, and numeric casts. Suffixes are applied from left to right; parenthesize a larger prefix or binary expression before applying a suffix to its result: ```aura users[0].name.clone() Result[int32, str].Ok(7) value as int64 ``` Postfix chains are limited by the maintained syntax-complexity budget described in [Grammar](/manual/grammar#syntactic-complexity-limits). ## Calls And Argument Binding A call has zero or more comma-separated arguments: ```aura print("hello") range(1, 4) process.run(["echo", "hi"], stdout=process.pipe(), group=true) replace(from="old", to="new") ``` Positional arguments come before named arguments. Static binding proceeds as follows: 1. Positional arguments fill parameters in declaration order. 2. A named argument fills the parameter with the same name. 3. A parameter cannot be filled more than once. 4. Unknown names and extra arguments are rejected. 5. Every omitted parameter must have a default. 6. Each argument must have the substituted parameter type. Arguments do not accept a trailing comma. A call may span physical lines while its `(` remains open. Every supplied argument is evaluated first in call-site source order before the next expression begins. A copy or move result is captured in its parameter slot; a borrow-mode selection is established without cloning and remains subject to the retained-borrow overlap rule. Later side effects cannot change an earlier captured argument. Defaults for omitted parameters are then evaluated afresh in declaration order. Binding a named value to its parameter slot never reorders evaluation, and no default runs for a supplied parameter. Mutable defaults are not shared process-global singletons. Call sites pass a value directly to bare, `own`, and `mut` parameters. Capability-prefixed argument forms are not expressions. The callee signature selects whether the argument receives shared access, ownership, or mutable access. A bare parameter is logically shared for every type; an explicit `own` parameter transfers ownership. See [Functions](/manual/functions#parameter-passing-modes) and [Ownership And Borrowing](/manual/ownership-and-borrowing). Calling a class name constructs the class. Calling an enum variant constructs that variant. Every class field and enum payload is an owned position. Constructor arguments follow the same positional-then-named rule and must supply every required field or payload exactly once. Named enum-variant arguments evaluate in their written source order; their captured results then bind by payload name to declaration-order slots. Slot binding never reorders the argument expressions. ## Explicit Generic Specialization Explicit type arguments use brackets: ```aura box = Box[int32](value=42) value = identity[int64](7) result = Result[int32, str].Ok(7) ``` Specialization and indexing share `[...]`. The parser treats brackets as specialization only when their contents form one or more type references and either: 1. `(` follows and the base is a name or member, or 2. `.` follows and the final target name begins with uppercase ASCII. Otherwise the brackets are indexing. Thus `Box[int32](...)` specializes, `Result[int32, str].Ok(...)` specializes, and `values[index]` indexes. A bare `Box[int32]` is not a general first-class specialized-type value. Type arguments do not accept a trailing comma. Generic inference, arity, and trait-bound rules are defined in [Static Semantics](/manual/static-semantics#contextual-inference). ## Member Access `object.member` selects a visible field, method, enum variant, module item, or maintained builtin member: ```aura point.x point.distance() Status.Ready io.Error.NotFound ``` An instance method call evaluates the receiver before its arguments. The method declaration determines whether the receiver is shared (`self`), consumed (`own self`), or mutable (`mut self`). A method without a receiver is associated and is called through its type. Visibility and resolution are static. Missing or private members are compile-time errors. ## Indexing `base[index]` evaluates the base, then the index. Direct indexing supports vectors, maps, and numeric Arrays under the maintained static rules: ```aura values[0] counts["ready"] matrix[1, 2] ``` List indices use the `int64` index domain. Non-negative indexes are zero-based; a negative index `i` is normalized once as `len + i`, so `values[-1]` selects the last element. The same rule applies to indexed assignment and the public List index methods. An index that remains outside the operation's valid range after normalization is not clamped. A contextually typed integer literal adopts `int64`; fixed-width `int8`, `int16`, `int32`, `uint8`, `uint16`, and `uint32` values widen losslessly only at an index-domain position. A dictionary index must have exactly the dictionary's key type. Direct reads are permitted only when the dictionary value type is copyable. For a non-copy value, use `get(key)` for an explicit cloned optional read only when the value type is clone-safe; use `remove(key)` to transfer any stored value, including one that contains `random.Rng`. A missing key in a direct read is a runtime `AU4003` lookup violation. An `Array[T]` index has one `int64` coordinate per runtime axis. Coordinates evaluate left to right and negative values normalize once against their own axis. A direct out-of-range coordinate is `AU4003`; a direct coordinate-count/rank mismatch is `AU4007`. `get(list[int64])` returns `None` for an invalid coordinate or rank. Mutable `set(list[int64], value)` returns `Some(old_value)` on success and traps on an invalid coordinate or rank. A direct list read of a copy element returns the value. Moving a non-copy List element by direct indexing is restricted; use `get(index)` when the intended operation is an explicit cloned/optional read and the element type is clone-safe. Use `pop(index)` to transfer a non-cloneable stored value. Index assignment is a statement target and is covered by [Statements](/manual/statements#bindings-and-assignment). Integer indexing on `str` is unavailable. Use a slice when selecting a substring, or the maintained string methods for whole-string operations. Exact UTF-8 conversion is available through `text.to_bytes()` and `str.from_bytes(bytes=...)`. ## Slicing `base[start:end]` selects the half-open range from start inclusive to end exclusive. Slicing is defined for `list[T]`, `str`, and `Array[T]`, and always returns a fresh owned value of the same type: middle = values[1:3] prefix = values[:2] suffix = values[-2:] all_values = values[:] scalars = "A🎉Z"[1:2] first_rows = matrix[0:2] An omitted start means zero and an omitted end means the source length. Equal endpoints produce an empty result. Every written endpoint uses the `int64` position domain. Fixed-width `int8`, `int16`, `int32`, `uint8`, `uint16`, and `uint32` values widen losslessly at that position. A negative endpoint `i` is normalized exactly once as `len + i`. After normalization, start and end must each be in `0..=len`, and start must not exceed end. Otherwise evaluation traps with `AU4003`. Aura deliberately differs from Python here: slice endpoints are **not clamped**. An endpoint that remains out of range after one normalization is a broken invariant, not a request for the nearest boundary. A reversed range is also an `AU4003` failure rather than an empty slice. A list slice copies Copy elements and clones non-Copy elements into a fresh owned list. The element type must therefore be clone-safe. A type containing `random.Rng`, an opaque FFI handle, or a capturing closure environment is rejected with `AU3007`, and a type containing a non-repeatable Task result right is rejected with `AU3009`. Generic slicing infers the same obligation for its element type. The source remains usable. String endpoints count Unicode scalar values, matching `str.len()`, not UTF-8 bytes or grapheme clusters. Locating scalar boundaries scans the source, so str slicing is O(n); the result is a newly allocated valid UTF-8 str. Integer `string[index]` remains unavailable. The base, written start, and written end are evaluated once from left to right. The selected non-Copy base remains retained through endpoint evaluation, so an endpoint may read it but cannot mutate or consume the overlapping source. No list, str, or Array slice is a place or a view. An Array slice applies the range only to axis zero, copies complete rows, and retains all later dimensions. Its first result dimension is `end - start`. It follows the same `int64`, one-time-negative-normalization, no-clamping, `AU4003`, owned-copy, no-step, and no-assignment rules. It is not a multidimensional slice or view. A second colon is reserved for future step syntax. `value[start:end:step]` and `value[::]` report `AU2005` with `slice steps are unavailable; use an explicit loop to select a stride`. Slice assignment and compound assignment report `AU2005` with `slice assignment is unavailable because slices are owned copies; mutate the source by index or build a new value`. ## Collection Literals Aura has list, set, and dictionary literals: ```aura values = [1, 2, 3] seen = {1, 2, 3} counts = {"ready": 2, "done": 1} ``` The first colon in a nonempty brace literal determines dictionary syntax. Without a colon, the literal is a set. Collection literal elements, keys, and values must have consistent types after contextual inference. Empty literals require expected types because they contain no values from which to infer element types: ```aura values: list[int32] = [] counts: dict[str, int32] = {} seen = set[int32]() ``` `{}` is a dictionary literal. An empty set uses `set[T]()`. Collection literals may span physical lines while their `[` or `{` remains open, but they do not accept trailing commas. Lists and sets evaluate elements in source order. Dictionaries evaluate each key before its value and entries in source order. If two evaluated dictionary keys are equal, the later value replaces the earlier value while the key retains its first insertion position. ## Comprehensions A comprehension is an eager collection expression: doubled = [value * 2 for value in values] visible = {value for value in values if value >= 0} by_id = {item.id: item for item in items} One or more `for` clauses are required. A clause may have multiple `if` filters and may be followed by another clause: coordinates = [ (row, column) for row in rows if row >= 0 for column in columns if column >= 0 ] The syntax places the output expression first, but runtime order starts at the first iterable. Its target is bound, its filters run left to right, and then the next iterable is selected. At the innermost surviving combination the output runs. Nested traversal is outer-major: every surviving inner item for one outer target is produced before the next outer item. Dictionary output evaluates and captures the key before evaluating the value. Each clause uses ordinary bare-loop iteration. List and set inputs are shared and frozen, Range yields copy values, `enumerate(...)` and `zip(...)` retain their loop contracts, and Queue retains its special receive semantics in which the handle is copied and each target arrives owned. A comprehension does not accept `mut` or `own` before its source. The result is a newly owned `list[T]`, `set[T]`, or `dict[K, V]`, never a view or lazy iterator. Result insertion owns non-Copy values. A shared non-Copy source element must be explicitly cloned when clone-safe; Queue-received owned values may move directly. Targets are progressively scoped over their filters, later clauses, and the output, then disappear when the expression ends. Lambdas reached inside a comprehension use the ordinary ADR-0037 capture contract. For example, a compiler-known callback can capture a Copy value while it is called from an element expression: shifted_rows = [ row.map(lambda value: value + offset) for row in rows ] The lambda is created only for a reached element. Shared non-Copy capability capture remains rejected, and a capturing closure cannot itself become a stored comprehension element. See [Closures](/manual/closures). Generator expressions remain unavailable. `(value for value in values)` and `consume(value for value in values)` report `AU2005` and direct the author to an eager owned list comprehension or an explicit loop. ## F-Strings An f-string produces an owned `str` and evaluates interpolations from left to right. Each interpolation is rendered to `str` immediately, before the next interpolation begins: ```aura name = "aura" count = 3 message = f"{name}: {count}" report = f"{name:<12s} {count:>8,d}" ``` Interpolation contents are ordinary expressions. A top-level colon introduces a statically checked format specification with fill, alignment, sign, width, decimal grouping, precision, and a closed set of string and numeric type codes. For numeric values, a width beginning with `0` pads after the sign, matching Python's `09.3f` shorthand. Formatting uses the interpolation value's exact static numeric width, so a `float32` is formatted from its binary32 value. String spelling, escapes, literal braces, and the complete format grammar are defined by [Lexical Structure](/manual/lexical-structure#f-strings). ## Match Expressions `match` may produce a value. Its scrutinee is evaluated exactly once. Arms are considered in source order, and only the first matching arm expression is evaluated. An arm contains exactly one expression. It may be inline: ```aura label = match code: case 0: "ok" case _: "other" ``` Or the expression may be placed on one indented following line: ```aura label = match code: case 0: "ok" case _: "other" ``` The indented form is still one expression, not a suite of statements. Every arm must produce one compatible result type, and the match must be exhaustive under [Enums And Pattern Matching](/manual/enums-and-match#exhaustiveness-and-wildcards). A complete match expression may appear anywhere an expression is expected, including an initializer, return value, call argument, collection element, or grouping. Inside an enclosing delimiter, its required arm layout forms a layout island rather than being suppressed by ordinary continuation. The exact forms are defined in [Grammar](/manual/grammar#match-expressions). Use `match value` to inspect without consuming a non-copy scrutinee, or `match mut value` when an arm must mutate through payload bindings. ## `try` `try expression` operates on `Result[T, E]`: ```aura def parse_value(text: str) -> Result[int32, str]: value = try parse_int32(text) return Result.Ok(value) ``` The operand is evaluated once: - `Result.Ok(value)` makes the `try` expression produce `value` - `Result.Err(error)` returns immediately from the enclosing function The enclosing function must return a compatible `Result`. When the error types differ, one applicable `From[SourceError] for TargetError` implementation may convert the error. Early return runs active `with` cleanups. See [Execution Model](/manual/execution-model#try). ## Enum Construction Enum constructors use the enum or specialized enum name followed by the variant: ```aura result: Result[int32, str] = Result.Ok(7) missing: Option[str] = Option.None ready = Status.Ready(count=3) ``` The variant must exist and receive exactly its declared payload shape. Generic enum arguments may be inferred from an expected type or payloads; explicit specialization is required when inference cannot resolve every type parameter. Bare builtin variants such as `Ok`, `Err`, `Some`, or `None` are accepted only where the expected enum identity is unambiguous. Qualified construction is the preferred reference and book style. ## Function Values And Indirect Calls A named module-level function may appear as an expression. Its value is a copy code pointer with type such as `def(T1, mut T2, own T3) -> R`, where bare parameters are shared. Calling that expression uses the ordinary call production and preserves the named function's parameter capabilities. Explicit generic specialization such as `show[int32]` fixes one concrete function value before storage or invocation. Function-valued variables, parameters, fields, and collection elements are ordinary primary/postfix expressions. A value with one statically known source declaration keeps that declaration's parameter names and defaults for indirect calls. A control-flow selection also keeps these extras when all candidates agree on their names and default availability; each omitted argument evaluates the selected target's own default expression. Conflicting reassignment, structural function returns, class-field loads, and mutable-collection loads have only the structural function type and therefore require the complete positional argument list. Storage preserves each parameter's bare shared, `mut`, or `own` ABI capability. Contextually typed `lambda parameters: expression` values use the same callable contract and may capture owned outer locals by value. See [Closures](/manual/closures). Instance and associated method values and trait-object interactions remain unavailable. ## Fixed-Width Numeric Example This program packs three bytes into a `uint32`, extracts them again, and uses the numeric helpers that return more than one value: ```aura def pack_rgb(red: uint32, green: uint32, blue: uint32) -> uint32: sixteen: uint32 = 16 eight: uint32 = 8 return (red << sixteen) | (green << eight) | blue def main() -> int32: red: uint32 = 0xFF green: uint32 = 0x80 blue: uint32 = 0b0000_0000 packed = pack_rgb(red, green, blue) mask: uint32 = 0xFF eight: uint32 = 8 sixteen: uint32 = 16 print(packed) print((packed >> sixteen) & mask) print((packed >> eight) & mask) print(packed & mask) print(3 ** 4) print(round(2.5)) quotient, remainder = divmod(-17, 5) print(quotient) print(remainder) return 0 ``` The program prints `16744448`, `255`, `128`, `0`, `81`, `2`, `-4`, and `3`, one value per line. ## Forms Not Implemented Aura 0.3 expressions do not include generator expressions, method values, assignment expressions, call-site capability annotations, non-numeric casts, or ordinary trailing commas. Lambdas are expression-bodied and contextually typed; they do not add statement-bodied or implicitly reference-capturing forms. The required singleton-tuple comma is the one tuple-specific exception. If a form is absent from [Grammar](/manual/grammar), it is not part of the implemented expression language. ## Grammar Primary, postfix, power, unary, multiplicative, additive, shift, bitwise, comparison, Boolean, conditional, `match`, `try`, lambda, collection literal/comprehension, constructor, and f-string expression productions are normative in [Grammar](/manual/grammar). The comparison production covers equality, ordering, and membership at one level and admits a chain of two or more operators. The precedence and associativity table above resolves every accepted operator sequence. A spelling absent from those productions is not accepted as an implicit extension. ## Typing Rules Each expression receives exactly one static type. Calls, constructors, operators, indexing, member access, collections, matches, casts, and `try` must satisfy the specific rules above after generic substitution. Context may type a literal, including an exactly representable integer literal in a floating context, but never converts a bound variable. Branching expressions require a single result type on every arm. List and set comprehension output expressions determine `T`; dictionary key and value expressions determine `K` and `V`. An expected result specialization provides context before inference. Filters require exact `bool`, and every source uses the static iterable rules of a bare statement loop. ## Runtime Semantics Operands and call arguments evaluate left to right, with each copy or move argument result captured before the next argument's side effects. Named enum arguments evaluate in source order and then bind to declaration-order payload slots. `and` and `or` short circuit. Conditional expressions evaluate the condition first and exactly one selected arm. A membership test evaluates its value before its container. A comparison chain evaluates its operands left to right, evaluates each at most once, and stops at its first `false` link. A binary power, shift, or bitwise expression evaluates its left operand once before evaluating its right operand once. A compound form selects its target place once and writes only after the operation succeeds. A member receiver is evaluated before arguments; an index base is evaluated before its index; a slice base is evaluated before its written start and end; collection entries preserve source order; a match scrutinee evaluates once; and each f-string interpolation renders immediately before the next begins. `try` either yields an `Ok` payload or returns the `Err` from the enclosing function after required cleanup. A comprehension allocates one result, evaluates every reached source once for its current outer combination, applies filters left to right, and then evaluates its output. Nested clauses are outer-major. Dictionary key evaluation precedes value evaluation. A trap or `try` propagation drops the partial result. ## Ownership And Evaluation Order Evaluation copies copy values and moves non-copy values only when the static context consumes them. Bare parameters grant logical shared access; `own` parameters and consuming receivers move, while `mut` parameters grant exclusive mutable access. Non-copy indexed reads report `AU3005` and require the safe method surface instead of an implicit copy. `in` and `not in` read both operands and move neither. Equality and inequality themselves also read both resulting operands and move neither; this includes structural tuple equality. Evaluation inside an operand retains its ordinary ownership effects. A comparison chain checks every operand as if it were evaluated, even where short-circuiting would skip it. Binary left operands, index bases, method receivers, and indexed-assignment targets retain their non-copy borrow through later inputs. An overlapping mutable borrow or consumption is rejected with `AU3002`, and no hidden clone repairs the invalid expression. Comprehension targets use progressive child scopes and do not leak. Active shared sources stay borrowed and frozen through downstream filters, clauses, and output evaluation. Insertion into the result is owned, so copy, move, explicit-clone, loop-carried-move, and ADR-0037 capture checks apply exactly as they do in the equivalent nested bare loops. ## Diagnostics `AU1101` means invalid expression syntax, including malformed comprehension clauses and forbidden comprehension `mut`/`own` modifiers. `AU2001` means an unresolved name or member. `AU2002` means a type, constructor-payload, match-result, or index-type mismatch. `AU2003` means an unsupported unary, binary, compound, membership, or cast operator. `AU2004` means call or constructor argument binding failed. `AU2005` means an unsupported syntax or expression feature, including the exact generator-expression guidance recorded above. `AU2999` means an expression rejection without a narrower compile-time code. `AU3001` means use of a moved value; `AU3002` means a borrow conflict, including a later mutable borrow or consumption overlapping a retained non-copy binary operand, index base, method receiver, or indexed-assignment target; `AU3003` means an immutable place was used mutably; and `AU3004` means an invalid ownership mode. `AU3005` means a direct indexed read would copy a non-copy stored value, and `AU3006` means indexed compound assignment would do the same during its read-modify-write step. `AU3007` and `AU3009` reject a list slice whose owned result would duplicate, respectively, non-cloneable state or a single-consumer Task observation right. `AU4003` reports an invalid normalized slice endpoint or reversed range. Reserved slice steps and slice assignment use `AU2005`. At runtime, `AU4001` means a general expression trap, `AU4002` means arithmetic overflow, underflow, range, or conversion-exactness failure, `AU4003` means a bounds or lookup violation, `AU4004` means a zero divisor, and `AU4005` means a trapping resource or I/O failure propagated by a call expression. For numeric operations, `AU4001` includes a runtime negative integer exponent and floating power domain errors. `AU4002` includes integer power overflow, invalid shift counts, and checked-left-shift overflow. ## Backend Support All expression forms marked implemented lower to MIR and are supported by the direct native backend. The forced backend-parity matrix verifies their observable results and primary traps. Compiler analysis and LSP diagnostics are produced before backend selection. ## Limits And Implementation-Defined Behavior The parser caps expression nesting and operator chains at 128. Physical lines continue only while a source delimiter remains open; backslashes and multiline string/f-string literals do not continue them. Ordinary trailing commas are unavailable; `(value,)` is the required singleton tuple spelling. Collection and string resource caps are documented by their feature pages. Comprehensions are eager, have no `mut`/`own` source form, and do not provide early exit, lazy resumption, or a user-defined iterable protocol; use an explicit loop when those properties are required. Floating values follow the specified Aura operations and shortest-round-trip printing; no backend may substitute a different expression result as an implementation-defined choice. ## Status The expression forms defined positively in this chapter are implemented. Delimiter continuation is accepted under ADR-0025 and does not add a new expression AST form. Conditional expressions are accepted under ADR-0027, and membership operators plus comparison chains are accepted under ADR-0028. The minimal tuple surface and its Batch 3 B3.0-c equality amendment are Accepted under ADR-0026. Capture-free named function values, indirect calls, and contextually typed by-value expression closures are implemented. Method values, generator expressions, assignment expressions, nonnumeric casts, and call-site capability modifiers are unavailable. Eager owned list, set, and dictionary comprehensions are implemented under Accepted ADR-0039. Integer base spellings, fixed-width bitwise operations, and shifts are Accepted under ADR-0047. Power, `round`, and `divmod` are Accepted under ADR-0048. ## Source: docs/manual/ffi.md # Foreign Function Interface (FFI) v0 Aura FFI v0 calls a deliberately small subset of the platform C ABI. It is an unsafe package capability for binding trusted, already-loaded native symbols; it is not a general dynamic-library, pointer, or callback system. Every source file that declares an extern function or opaque handle must belong to an Aura package whose manifest explicitly opts in. Compiler embedders must therefore use the public path-based checking, lowering, or execution APIs for FFI source; source-only APIs cannot establish manifest authorization: ```toml [package] name = "native_binding" version = "0.1.0" edition = "2026" allow_ffi = true ``` A standalone `.au` file outside a package cannot declare FFI. If any dependency in the package graph enables FFI, the root package must also set `allow_ffi = true` and list every reachable FFI-enabled dependency by package name, including transitive dependencies: ```toml [package] name = "app" version = "0.1.0" edition = "2026" allow_ffi = true [dependencies] native_binding = { path = "../native_binding" } [ffi] dependencies = ["native_binding"] ``` The report is exact. Duplicate, unknown, unreachable, non-FFI, and root-package entries are rejected. An FFI-enabled dependency must opt itself in as well. The report grants visibility, not trust: the root application remains responsible for reviewing the declarations and the native code they invoke. ## Grammar Only bodyless C declarations are accepted: ```aura public extern "C" opaque class ProcessHandle public extern "C" def getpid() -> int32 extern "C" def inspect(label: str, data: list[uint8]) -> uint64 extern "C" def update(data: mut list[uint8]) -> None extern "C" def close(handle: own ProcessHandle) -> None def main() -> int32: print(getpid() > 0) return 0 ``` `public` has its ordinary module-visibility meaning. A public declaration may be imported from another module; a private declaration is local to its defining module. The Aura declaration name is the C symbol name. FFI v0 has no source spelling for a separate link name, library name, calling convention, symbol version, or variadic tail. The ABI string must be exactly `"C"`. Every extern function must spell an explicit `-> Type`; use `-> None` for a C function that returns no value. Extern functions have no Aura body, type parameters, receiver, defaults, or trailing colon. An opaque declaration uses `extern "C" opaque class Name` and has no fields, methods, body, or type parameters. Raw pointer syntax, callback types, and `...` variadics are reserved and rejected with teaching diagnostics. Aura code cannot construct an opaque handle or use an extern declaration as a first-class function value; externs are direct-call-only. ## Typing Rules The accepted scalar surface is fixed: | Aura type | C ABI value | | --- | --- | | `bool` | one-byte boolean; returns must be exactly `0` or `1` | | `int8`, `int16`, `int32`, `int64` | signed 8-, 16-, 32-, or 64-bit integer | | `uint8`, `uint16`, `uint32`, `uint64` | unsigned 8-, 16-, 32-, or 64-bit integer | | `float32`, `float64` | IEEE-754 binary32 or binary64 | | `int` | the exact `int64` alias; `int64` is preferred in ABI declarations | | `None` | return-only void result | Scalar parameters must be bare because their bits are passed by value. `int128`, `uint128`, `intsize`, `uintsize`, `Duration`, tuples, user classes, enums, generic types, and arbitrary collection types do not have an FFI v0 representation. The three pointer-length parameter forms are: | Aura parameter | C parameters in order | Contract | | --- | --- | --- | | `text: str` | `const uint8_t *`, `size_t` | UTF-8 bytes; not NUL-terminated | | `data: list[uint8]` | `const uint8_t *`, `size_t` | read-only bytes | | `data: mut list[uint8]` | `uint8_t *`, `size_t` | fixed-length writable bytes | The pointer is valid only during the synchronous foreign call. The native callee must not retain it. An empty str or byte view passes a null pointer and length zero; a non-empty view passes a valid pointer and its exact byte length. A mutable byte view uses a same-length scratch buffer: Aura copies the list's initial bytes in, then copies exactly that length back after the foreign function returns. The writeback happens even if subsequent result validation reports an Aura error. Its length and capacity cannot be changed by foreign code. `own str`, `mut str`, and `own list[uint8]` are rejected. Text or byte views cannot be returned because v0 has no foreign allocator or lifetime contract. An opaque handle is one non-null foreign pointer with no Aura-visible layout. A bare handle parameter shares the pointer for that call and retains the Aura handle. An `own Handle` parameter consumes it, normally for a foreign close/free operation. `mut Handle` is reserved. Opaque handles are non-Copy, non-cloneable, and never `Transfer`, so they cannot cross a task or Queue boundary. A returned null pointer is an Aura runtime failure; nullable opaque handles are not part of FFI v0. This non-cloneability is structural through tuples, collections, user classes, enum payloads, and generic specializations. `.clone()` and clone-producing collection observations such as `get`, projected reads, and `filter` are rejected whenever the duplicated value contains an opaque handle. Consuming transfer operations such as `pop`, `remove`, and replacement remain allowed. Equality and inequality are also rejected for an opaque handle or any value that structurally contains one. FFI v0 deliberately does not expose foreign addresses or assume that address identity is the native API's semantic identity; a binding should expose a stable scalar or str identifier when callers need to compare foreign objects. Arithmetic and ordering operators on the handle itself are rejected with dedicated diagnostics: raw pointer arithmetic and foreign-address ordering are not language capabilities. A binding must expose reviewed extern operations or stable scalar/str keys instead. ## Runtime Semantics The runtime resolves the declaration name against the process-global symbol table at the moment of the call. FFI v0 does not open a dynamic library or search a user-specified path. The symbol must already be visible to the process, commonly because it comes from the platform C runtime or was linked into the executable. Arguments are evaluated left-to-right under ordinary Aura call rules, then marshalled to the C ABI. A missing symbol or marshalling failure prevents the foreign call. After the function returns, Aura writes back each mutable same-length byte scratch buffer and then validates representable results, including canonical booleans and non-null opaque handles. Foreign side effects and completed byte writeback cannot be rolled back by a later return-value validation failure. Every foreign call is synchronous. It occupies the current Aura worker until the native function returns; it is not moved to the blocking I/O pool and does not create an implicit scheduling point. A long or blocking native call can therefore delay other tasks pinned to that worker. FFI declarations are unsafe contracts. Aura cannot verify that a process-global symbol exists at compile time or that its real C signature, pointer retention, allocation, thread-safety, and mutation behavior match the declaration. ## Ownership And Evaluation Order Ordinary scalar arguments are copied into ABI slots. Bare `str`, `list[uint8]`, and opaque-handle arguments remain owned by the caller and are available after the call. `mut list[uint8]` requires an exclusive mutable place and exposes in-place byte updates after return. An `own` opaque-handle argument moves the handle before the call and cannot be used afterward. The declaration's capability is exact; no implicit clone, ownership conversion, or pointer-lifetime extension is inserted. Because a process call may have irreversible external effects, evaluating a later argument or validating the result does not undo earlier evaluation, the foreign call, or foreign writes. Opaque handles have no automatic foreign destructor. A binding package must declare and call the appropriate consuming C function. Dropping an unconsumed handle discards only Aura's wrapper and may leak the foreign resource if the native API requires explicit destruction. Printing, f-string interpolation, or `str(...)` renders a handle as ``, using its canonical Aura type name. The pointer address is never part of source-visible rendering or diagnostics. ## Diagnostics - `AU1101` rejects malformed extern/opaque syntax and gives dedicated guidance for a foreign body, defaults, type parameters, callbacks, variadics, and raw-pointer spelling recognized by the parser. - `AU2002` rejects types outside the fixed scalar, view, and opaque-handle table, including returned `str` or `list[uint8]` views. - `AU2003` rejects equality or inequality on an opaque handle or a value that structurally contains one. - `AU2005` rejects reserved FFI forms, constructing an opaque handle, and callback or raw-pointer contracts that reach static checking. - `AU2999` reports missing package opt-in, an inaccurate root dependency report, standalone FFI source, a direct-call-only extern used as a value, or another FFI policy violation without a narrower code. - `AU3001` reports use of an opaque handle after an `own` extern call. - `AU3004` reports an invalid scalar/view/handle capability. - `AU3008` rejects an opaque handle at a task or Queue `Transfer` boundary. - `AU4001` reports a non-canonical C boolean result: the returned byte was neither `0` nor `1`. - `AU4005` reports a recoverable runtime boundary failure such as a missing process-global symbol, null opaque-handle result, or runtime marshalling failure. Aura panics and traps never unwind through a foreign frame: pre-call failures stop before entry and post-call failures are raised after return. Conversely, FFI v0 cannot catch or translate a native abort, signal, memory fault, or foreign unwind. Foreign code must not unwind across the C ABI. Such a native failure may terminate the process rather than produce an Aura diagnostic. Out-of-bounds writes, a mismatched C signature, or retaining a temporary view is outside Aura's memory-safety guarantees. ## Backend Support The MIR and direct native backends share one validated ABI description and one host-call engine. They must agree on argument layout, ownership, mutable-view writeback, results, and Aura diagnostics. The maintained `examples/packages/ffi_getpid` package and FFI acceptance test run the same `getpid` declaration on both backends. Process-global symbol lookup is currently implemented on Unix-family hosts. On another host, a call fails with the documented runtime boundary diagnostic rather than silently selecting a different ABI. The source declaration still must name the host's actual C symbol. ## Limits And Implementation-Defined Behavior FFI v0 does not load libraries, select symbols by link name, define C structs or unions, pass enums, allocate foreign memory, expose pointer arithmetic, return views, represent nullable handles, accept callbacks or variadics, or offer asynchronous foreign calls. C ABI layout outside the explicit table is not inferred. Symbol availability and behavior are host-defined. `size_t`, pointer layout, and process symbol visibility follow the target platform. The fixed-width integer and floating contracts remain exact. A declaration that lies about the real native signature has undefined foreign behavior and may corrupt or terminate the process; no backend can make such a declaration safe. ## Status FFI v0, its package opt-in and root dependency report, bodyless `extern "C"` functions, opaque handles, fixed-width scalars, pointer-length views, and Unix process-global lookup are implemented in Aura 0.3. Callbacks, raw pointers, variadics, returned views, nullable handles, explicit library loading/link configuration, and foreign aggregate layout are reserved or unavailable. They are not inferred from current syntax. ## Source: docs/manual/filesystem.md # Filesystem Module The `fs` module provides one-shot helpers for common file operations and an owned `fs.File` resource for handle-based workflows. ```aura import fs import io ``` Filesystem APIs return `Result[..., io.Error]` except `fs.exists(...)`, which returns a plain `bool`. ## One-Shot Functions | API | Signature | Contract | | --- | --- | --- | | `fs.exists` | `exists(path: str) -> bool` | Returns `true` when `path` exists. Errors are collapsed to `false`. | | `fs.read_to_string` | `read_to_string(path: str) -> Result[str, io.Error]` | Reads a UTF-8 file into a `str`. Reads are capped at 256 MiB. | | `fs.read_bytes` | `read_bytes(path: str) -> Result[list[uint8], io.Error]` | Reads a file into raw bytes. Reads are capped at 256 MiB. | | `fs.write_string` | `write_string(path: str, text: str) -> Result[None, io.Error]` | Creates or replaces `path` with `text`. | | `fs.write_bytes` | `write_bytes(path: str, bytes: list[uint8]) -> Result[None, io.Error]` | Creates or replaces `path` with raw bytes. Empty byte vectors are allowed. | | `fs.append_string` | `append_string(path: str, text: str) -> Result[None, io.Error]` | Creates or opens `path` and appends `text`. | | `fs.append_bytes` | `append_bytes(path: str, bytes: list[uint8]) -> Result[None, io.Error]` | Creates or opens `path` and appends bytes. | | `fs.create_dir` | `create_dir(path: str) -> Result[None, io.Error]` | Creates one directory. Parent directories must already exist. | | `fs.read_dir` | `read_dir(path: str) -> Result[list[str], io.Error]` | Returns the directory's immediate entry names in sorted order. Names that are not valid UTF-8 are converted lossily. | | `fs.remove_file` | `remove_file(path: str) -> Result[None, io.Error]` | Removes a file. | | `fs.open` | `open(path: str) -> Result[fs.File, io.Error]` | Opens a file for reading. | | `fs.create` | `create(path: str) -> Result[fs.File, io.Error]` | Creates or truncates a file for writing. | | `fs.append` | `append(path: str) -> Result[fs.File, io.Error]` | Opens a file for appending, creating it if needed. | The read cap is part of the API contract and also applies to `fs.File.read_all()` and `fs.File.read_bytes()`. Aura 0.3 has no chunked file-read API, so a program that must process a larger file needs a host-side helper or must split the data before reading it through Aura. `fs.read_dir` reports failure to open the directory, but the current implementation silently skips an individual entry whose metadata/read operation fails after opening. Code that requires a complete audited directory snapshot must validate results through a host helper until that defect is fixed. ## fs.File `fs.File` is an owned resource. Use `with` for deterministic cleanup: ```aura def show_file() -> Result[None, io.Error]: with file = try fs.open("data.txt"): text = try file.read_all() print(text) return Result.Ok(None) ``` | API | Signature | Contract | | --- | --- | --- | | `read_all` | `read_all() -> Result[str, io.Error]` | Reads remaining file contents as strict UTF-8 text, capped at 256 MiB. | | `read_bytes` | `read_bytes() -> Result[list[uint8], io.Error]` | Reads remaining file contents as raw bytes, capped at 256 MiB. | | `write_all` | `write_all(text: str) -> Result[None, io.Error]` | Writes all of `text` to the file. | | `write_bytes` | `write_bytes(bytes: list[uint8]) -> Result[None, io.Error]` | Writes all raw bytes to the file. | | `flush` | `flush() -> Result[None, io.Error]` | Flushes pending writes to the operating system. | | `close` | `close() -> None` | Closes the handle. Further use is invalid. | ## Text And Bytes Use text helpers when the file is known to be UTF-8: ```aura def read_config() -> Result[str, io.Error]: text = try fs.read_to_string("config.txt") return Result.Ok(text) ``` Use byte helpers for binary data or unknown encodings: ```aura def read_image_size() -> Result[int64, io.Error]: bytes = try fs.read_bytes("image.bin") return Result.Ok(bytes.len()) ``` The same distinction exists on `fs.File`. Raw file bytes can be validated as UTF-8, encoded as canonical hex/base64, or hashed through the separate [Bytes, Text Codecs, And SHA-256](/manual/bytes) surface. Those conversions do not change the filesystem API's typed `io.Error` boundary. All text reads decode UTF-8 strictly and return `io.Error.InvalidData` for invalid input. A read that exceeds 256 MiB also returns `InvalidData`. File writes are not transactional: after cancellation or a host failure, the caller must not assume that no bytes were written. ## Example: Append A Line ```aura import fs import io def append_line(path: str, line: str) -> Result[None, io.Error]: with file = try fs.append(path): try file.write_all(line) try file.write_all("\n") try file.flush() return Result.Ok(None) ``` ## Error Handling Filesystem errors use `io.Error`. Match variants when the program has different policy for different cases: ```aura match fs.read_to_string("config.txt"): case Result.Ok(text): print(text) case Result.Err(io.Error.NotFound): print("using defaults") case Result.Err(error): print(error) ``` ## Grammar The filesystem module adds no source-language grammar. Programs use ordinary imports, calls, member calls, `Result`, `try`, `match`, and `with`. A `with name = expression:` binding follows the general resource-scope grammar and invokes the resource's `close()` operation on every scope exit. Paths are `str` values, not path literals or a distinct path type. Text and byte operations are selected by different function names; no encoding annotation changes a byte operation into a text operation. ## Typing Rules The signatures in the one-shot and `fs.File` tables are normative. All operations except `fs.exists` return `Result`; failure values are `io.Error`. Text reads produce `str`, binary reads produce `list[uint8]`, and open/create/append produce the non-copy resource type `fs.File`. `fs.File.write_all`, `write_bytes`, `flush`, and `close` require a mutable receiver place. `read_all` and `read_bytes` are callable through a shared receiver even though the host file cursor advances. Calling a method on the wrong type, supplying a wrong argument type, or ignoring the `Result` where a `try` expression requires it is checked by the ordinary static rules. ## Runtime Semantics One-shot operations perform the host filesystem action named in the table. `write_string` and `write_bytes` create or replace a file; append operations create when absent and otherwise append. `create_dir` creates only one directory. `read_dir` returns sorted immediate entry names. `fs.exists` deliberately collapses metadata errors to `false`. Text is strict UTF-8. Invalid text and reads over 256 MiB return `io.Error.InvalidData`; byte reads preserve bytes. A file handle maintains an operating-system cursor, so successive reads observe and advance the same underlying position. Writes and appends are observable as they occur and are not transactional. Normal host failures return the closest documented `io.Error` variant. ## Ownership And Evaluation Order Call arguments are evaluated left to right. Path, text, and byte-list arguments are shared for the duration of the operation and are not retained by the filesystem API. Successful reads return fresh owned values. `fs.File` is non-copy: assigning or passing it by ownership moves the handle, and later use of the moved binding is rejected. `with` owns the bound resource for the lexical scope and closes it exactly once on normal exit, early return, loop transfer, or error propagation. Cleanup runs after the body and does not undo completed host I/O. Shared read methods use interior host state for the file cursor; mutating write, flush, and close methods require a mutable receiver binding. ## Diagnostics Unknown filesystem members use `AU2001`, wrong types use `AU2002`, and invalid argument binding uses `AU2004`. Use after moving a file handle uses `AU3001`; conflicting borrows use `AU3002`; invoking a mutating file method through an immutable place uses `AU3003`; remaining static rejections use `AU2999`. Documented filesystem failures are typed outcomes, not language traps: they return `Result.Err(io.Error)`. In particular, missing files, permission failures, invalid UTF-8, closed handles, and the 256 MiB cap must be handled through `Result`. A compiler or runtime invariant failure outside that typed boundary uses the general diagnostic categories in [Diagnostics](/manual/diagnostics), including `AU4005` for an uncaught resource/I/O trap. ## Backend Support The complete API on this page is implemented by the MIR runtime and direct native backend. Strict UTF-8 decoding, the read cap, sorted directory results, error variants, owned-resource behavior, and cleanup are backend-parity requirements. Host filesystem results can differ by operating system and environment. Such differences do not permit a backend to change the Aura return type, discard a successful byte value, or replace a documented typed `io.Error` with a backend-specific value. ## Limits And Implementation-Defined Behavior Each one-shot read and each `fs.File` whole-file read is capped at 256 MiB of remaining content. Aura 0.3 has no chunked file-reading API, recursive directory operation, transactional write, atomic replace helper, memory mapping, filesystem watcher, permission API, or symlink-specific API. Host paths, permissions, case sensitivity, separators, and symlink traversal follow the host. After opening a directory, an individual entry that fails during enumeration is currently skipped; only failure to open the directory is returned. Non-Unicode entry names are converted lossily. Partial writes and externally visible side effects may remain after a host failure or task cancellation. ## Status The one-shot functions, `fs.File` methods, typed errors, deterministic cleanup, strict text/byte distinction, and limits documented here are implemented and maintained in Aura 0.3. The fixed 256 MiB whole-read policy is accepted under ADR-0018. The skipped-entry behavior is a documented current defect, not a guarantee that callers should rely on. Aura 0.3 has no chunked or asynchronous file access, transactional operations, richer metadata, or cross-platform path abstraction API. ## Source: docs/manual/functions.md # Functions Functions are module-level declarations introduced by `def`. Their callable contracts fix the parameter names, parameter passing modes, parameter types, generic parameters and bounds, return behavior, and any inferred clone-safety obligations used at every call site. ```aura def add(left: int32, right: int32) -> int32: return left + right ``` The complete declaration grammar is in [Grammar](/manual/grammar#functions-methods-and-parameters). This chapter defines the corresponding static and execution rules. ## Signatures And Return Types Every ordinary parameter has an explicit type. A return annotation is optional; omitting it is exactly equivalent to `-> None`. ```aura def square(value: int32) -> int32: return value * value def log(message: str): print(message) ``` `return expression` must have exactly the declared return type. `return` without an expression has type `None` and is valid only in a `None`-returning function. Reaching the end of a `None` function returns `None` implicitly. A function may return one fixed structural tuple. Both the return annotation and value use parentheses, and a comma distinguishes a singleton tuple from grouping: `def locate() -> (str, int64):` may `return ("north", 7)`. The caller may bind the result with `name, number = locate()`. Tuple return copy/move behavior follows the complete tuple's recursive classification; see [Tuples](/manual/tuples). A function with any other return type must return on every statically reachable path: ```aura def classify(value: int32) -> str: if value < 0: return "negative" return "non-negative" ``` There is no implicit numeric widening or general return coercion. A bare `None` in an argument or return position adopts an expected `Option[T]`, and grouping does not discard that context. Other contextual literal typing and the complete symmetric option-equality rule follow [Static Semantics](/manual/static-semantics#contextual-inference). Function names share the module item namespace with classes, enums, traits, and imports. Duplicate items and attempts to redefine maintained builtin function names are rejected. Ordinary parameter names must be unique. A method parameter also cannot be named `self` when the method has a receiver. In a method declaration, `self: Type` is rejected rather than treated as an ordinary first parameter; receivers use `self`, `own self`, or `mut self`. See [Names And Scopes](/manual/names-and-scopes) for the complete namespace rules. A function is private to its defining module by default. Prefix the declaration with `public` to make it importable from another module: ```aura public def double(value: int32) -> int32: return value * 2 ``` Visibility controls name access, not the ownership or type rules of the signature. ## Parameter Passing Modes The passing mode is part of the function signature: | Declaration | Contract at the call boundary | | --- | --- | | `value: T` | Shared access. An implementation may pass copy bits directly without changing the source contract. | | `value: own T` | Owned argument. A move value is consumed; a copy value is duplicated. | | `value: mut T` | Exclusive mutable borrow. The argument must be a mutable place. | ```aura def consume(name: own str): print(name) def length(text: str) -> int64: return text.len() def push_name(names: mut list[str], name: own str): names.append(name) ``` The modifier is written in the declaration after the colon. Calls pass the expression directly; Aura has no call-site capability prefix: ```aura mut names = list[str]() push_name(names, "Ada") ``` Arguments must have exactly the substituted parameter type. A call retains each non-copy method receiver and non-copy argument access through every later sibling expression. A later sibling, including an access nested inside another call or expression, may shared-borrow the same place, but it may not mutably borrow or consume an overlapping place; violations report `AU3002`. The ownership and place rules are specified in [Ownership And Borrowing](/manual/ownership-and-borrowing). The bare rule is resolved where the function is declared, not independently at each call. An unconstrained generic `value: T` therefore resolves to a shared borrow because `T` is not known copyable there. That choice is **declaration-stable**: specializing the function later with `T = int32` does not turn the parameter into an owned value. Write `value: own T` when a generic function must consume or return its argument. ## Call Binding Calls accept positional arguments followed by named arguments: ```aura def render(name: str, count: int32 = 1): print(name) render("Aura") render("Aura", 2) render(name="Aura", count=2) ``` Every declared parameter is positionally bindable. Aura 0.3 structural callable types do not encode keyword-only callability, so a `*` marker in a parameter list is rejected with `AU1101`. Binding is deterministic: 1. positional arguments fill parameters in declaration order 2. named arguments fill the parameter with the same name 3. one parameter cannot be filled twice 4. unknown names and excess positional arguments are rejected 5. every omitted parameter must have a default 6. each bound argument must have the parameter's exact substituted type Positional arguments cannot follow a named argument. Parameter and argument lists may span physical lines while their parentheses remain open, but they do not accept trailing commas in Aura 0.3. ## Default Arguments A default is permitted on a bare shared or `own` parameter of a top-level function or class method: ```aura def greet(name: str = "world"): print("hello " + name) ``` The complete rules are: - `mut` parameters cannot have defaults, regardless of whether their types are copyable; the default would be a caller-invisible temporary, so every mutation would be a silent lost write. Require the caller to pass a value, or take the parameter as `own T` and return the result - a shared-borrow default is permitted; its default temporary lives until the call completes - an `own` default is permitted and its fresh temporary is consumed by the call - after the first defaulted parameter, every remaining parameter must also have a default - the default expression must have exactly the declared parameter type - a default expression cannot reference any parameter of the same declaration, including an earlier parameter - trait method declarations and trait implementation methods cannot declare defaults Defaults are evaluated afresh when the corresponding argument is omitted. They are not process-global singleton values. Every supplied argument is evaluated first in call-site source order before the next supplied expression begins. A copy or move result is captured in its parameter slot; a borrow-mode selection is established without cloning and remains subject to the retained non-copy overlap rules. Later side effects cannot change an earlier captured argument. Defaults for omitted parameters are then evaluated in declaration order. Binding named values to parameter slots never reorders their evaluation, and a supplied argument suppresses its default. See [Execution Model](/manual/execution-model#evaluation-order). ## Named Arguments For Builtins Maintained builtin functions and methods use the same binding rules, with parameter names defined by their API metadata: ```aura import process process.run(["/bin/echo", "hi"], stdout=process.pipe(), group=true) ``` ```aura import net net.http_request_text_timeout(method="POST", url="http://127.0.0.1:8080/jobs", body="{}", headers={}, timeout=2s) ``` The module chapters and [API Index](/manual/api-index) are authoritative for builtin parameter names, defaults, and return types. ## `try` And Result Returns `try` is valid only when its operand has type `Result[T, E1]` and the enclosing function returns `Result[U, E2]`: ```aura def parse_total(left: str, right: str) -> Result[int32, str]: a = try parse_int32(left) b = try parse_int32(right) return Result.Ok(a + b) ``` `Result.Ok(value)` makes `try` evaluate to `value`. `Result.Err(error)` returns from the enclosing function immediately. `E1` must equal `E2`, or an applicable `impl From[E1] for E2` with a `from` method must be visible. Active `with` cleanups run during this early return. See [Execution Model](/manual/execution-model#try). ## Owned Returns Every return annotation describes an owned result: ```aura class User: name: str score: int32 def score(user: User) -> int32: return user.score ``` Here the caller receives an ordinary `int32` copy. Copy results need no provenance annotation because they are independent owned values. For a non-copy result, the function must produce ownership. It can construct a fresh value, clone a clone-safe value, move from an `own` parameter, or invoke an operation that consumes an owner: ```aura def copy_name(user: User) -> str: return user.name.clone() def into_name(user: own User) -> str: return user.name ``` A bare or `mut` parameter grants access but does not give the function ownership of a non-copy value stored behind that access. Moving such a value into the result is rejected; use one of the owned-result patterns above. Every result is an owned value. See [Ownership And Borrowing](/manual/ownership-and-borrowing#owned-returns). ## Generic Functions Type parameters follow the function name: ```aura def identity[T](value: own T) -> T: return value ``` Bounds restrict substitutions: ```aura def describe[T: Greeter](value: T) -> str: return value.greet() def use_both[T: First + Second](value: T) -> int32: return value.score() ``` The checker infers type arguments from call arguments and an available expected result type. Explicit specialization fixes them: ```aura answer = identity[int64](42) ``` Every type parameter must resolve, all bounds must hold, and explicit type arguments must have the declared arity. See [Generics And Traits](/manual/generics-and-traits#inference-and-specialization). A clone-producing operation over an unresolved type parameter does not make the generic declaration invalid. The checker infers a clone-safety obligation for that parameter. Calls discharge the obligation after substitution, and a generic caller propagates an unresolved obligation as part of its own callable contract. The requirement also applies when the callable is imported or used as a maintained task target. See [Generics And Traits](/manual/generics-and-traits#inferred-clone-safety-obligations). ## Function Values A module-level named function is a value. Its type uses declaration-shaped syntax: `def(T1, mut T2, own T3) -> R`. The parameter list contains modes and types rather than parameter names, and `def() -> R` is the zero-parameter form. Bare parameters are shared. Function types may appear anywhere another complete type may appear, including variable and parameter annotations, class fields, return types, and collection element types. This includes public user-module functions and maintained builtin-module functions such as `process.pipe`. Calling an imported builtin through a value uses the same builtin dispatch and result type as calling its qualified name. An inferred local binding retains the named function's exact declared parameter modes. An indirect call through a `mut` parameter requires a mutable place, and an `own` parameter moves a non-copy argument. Assignment and argument passing compare these modes as part of the function type: `def(mut Counter) -> None` does not match `def(Counter) -> None`. Function values are code pointers. They are copy values, cloning is unnecessary, and copying or passing one as an `own def(...) -> R` parameter does not invalidate the source binding. They also satisfy `Transfer`. Ordinary indirect calls evaluate and bind arguments under the selected function's unchanged capability contract. A binding whose target declaration is statically known retains that declaration's call contract: named arguments are accepted and omitted arguments use its defaults. The structural `def(...) -> ...` type itself does not contain names or default expressions. A control-flow selection can retain names and default availability when every candidate agrees; an omitted argument then evaluates the runtime-selected target's own default expression. Reassignment between conflicting contracts, return through a structural function annotation, class-field storage, and mutable-collection storage erase those extras. Storage still retains the complete ABI type, including `mut` and `own`; a loaded value takes every argument positionally. If an indirect-call default traps, diagnostics use the public target name and the precise default-expression span. Compiler-generated default helpers never appear in the call chain. A generic named function must receive explicit type arguments, for example `show_int = show[int32]`, or a concrete expected function type. Expected types can specialize a variable annotation, argument, field, collection element, or parameter default such as a generic `empty` used where `def() -> Option[str]` is required. A generic name with neither source of type arguments does not have one concrete function-value type. This stage is deliberately capture-free. Instance-method, associated-method, and trait-method values are not first-class; an associated method without `self` remains accepted only in the existing direct `TaskGroup` target form. Lambdas and closure capture are specified separately. ## Function Values And Task Starts The ordinary and explicit-stack `TaskGroup` start methods accept a named function value as their target. Existing direct named-function and associated-method-without-`self` target forms remain accepted. ```aura def work(value: int32) -> int32: return value * 2 worker = work with group = TaskGroup(): task = group.start(worker, 21) ``` Task capture ownership is independent of the target function's call ABI. Each argument is first copied or moved into task-owned capture storage: `own` target parameters consume their capture, while bare shared parameters access that storage for the duration of the child call. `mut` targets are rejected because mutable access to detached capture storage has no caller-visible writeback contract. See [Concurrency](/manual/concurrency). ## `main` In the selected entry module, a local function named `main` is the entrypoint when there are no executable top-level statements. Its only valid signatures are: ```aura def main() -> int32: return 0 ``` ```aura def main(): print("done") ``` `main` takes no parameters and returns exactly `int32` or `None`. A returned `int32` becomes the requested host exit status; `None` means success. An imported function named `main` remains an ordinary imported function. A file cannot combine a local `main` with executable top-level statements. The alternate top-level execution form, evaluation order, cleanup on return, and the 256-call runtime depth limit are specified in [Execution Model](/manual/execution-model#entry-module-execution). ## Grammar Function and method declarations, generic parameters and bounds, receiver and ordinary parameter forms, defaults, owned return annotations, and call arguments are normative in [Grammar](/manual/grammar). Ordinary functions are module items; nested function declarations are not accepted. Expression lambdas are specified by [Closures](/manual/closures). ## Typing Rules Every ordinary parameter has one declared type and declaration-stable passing mode. Calls bind positional then named arguments, substitute inferred or explicit generic arguments, enforce bounds and exact types, and fill only legal defaults. They also enforce inferred clone-safety obligations after substitution. Every reachable non-`None` path returns the declared type. Shared or mutable access never authorizes moving a non-copy value into the result; non-copy returns require an owned source. ## Runtime Semantics The callee target is resolved statically. Supplied arguments evaluate left to right and each result is captured before later argument side effects; omitted defaults then evaluate freshly in declaration order, a call creates one frame, and `return` transfers its value after exited cleanups run. `try` may perform that return early. Entry `main` maps `None` to success or its `int32` result to the requested host process status. ## Ownership And Evaluation Order Bare parameters grant shared access; an implementation may pass copy bits directly. `own` parameters consume their arguments; `mut` requires one exclusive mutable place and writes through it. Borrowed default temporaries live through the call, owned defaults are consumed, and mutable borrow defaults are rejected as guaranteed lost writes. Task start first stores owned captures and then invokes the target under its declared ABI. ## Diagnostics `AU1101` means malformed function, method, parameter, return, or call syntax. `AU2001` means the call target or referenced declaration could not be resolved. `AU2002` means a signature, function-value capability, parameter, default, return, bound, or entrypoint type mismatch. `AU2004` means positional or named argument binding failed. `AU2005` means focused guidance for an unavailable callable spelling, including out-of-scope method values. `AU2999` means another callable rejection without a narrower compile-time code. `AU3001` means a moved argument was used; `AU3002` means a borrow or alias conflict; `AU3003` means a mutability violation; and `AU3004` means an invalid parameter, receiver, return, or task-capture ownership mode. `AU3007` means a call specialization would duplicate non-cloneable `random.Rng` state or could not satisfy a callable clone-safety obligation. `AU4001` means a call-depth or general call trap. A callee's `AU4002` means arithmetic overflow or underflow, `AU4003` means bounds or lookup violation, `AU4004` means zero divisor, and `AU4005` means a trapping resource or I/O failure; each retains the same typed Aura call frames and task ancestry on MIR and direct-native execution. ## Backend Support Ordinary, indirect function-value, generic, imported, associated, trait-dispatched, and maintained task-target calls are implemented for MIR execution and direct native builds. Shared semantic checking and the forced parity matrix require identical call results and primary failures. Compiler analysis and the LSP use the same resolved signature metadata, including inferred clone-safety obligations. ## Limits And Implementation-Defined Behavior Aura has no method values, trait-object function interactions, Aura variadic functions, overloads, nested functions, or mutable-parameter task targets. Expression lambdas are specified by [Closures](/manual/closures); they do not add nested item declarations. Written function types express bare shared, `mut`, and `own` parameter contracts. Runtime calls are limited to 256 nested Aura frames. Host process exit representation may narrow the requested `int32` after it leaves Aura; function binding and evaluation order are otherwise not implementation-defined. ## Status The function, method, generic, capture-free function-value, default-argument, named-argument, owned-return, inferred clone-safety, task-target, and entrypoint contracts described above are implemented. Supplied/default evaluation and argument capture follow `architecture_docs/decisions/0015-explicit-and-default-argument-order.md`, which is **Accepted**. The rules are pinned by `crates/aura-compiler/tests/fixtures/run-pass/explicit_and_default_argument_order.au` on both backends. Return values are owned. By-value expression closures are implemented under Accepted ADR-0037. FFI v0 adds bodyless direct-call-only `extern "C" def` declarations; they are not function values and their restricted signatures are specified by [FFI v0](/manual/ffi). ## Source: docs/manual/generics-and-traits.md # Generics And Traits Generics parameterize declarations over types. Traits are nominal interfaces used for generic bounds, method dispatch, operator dispatch, supertrait requirements, and `try` error conversion. Aura does not use structural typing: having methods with matching spellings does not satisfy a trait. A visible applicable `impl` is required. ## Generic Declarations Classes, enums, functions, methods, and implementation blocks may declare type parameters: ```aura class Box[T]: value: T enum MaybePair[T]: One(T) Two(T, T) def identity[T](value: own T) -> T: return value ``` Type parameter names must be unique within their declaration. `Self` is reserved and cannot be declared as a type parameter. A generic use must provide exactly the declared arity; generic arguments are invariant and are never implicitly widened or structurally converted. Bounds follow a type parameter after `:`. `+` means every listed bound is required: ```aura def use_value[T: Display + Score](value: T) -> int32: print(value.display()) return value.score() ``` Classes and enums may also carry bounds. The checker enforces them when resolving construction and when the specialized value is used through bounded generic operations: ```aura class NamedBox[T: Named]: value: T enum MaybeNamed[T: Named]: Some(T) Empty ``` The exact parameter-list forms are in [Grammar](/manual/grammar#type-references-and-type-parameters). ## Inference And Specialization Generic calls infer substitutions by unifying argument types with parameter type patterns. An available expected result type may add constraints. Generic class and enum construction similarly use provided fields/payloads and an expected constructed type. Parameter ownership is resolved at the generic declaration. Because an unconstrained `T` is not assumed copyable, a bare `value: T` is a shared borrow and remains declaration-stable even when a call later specializes `T` to a copy type. Use `value: own T` when the generic body consumes, stores, or returns the argument. ```aura boxed = Box(value=7) # Box[int64] value = identity("Aura") # str ``` Every declared type parameter must resolve. The checker does not invent a type for a parameter that appears nowhere in supplied values or expected context. Explicit specialization fixes the arguments: ```aura boxed = Box[int64](value=42) value = identity[int64](42) ok = Result[int32, str].Ok(7) ``` Explicit arguments must have exact arity and satisfy all substituted bounds. Specialization and indexing share bracket syntax; the parser rules that distinguish them are specified in [Grammar](/manual/grammar#explicit-specialization). ## Inferred Clone-Safety Obligations Generic clone-safety obligations are inferred from clone-producing operations in callable bodies. An operation over an unresolved type parameter is accepted when the checker can record which declared parameters must be safe to clone. A concrete call discharges those obligations after substitution. A type is safe for this purpose when duplicating it cannot duplicate `random.Rng` state through an ordinary class, enum, or collection path; `Task[T]` and `Queue[T]` handles stop that clone-safety traversal because an allowed handle copy does not observe or copy `T`. Accepted ADR-0033 separately requires Queue payloads and task results to be `Transfer` and makes `Task[T]` non-copy when `T` is not repeatable. A clone barrier is therefore not an escape from the task-boundary rule. A generic-to-generic call propagates the obligation to the caller. Inference continues to a fixed point independent of declaration order, and the resulting contract is retained by imported functions and methods. The same rules apply to ordinary, inherent, associated, task-target, trait, operator, and `From` calls. There is no source annotation for this obligation in Aura 0.3. When a type is concrete, a substitution containing `random.Rng` is rejected with `AU3007`. A concrete type whose clone safety cannot be proved is rejected conservatively. Moving, removing, receiving, or rearranging one owned value is not clone-producing and introduces no such obligation. ## Trait Declarations A trait declares a nominal method contract: ```aura trait Greeter: def greet(self) -> str ``` Trait methods may be signature-only, ending at the newline, or may provide a default body after `:`: ```aura trait Named: def name(self) -> str def label(self) -> str: return "name=" + self.name() ``` A marker trait contains `pass` and no required methods: ```aura trait Marker: pass ``` Trait names and method names must be unique in their scopes. Trait type parameter lists use the plain parameter form: ```aura trait Mapper[T]: def map(self, value: own T) -> T ``` Bounds may appear on a trait method's own generic parameters. Ordinary trait method parameters cannot have defaults. An obligation inferred from a trait default body is part of the trait method's contract. It is substituted through `Self`, trait arguments, and method type arguments for every implementation and every form of dispatch. A signature-only trait method has no inferred clone-safety obligation. A trait is private to its defining module unless declared `public trait`. Implementation blocks have no independent exported name and cannot be prefixed with `public`; their methods become available through the implemented public trait/type context when the implementation is loaded. ## `Self` `Self` denotes the implementing or enclosing concrete class specialization in supported class, trait, and implementation method type positions: ```aura trait Combine: def combine(self, other: Self) -> Self ``` `Self` takes no type arguments. It is not a global type and is unavailable in an unrelated top-level function. Inside a trait declaration it is initially a placeholder; inside an implementation it is substituted with the implementation target. ## Implementations An implementation attaches one trait specialization to one target type pattern: ```aura class Person: name: str impl Greeter for Person: def greet(self) -> str: return "hello " + self.name ``` Generic and specialized implementations are supported: ```aura impl Mapper[int32] for Doubler: def map(self, value: own int32) -> int32: return value * self.factor ``` ```aura impl[T] Mapper[T] for Box[T]: def map(self, value: own T) -> T: return value ``` ```aura impl Displayable for Box[str]: def display(self) -> str: return self.value.clone() ``` An implementation target must have a concrete or generic named outer type such as `Box[T]`; a bare target type parameter in `impl[T] Trait for T` is rejected. Implementation type parameters may have bounds, and every parameter used by the target/trait pattern must resolve during applicability checking. Two implementations with exactly the same trait specialization and target are duplicates and are rejected. More general and more specialized overlapping patterns may coexist. Dispatch selects the unique applicable implementation with greatest structural specificity; equal-best matches are ambiguous and rejected. Source order is never a tie breaker. Aura 0.3 does not impose a separate orphan-rule restriction, but an implementation must refer to known visible types and traits and participates only where that implementation is present in the loaded module/package context. ## Implementation Method Conformance An implementation may define only methods belonging to the trait. It must provide every signature-only required method; a trait method with a default body is inherited when omitted. An implementation may override a default method. For an explicitly implemented method, conformance compares: - receiver presence and passing mode (shared `self`, consuming `own self`, `mut self`, or none) - ordinary parameter count and substituted types - each ordinary parameter's resolved owned/shared/mutable access mode - owned return type - the trait method's substituted clone-safety obligations Ordinary parameter names may differ between the trait and implementation when their positions and types still match. Implementation methods cannot add default ordinary arguments. Extra methods, missing required methods, receiver mismatches, and signature mismatches are rejected before body execution. An explicit implementation MUST NOT strengthen its trait method's clone-safety contract. Its body may rely on obligations already inferred by the trait method, but it cannot introduce a requirement that bound-based callers cannot see. Because Aura 0.3 has no explicit clone-safety annotation, generic clone-producing behavior belongs in a trait default body. An implementation that adds such a requirement is rejected with `AU3007`. An `impl` targeting any builtin type MUST NOT explicitly define or inherit a trait method whose name is a builtin member of that target. This covers the runtime handles `Queue[T]`, `Task[T]`, `TaskGroup`, `random.Rng`, `fs.File`, and the `net` and `process` handles, and equally the builtin value types such as `str`, `list[T]`, `dict[K, V]`, `set[T]`, `Duration`, and the scalar types. Builtin member names are reserved for their runtime operation; a collision reports `AU2006` and the trait method must be renamed. A trait method whose name does not collide still implements and dispatches normally on a builtin target. This rule is applied after default trait methods are inherited. ## Trait Method Dispatch For a concrete value, member lookup considers inherent class methods and applicable visible trait implementations. The selected method keeps its declared receiver and argument ownership behavior. For a type parameter, only methods justified by declared bounds are available: ```aura def say_hello[T: Greeter](value: T): print(value.greet()) ``` Specialized trait bounds provide their type arguments: ```aura def apply[M: Mapper[int32]](mapper: M, value: int32) -> int32: return mapper.map(value) ``` If multiple bounds or equally specific implementations expose an indistinguishable applicable method, the call is ambiguous and rejected. Concrete and bound-based dispatch enforce the same substituted clone-safety contract. Associated trait methods follow the same rule as receiver methods. Traits may also declare associated methods without `self`: ```aura trait Factory: def make() -> int32 impl Factory for Widget: def make() -> int32: return 7 value = Widget.make() ``` ## Supertraits A trait may require one or more supertraits: ```aura trait Labelled: Named: def label(self) -> str: return "name=" + self.name() ``` The second colon terminates the header. Multiple supertraits are comma-separated. An `impl Labelled for User` is valid only when the same target also satisfies `Named` through an applicable implementation. Implementing the child does not synthesize the parent implementation. Supertrait methods are available through a child bound, and default child methods may call them. Supertrait types must name known traits with exact arity. Requirements are transitively closed during bound and dispatch checking. ## Operator Traits When builtin numeric/string operator rules do not apply, these operator spellings request traits and method names: | Source operator | Trait method | | --- | --- | | `left + right` | `Add.add` | | `left - right` | `Sub.sub` | | `left * right` | `Mul.mul` | | `left / right` | `Div.div` | | `left // right` | `FloorDiv.floor_div` | | `left % right` | `Mod.mod` | | `-value` | `Neg.neg` | | `not value` | `Not.not` | | `<`, `<=`, `>`, `>=` | `Ord.lt`, `Ord.le`, `Ord.gt`, `Ord.ge` | Builtin numeric `//` and the heterogeneous builtin `Duration // int64` rule take precedence over trait dispatch. Otherwise `//` and `//=` request an applicable `FloorDiv.floor_div` implementation. Equal integer operands with `/` are rejected with the integer-division teaching diagnostic rather than dispatched to `Div.div`; `/` can still request `Div.div` for an applicable non-numeric user type. The divisor-sign rule for `%` describes builtin numeric remainder; `Mod.mod` on a user type has the semantics of that implementation. The maintained generic shapes are illustrated by: ```aura trait Add[Rhs, Out]: def add(self, rhs: Rhs) -> Out trait FloorDiv[Rhs, Out]: def floor_div(self, rhs: Rhs) -> Out trait Neg[Out]: def neg(self) -> Out trait Ord[Rhs]: def lt(self, rhs: Rhs) -> bool def le(self, rhs: Rhs) -> bool def gt(self, rhs: Rhs) -> bool def ge(self, rhs: Rhs) -> bool ``` `Sub`, `Mul`, `Div`, and `Mod` follow the same binary `Rhs, Out` shape as `Add` and `FloorDiv`; `Not` follows the unary `Out` shape. Ordering methods must return `bool`. `and` and `or` do not dispatch through traits. Builtin `==` and `!=` also do not use an equality trait in Aura 0.3. This includes recursive structural tuple equality, which a trait implementation cannot override. Builtin operations take precedence wherever their concrete value rule applies. When an operator selects a trait method, it also enforces that method's substituted clone-safety obligations. ## `From` And `try` When `try` propagates `Result[T, SourceError]` from a function returning `Result[U, TargetError]`, exact error-type equality needs no trait. Otherwise the checker looks for an applicable `impl From[SourceError] for TargetError` containing `from`. The conventional contract is: ```aura trait From[Source]: def from(value: own Source) -> Self ``` The selected conversion runs before `Result.Err` is returned from the enclosing function. If no applicable conversion exists, `try` is rejected. See [Functions](/manual/functions#try-and-result-returns). The selected `From.from` method's clone-safety obligations are enforced before the conversion is accepted. ## Current Generic And Trait Boundaries - generic arguments are invariant and there is no general subtyping - type inference is local/contextual rather than whole-program inference - trait and implementation method defaults for ordinary parameters are not supported - generic user classes cannot currently serve as `with` resources - generic task targets are permitted when their callable type arguments can be resolved; bare shared and `own` targets use task-owned captures, while `mut` targets are rejected - equal-specificity overlapping implementations remain an error at the use site - clone-safety obligations are inferred rather than written, and an explicit implementation cannot strengthen the contract inferred by its trait method Observable syntax and implementation limits are collected in [Current Limits](/manual/current-limits), while cross-cutting type rules are in [Static Semantics](/manual/static-semantics#generics-traits-and-implementations). ## Grammar The normative productions for type parameters, bounds, explicit specialization, trait declarations, supertraits, `Self`, and implementation blocks are in [Grammar](/manual/grammar). Classes, enums, functions, methods, traits, and implementations use the declaration-specific parameter forms shown above. Trait methods may be signature-only or have a default suite; implementation methods always use ordinary method-definition syntax. ## Typing Rules Generic arguments are invariant and have exact arity. Inference is local and contextual, must resolve every declared parameter, and must satisfy every substituted bound. Trait satisfaction is nominal through a visible applicable `impl`, never structural. Implementations must conform after substituting receiver mode, parameter modes and types, owned return type, clone-safety obligations, and supertrait requirements. Dispatch selects one unique greatest-specificity applicable implementation; equal-best matches are rejected. `Self` denotes the enclosing/implementing concrete specialization only in its supported declaration contexts. ## Runtime Semantics Generic construction and calls use the statically resolved specialization; there is no runtime generic inference. Trait member and operator calls invoke the statically selected implementation, inheriting a trait default body when the implementation omits that method. Source order never resolves overlapping implementations. `try` invokes the selected `From[Source]` conversion before constructing the enclosing `Result.Err`. Traits do not create runtime reflection, dynamic method dictionaries, or implicit conversions. ## Ownership And Evaluation Order Parameter ownership is resolved at the generic declaration and remains stable after specialization: an unresolved bare `T` is shared, even when one later substitution is copy, while `own T` is the explicit consuming form. Trait and implementation signatures must agree on that resolved mode. Receiver evaluation precedes ordinary arguments, selected methods keep their declared receiver/parameter behavior, and `From.from` owns its source error. No generic or trait boundary inserts a hidden clone, coercion, or ownership-mode change. Clone-producing bodies infer obligations, generic calls propagate them, and concrete dispatch discharges them after substitution. ## Diagnostics `AU1101` reports malformed generic, trait, supertrait, specialization, or implementation syntax. `AU2001` reports unknown types, traits, methods, and members. `AU2002` covers inference failure, generic arity, unsatisfied bounds, missing trait satisfaction, ambiguous equal-specificity dispatch, invalid specialization, and substituted type mismatch. `AU2003` reports an unsupported operator when no builtin rule or applicable operator trait supplies it. `AU2004` reports call argument binding and the prohibition on ordinary default arguments in trait methods. `AU2006` reports builtin method collisions. `AU2999` covers duplicate/invalid implementations, method-conformance or supertrait failure, unsupported implementation targets, and remaining generic/trait rejections. `AU3001` reports use after an owned generic or receiver move. `AU3002` reports borrow conflicts or storing through a bare shared generic parameter. `AU3003` reports a mutable receiver call through an immutable place, and `AU3004` reports an invalid ownership mode. `AU3007` reports an unsafe concrete clone specialization, an unprovable concrete requirement, or an implementation that would strengthen its trait method's clone-safety contract. A selected body retains its runtime diagnostic: `AU4001` for a general trap, `AU4002` for arithmetic overflow or underflow, `AU4003` for a bounds or lookup violation, `AU4004` for a zero divisor, and `AU4005` for a resource or I/O failure. ## Backend Support Generic functions, classes, enums, methods, traits, supertraits, default trait bodies, generic and specialized implementations, operator dispatch, `Self`, and `From` conversion are implemented for MIR execution and direct native generation. User-trait dispatch on builtin values, including `Queue[T]`, `Task[T]`, `TaskGroup`, `random.Rng`, `str`, and the builtin collections, is maintained for noncolliding method names on both backends; builtin target members always retain builtin dispatch. The checker supplies one resolved specialization and implementation target to lowering, analysis, and the LSP, including inferred clone-safety obligations; the parity gate rejects backend-specific dispatch behavior. ## Limits And Implementation-Defined Behavior Aura 0.3 has no trait objects, dynamic dispatch, associated types or constants, higher-kinded parameters, default type arguments, `where` clauses, specialization annotations, general subtyping, or separate orphan-rule restriction. A bare target parameter in `impl[T] Trait for T` is unsupported. Equal-specificity overlaps remain errors, ordinary trait/impl parameters cannot add defaults, and generic user classes cannot be `with` resources. Inference and dispatch are defined by the rules above rather than source order or backend implementation choice. ## Status Invariant generics, local/contextual inference, explicit specialization, nominal traits and bounds, supertraits, default methods, generic and specialized implementations, unique-most-specific dispatch, operator traits, `Self`, and `From`-based `try` conversion plus inferred clone-safety contracts are implemented for the post-Phase 1.5 surface. Return values are owned. Trait objects, dynamic dispatch, associated types, higher-kinded types, general subtyping, and arbitrary blanket implementation targets are unavailable. ### Verified Clone-Safety Contracts The following blocks pin the observable boundary. A generic clone helper is valid for a safe specialization: ```aura def duplicate[T](values: list[T]) -> list[T]: return values.copy() def main() -> int32: values = [1, 2] print(duplicate(values)) return 0 ``` The same callable rejects an unsafe concrete specialization: ```aura import random def duplicate[T](values: list[T]) -> list[T]: return values.copy() def reject(values: list[random.Rng]) -> list[random.Rng]: return duplicate(values) ``` The requirement also survives a generic-to-generic call: ```aura import random def duplicate[T](values: list[T]) -> list[T]: return values.copy() def forward[T](values: list[T]) -> list[T]: return duplicate(values) def reject(values: list[random.Rng]) -> list[random.Rng]: return forward(values) ``` A signature-only trait method does not let an implementation add a hidden requirement: ```aura trait Copier[T]: def copy_values(self) -> list[T] class Wrapper[T]: values: list[T] impl[T] Copier[T] for Wrapper[T]: def copy_values(self) -> list[T]: return self.values.copy() ``` A trait default body can establish the requirement for safe specializations: ```aura trait Duplicator[T]: def duplicate(self, values: list[T]) -> list[T]: return values.copy() class Marker[T]: value: T impl[T] Duplicator[T] for Marker[T]: pass def main() -> int32: marker = Marker(0) values = [4, 5] print(marker.duplicate(values)) return 0 ``` Its unsafe specialization is rejected through the same contract: ```aura import random trait Duplicator[T]: def duplicate(self, values: list[T]) -> list[T]: return values.copy() class Marker[T]: value: T impl[T] Duplicator[T] for Marker[T]: pass def reject(marker: Marker[random.Rng], values: list[random.Rng]) -> list[random.Rng]: return marker.duplicate(values) ``` ## Source: docs/manual/grammar.md # Grammar This chapter defines the complete source grammar of Aura 0.3. The grammar is normative after lexical token formation. Static restrictions—types, visibility, ownership, exhaustiveness, valid receivers, and API-specific rules—are defined by [Static Semantics](/manual/static-semantics). ## Notation The grammar uses an EBNF-style notation: - quoted text is a literal token - `name` is a nonterminal - `[ item ]` is optional - `{ item }` repeats zero or more times - `( a | b )` selects one alternative - a comma in the grammar separates sequence elements; `","` is the source comma token - comments inside grammar blocks are informative `NEWLINE`, `INDENT`, `DEDENT`, and `EOF` are layout tokens produced by the lexer. `IDENT`, `INTEGER`, `FLOAT`, `DURATION`, `STRING`, `FSTRING`, and `BOOLEAN` are lexical tokens described below. Comma-separated source lists do not accept a trailing comma unless their production explicitly adds one. The singleton tuple forms `(value,)`, `(T,)`, and `(pattern,)` require their one comma; multi-element tuples do not accept a trailing comma. `NEWLINE` in the productions means a logical newline. A physical newline suppressed inside an open `(`, `[`, or `{` never reaches this grammar. Delimiter continuation changes token formation, not the expression productions; it does not add a trailing comma to any list form. ## Lexical Grammar ```ebnf ascii-letter = "A" … "Z" | "a" … "z" ; digit = "0" … "9" ; binary-digit = "0" | "1" ; octal-digit = "0" … "7" ; hex-digit = digit | "a" … "f" | "A" … "F" ; IDENT = (ascii-letter | "_"), { ascii-letter | digit | "_" } ; decimal-digits = digit, { digit } ; decimal-integer = digit, { digit | ("_", digit) } ; hex-integer = ("0x" | "0X"), hex-digit, { hex-digit | ("_", hex-digit) } ; binary-integer = ("0b" | "0B"), binary-digit, { binary-digit | ("_", binary-digit) } ; octal-integer = ("0o" | "0O"), octal-digit, { octal-digit | ("_", octal-digit) } ; INTEGER = decimal-integer | hex-integer | binary-integer | octal-integer ; EXPONENT = ("e" | "E"), [ "+" | "-" ], digit, { digit } ; FLOAT = decimal-digits, ".", decimal-digits, [ EXPONENT ] | decimal-digits, EXPONENT ; DURATION = decimal-digits, ("ms" | "s" | "m") ; BOOLEAN = "true" | "false" ; ``` Identifiers are ASCII and case-sensitive. Unicode is allowed in string contents. Integers may be decimal, hexadecimal, binary, or octal and must fit the lexer’s unsigned 128-bit literal representation before contextual typing. An underscore is accepted only between digits valid for the selected base. Floats must be finite `f64` values at lexing time. Duration literals represent non-negative integral decimal milliseconds, seconds, or minutes and must fit signed 128-bit nanoseconds after scaling. A negative number is unary `-` applied to a positive literal, not one lexical token. Leading-dot and trailing-dot float forms are not accepted. ## Keywords And Contextual Words The reserved token words are: ```text class enum def trait impl import from mut own indirect public extern opaque return assert if elif else and or not match case for in while break continue pass try with as true false ``` `from` is contextual: it introduces a from-import at module level and may also be used as an identifier where the grammar expects one. `lambda` is lexed as an identifier but introduces a lambda at the start of an expression; member and named-argument positions may still use that spelling. `copy`, `self`, `None`, `set`, `Self`, and `_` are lexed as identifiers and acquire special meaning only in the positions defined below. ## Strings And F-Strings `STRING` is an ordinary, triple-quoted, or raw string. Ordinary strings use a matching pair of single or double quotes. Triple-quoted strings use three matching single or double quotes and may span physical lines. Ordinary and triple-quoted strings accept the same escapes: | Escape | Meaning | | --- | --- | | `\n` | line feed | | `\t` | tab character in the decoded value | | `\"` | double quote | | `\'` | single quote | | `\\` | backslash | | `\0` | NUL | | `\xHH` | byte-valued Unicode scalar from exactly two hexadecimal digits | | `\u{H...}` | Unicode scalar from one or more hexadecimal digits | An invalid scalar, unknown escape, missing digit, or missing or mismatched closing quote is a lexical error. Triple-quoted values preserve every scalar between their delimiters. Aura does not trim the first or last newline, remove indentation, or normalize whitespace. Raw strings use lowercase `r` immediately followed by one single or double quote. Backslashes are content. A backslash may retain the active quote inside the value, with both characters preserved. A raw string cannot span a physical line or end in an odd run of backslashes. Raw triple strings, raw f-strings, and byte strings are not tokens. There is no separate character-literal token. `FSTRING` begins with `f"` and ends at the matching double quote. `{ expression }` interpolates an ordinary Aura expression. Two opening braces insert one literal opening brace, and two closing braces insert one literal closing brace. A lone closing brace outside an interpolation is also literal in Aura 0.3. Interpolations may contain nested braces and ordinary single- or double-quoted strings; braces inside those strings do not change interpolation depth. Empty or invalid interpolations are rejected. An interpolation may end with one top-level `:` followed by this static format grammar: ```text [[fill]align] [sign] [width] [","] ["." precision] [type] ``` `align` is `<`, `^`, or `>`; `sign` is `+`, `-`, or a space; and `type` is `d`, `f`, `e`, `x`, `X`, `b`, `o`, `%`, or `s`. Width and precision are decimal values through `1_000_000`. The parser accepts a complete expression before looking for the separator, so colons inside slices, calls, dictionaries, and other nested delimiters remain expression syntax. Nested fields and dynamic specifications are rejected. Single-quoted f-strings and conversion flags are not supported. Although `\t` creates a tab in a decoded ordinary string, a physical tab is rejected outside a triple-quoted string. A physical tab inside a triple-quoted string is exact string content. ## Comments, Physical Lines, And Indentation `#` starts a comment outside a string and consumes the rest of the physical line. There are no block comments. The source is UTF-8. One optional UTF-8 BOM is ignored only at the beginning of the file. Layout token formation is: 1. A blank or comment-only physical line produces no token and does not affect indentation. 2. Every other physical line is measured by its number of leading ASCII spaces. 3. In ordinary block-layout mode, an increase from the current indentation count emits one `INDENT` and pushes that exact count. 4. In ordinary block-layout mode, a decrease emits `DEDENT` tokens until an earlier count is reached. A count not present on the stack is inconsistent indentation and is rejected. 5. The line content is tokenized. An ordinary-layout line emits one `NEWLINE`; a continuation line suppresses it; and a delimited expression-`match` layout island emits only the layout tokens required by its header and arms. 6. At end of source, remaining indentation levels emit `DEDENT`, followed by `EOF`. Outside an open delimiter, Aura does not prescribe four-space indentation; it requires consistent return to previous block levels. The maintained formatter and examples use four spaces. While a `(`, `[`, or `{` remains open, ordinary physical newlines and their leading spaces do not produce layout tokens. Delimiters must nest and match by kind. A delimited expression-form `match` is a layout island: its header and arms retain the layout tokens required by the match productions even though an outer delimiter remains open. Backslash continuation is unavailable. Ordinary, raw, and f-strings remain single-line. Triple-quoted ordinary strings may span physical lines without creating layout tokens. Existing comma-separated forms do not gain a trailing comma. ## Punctuation And Operators ```text ( ) [ ] { } : , . ? = == != < <= > >= + += - -= * *= ** **= / /= // //= % %= & &= | |= ^ ^= ~ << <<= >> >>= -> ``` There is no semicolon, assignment expression, unary plus, or lambda arrow. ## Modules And Imports ```ebnf module = { module-element }, EOF ; module-element = import-declaration | module-constant | item | statement ; module-constant = [ "public" ], IDENT, [ ":", type ], "=", expression, NEWLINE ; import-declaration = "import", identifier-path, [ "as", import-alias ], NEWLINE | "from", identifier-path, "import", import-name, { ",", import-name }, NEWLINE ; import-name = identifier, [ "as", import-alias ] ; import-alias = IDENT ; identifier-path = identifier, { ".", identifier } ; identifier = IDENT | "from" ; ``` Imports, module constants, items, and executable top-level statements may be interleaved syntactically. Imports resolve before initializer checking. Module constants initialize after their dependencies and in declaration source order. Executable entry statements run only after constant initialization completes. The compiled module represents these as separate categories; programs MUST use the defined category ordering and MUST NOT infer another execution order from cross-category interleaving. An `as` clause binds the complete imported module or declaration under the written local alias. A from-import may mix direct and aliased names in one declaration. Aliasing does not change the target module identity, visibility, type identity, or package resolution path. Wildcard imports, relative-dot imports, parenthesized import lists, and trailing import commas are not part of the grammar. ## Items ```ebnf item = [ "public" ], class-declaration | [ "public" ], enum-declaration | [ "public" ], function-declaration | [ "public" ], extern-function-declaration | [ "public" ], extern-opaque-declaration | [ "public" ], trait-declaration | impl-declaration ; extern-function-declaration = "extern", STRING, "def", identifier, "(", [ parameter-list ], ")", "->", type, NEWLINE ; extern-opaque-declaration = "extern", STRING, "opaque", "class", identifier, NEWLINE ; ``` `public` is not allowed on an implementation block. Item declarations are module-level; they are not statements and cannot appear inside function/control-flow suites. Parsing requires the extern ABI string to be exactly `"C"`. Extern declarations are bodyless and non-generic. Their parameter modes and types are restricted by [FFI v0](/manual/ffi). ## Type References And Type Parameters ```ebnf type = [ "indirect" ], type-primary, [ "?" ] ; type-primary = identifier-path, [ "[", type-list, "]" ] | tuple-type | function-type ; type-list = type, { ",", type } ; tuple-type = "(", type, ",", ")" | "(", type, ",", type, { ",", type }, ")" ; function-type = "def", "(", [ function-type-parameter, { ",", function-type-parameter } ], ")", "->", type ; function-type-parameter = [ "mut" | "own" ], type ; plain-type-parameters = "[", identifier, { ",", identifier }, "]" ; bounded-type-parameters = "[", bounded-type-parameter, { ",", bounded-type-parameter }, "]" ; bounded-type-parameter = identifier, [ ":", type, { "+", type } ] ; ``` A function type contains parameter modes and types, but no names or default expressions: `def(int32, mut Counter, own str) -> bool`. A bare parameter is shared, `mut` requires caller-visible mutable access, and `own` transfers the argument. Parameter names are not accepted inside the list. `indirect` is invalid on a function type because the value is already a code pointer. `T?` denotes `Option[T]`, including when `T` is a tuple type. Type and type-parameter lists are nonempty when brackets are present and do not accept trailing commas. `(T,)` is a singleton tuple type; `(T)` is not a type. `()` and a trailing comma on a multi-element tuple type are rejected. Although the grammar places `indirect` before any type primary, it is statically valid only on the complete named type reference where recursive-field rules permit it; an `indirect` tuple type is rejected. ## Classes ```ebnf class-declaration = [ "copy" ], "class", identifier, [ bounded-type-parameters ], ":", NEWLINE, INDENT, class-member, { class-member }, DEDENT ; class-member = "pass", NEWLINE | [ "public" ], field-declaration | [ "public" ], method-declaration ; field-declaration = identifier, ":", type, [ "=", expression ], NEWLINE ; ``` `copy` is contextual and is recognized only immediately before `class`. Fields and methods may be interleaved. `pass` permits an otherwise empty class body; a comment-only body is not a suite. ## Enums ```ebnf enum-declaration = "enum", identifier, [ bounded-type-parameters ], ":", NEWLINE, INDENT, enum-variant, { enum-variant }, DEDENT ; enum-variant = identifier, [ "(", enum-payload-list, ")" ], NEWLINE ; enum-payload-list = type, { ",", type } | identifier, ":", type, { ",", identifier, ":", type } ; ``` A variant payload list is either entirely positional or entirely named. Empty payload parentheses and mixed positional/named declarations are rejected. A no-payload variant omits parentheses. ## Functions, Methods, And Parameters ```ebnf function-declaration = "def", identifier, [ bounded-type-parameters ], "(", [ parameter-list ], ")", [ return-annotation ], ":", NEWLINE, suite ; method-declaration = "def", identifier, [ bounded-type-parameters ], "(", [ method-parameter-list ], ")", [ return-annotation ], ":", NEWLINE, suite ; parameter-list = parameter, { ",", parameter } ; method-parameter-list = receiver, [ ",", parameter, { ",", parameter } ] | parameter-list ; receiver = "self" | "mut", "self" | "own", "self" ; parameter = identifier, ":", [ "mut" | "own" ], type, [ "=", expression ] ; return-annotation = "->", type ; ``` A receiver, when present, is the first method parameter. Bare `self` is the shared receiver, `mut self` is mutable, and `own self` is consuming. There is exactly one spelling per capability. A first method parameter written as `self: Type` is rejected rather than interpreted as an ordinary parameter; use one of the receiver forms above. Ordinary parameter capabilities appear after the colon: bare `T` is shared, `mut T` is mutable, and `own T` is consuming. Call sites pass the value directly and never prefix an argument with a capability. Bare means shared access for every type, including declaration-known copy types. Return annotations carry no capability: every return is an ordinary owned return. Parameter lists, calls, and return annotations do not accept trailing commas. Static checking further restricts duplicate names, default placement/availability, and mutable task targets. ## Traits And Implementations ```ebnf trait-declaration = "trait", identifier, [ plain-type-parameters ], ":", [ type, { ",", type }, ":" ], NEWLINE, INDENT, trait-member, { trait-member }, DEDENT ; trait-member = "pass", NEWLINE | trait-method ; trait-method = "def", identifier, [ bounded-type-parameters ], "(", [ method-parameter-list ], ")", [ return-annotation ], ( NEWLINE | ":", NEWLINE, suite ) ; impl-declaration = "impl", [ bounded-type-parameters ], identifier, [ "[", type-list, "]" ], "for", type, ":", NEWLINE, INDENT, impl-member, { impl-member }, DEDENT ; impl-member = "pass", NEWLINE | method-declaration ; ``` Trait-declaration type parameters use the plain form; bounds on those parameters are expressed through supertraits or method constraints rather than inline bounds in the trait parameter list. Trait methods may be signature-only (newline immediately after the return annotation) or provide one default body after `:`. The second colon in a trait header separates an optional comma-separated supertrait list from the body, for example `trait Child: Parent, Named:`. ## Suites And Statements ```ebnf suite = INDENT, statement, { statement }, DEDENT ; statement = assignment-statement | return-statement | assert-statement | pass-statement | if-statement | match-statement | for-statement | with-statement | while-statement | break-statement | continue-statement | expression-statement ; statement-end = NEWLINE | DEDENT | EOF ; assignment-statement = [ "mut" ], assignment-target, [ ":", type ], assignment-operator, expression, statement-end | unpack-target, "=", expression, statement-end ; assignment-target = identifier, { ".", identifier | "[", expression, "]" } ; unpack-target = binding-target, ",", binding-target, { ",", binding-target } | "(", binding-target-list, ")" ; binding-target-list = binding-target, "," | binding-target, ",", binding-target, { ",", binding-target } ; binding-target = identifier | "(", binding-target-list, ")" ; assignment-operator = "=" | "+=" | "-=" | "*=" | "**=" | "/=" | "//=" | "%=" | "&=" | "|=" | "^=" | "<<=" | ">>=" ; return-statement = "return", [ expression ], statement-end ; assert-statement = "assert", non-tuple-expression, [ ",", non-tuple-expression ], statement-end ; pass-statement = "pass", NEWLINE ; break-statement = "break", NEWLINE ; continue-statement = "continue", NEWLINE ; expression-statement = expression, statement-end ; ``` An annotation is valid only on a simple-name assignment target. Place assignment targets cannot contain calls. An unpack target contains only names and recursively parenthesized binding-target lists; it uses plain `=`, has no annotation or leading `mut`, and must match one exact tuple shape. The top-level comma distinguishes `left, right = pair` from an expression. Parentheses group or nest an unpack target. One-line suites are not supported. The optional top-level comma in an assertion belongs to `assert-statement`; tuple operands must be parenthesized. ## Conditional And Loop Statements ```ebnf if-statement = "if", expression, ":", NEWLINE, suite, { "elif", expression, ":", NEWLINE, suite }, [ "else", ":", NEWLINE, suite ] ; while-statement = "while", expression, ":", NEWLINE, suite ; for-statement = "for", loop-target, "in", [ "mut" | "own" ], expression, ":", NEWLINE, suite ; loop-target = identifier | unpack-target ; ``` The loop target is one identifier or a recursively nested tuple unpack target. Tuple leaves inherit the yielded element's ownership provenance. A tuple target is rejected with `mut` iteration because the minimal tuple surface has no recursive writeback. Loop `else` clauses are not supported. For collection-place traversal, an absent modifier is shared iteration. Queue and Range use their iterable-specific bare defaults instead: Queue receives owned items, while Range yields independent copy `int64` values. Explicit modifiers are rejected for Queue because it is a receive operation and for Range because there is no place or ownership transfer to modify. The iterable position also recognizes two compiler-known call shapes, `enumerate(expression)` and `zip(expression, expression)`. They are not values and have no production outside this position; static semantics reject either name elsewhere, and a user declaration of the name shadows the loop form. Explicit ownership modifiers are rejected for both, because they iterate over the bare-loop shared default. ## `with` Statements ```ebnf with-statement = "with", identifier, "=", expression, ":", NEWLINE, suite | "with", expression, "as", identifier, ":", NEWLINE, suite ; ``` The two forms are equivalent. Static semantics require a supported resource and a fresh binding. ## Patterns And Statement Matches ```ebnf match-statement = "match", [ "mut" | "own" ], expression, ":", NEWLINE, INDENT, match-statement-arm, { match-statement-arm }, DEDENT ; match-statement-arm = "case", pattern, [ "if", expression ], ":", NEWLINE, suite ; pattern = closed-pattern, { "|", closed-pattern } ; closed-pattern = "_" | BOOLEAN | STRING | FLOAT | INTEGER | "-", (INTEGER | FLOAT) | tuple-pattern | binding-pattern | variant-pattern ; binding-pattern = IDENT ; variant-pattern = identifier-path, [ "(", [ pattern, { ",", pattern } ], ")" ] ; tuple-pattern = "(", pattern, ")" | "(", pattern, ",", ")" | "(", pattern, ",", pattern, { ",", pattern }, ")" ; ``` Pattern parsing uses these contextual rules: - exact `_` is the wildcard - one unparenthesized, unqualified name beginning with lowercase ASCII or `_` is a binding - a dotted name, a capitalized name, or any name followed by parentheses is a variant pattern - payload patterns are positional even when the variant declaration used named payload fields - a parenthesized comma form is a fixed-arity recursive tuple pattern - `|` has the lowest pattern precedence and joins alternatives - parentheses group one pattern when no comma is present Every or-pattern alternative must bind the same names with identical exact types and capabilities. A guard is an ordinary expression checked as exactly `bool`; its pattern bindings are in scope. A top-level binding is an irrefutable catch-all when unguarded and must be the final arm. A guarded top-level binding does not contribute to exhaustiveness. There are no ranges, collection destructuring, rest patterns, named-payload patterns, duration patterns, or f-string patterns. `match mut` rejects a tuple pattern because mutable tuple reconstruction/writeback is not part of the minimal surface. Statement match arms always contain suites; `case pattern: statement` is not valid. ## Expressions And Precedence From lowest to highest precedence: | Level | Form | Associativity | | --- | --- | --- | | 1 | conditional expression | right | | 2 | `or` | left | | 3 | `and` | left | | 4 | prefix `not` | right | | 5 | `==`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `not in` | chained left to right | | 6 | `|` | left | | 7 | `^` | left | | 8 | `&` | left | | 9 | `<<`, `>>` | left | | 10 | `+`, `-` | left | | 11 | `*`, `/`, `//`, `%` | left | | 12 | prefix `match`, `try`, unary `-`, unary `~` | right/prefix | | 13 | `**` | right | | 14 | specialization, indexing, slicing, member access, call, numeric cast | left-to-right postfix chain | | 15 | primary | — | ```ebnf expression = lambda-expression | non-tuple-expression ; non-tuple-expression = conditional-expression ; lambda-expression = "lambda", [ lambda-parameter, { ",", lambda-parameter } ], ":", expression ; lambda-parameter = [ "mut" | "own" ], identifier ; conditional-expression = or-expression, [ "if", or-expression, "else", conditional-expression ] ; or-expression = and-expression, { "or", and-expression } ; and-expression = not-expression, { "and", not-expression } ; not-expression = { "not" }, comparison-expression ; comparison-expression = bitwise-or-expression, { comparison-operator, bitwise-or-expression } ; comparison-operator = "==" | "!=" | "<" | "<=" | ">" | ">=" | "in" | "not", "in" ; 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 = prefix-expression, { ("*" | "/" | "//" | "%"), prefix-expression } ; prefix-expression = match-expression | "try", prefix-expression | "-", prefix-expression | "~", prefix-expression | power-expression ; power-expression = postfix-expression, [ "**", prefix-expression ] ; postfix-expression = primary-expression, { specialization-suffix | index-suffix | member-suffix | call-suffix | numeric-cast-suffix } ; index-suffix = "[", expression, { ",", expression }, "]" | "[", [ expression ], ":", [ expression ], "]" ; member-suffix = ".", identifier ; call-suffix = "(", [ argument, { ",", argument } ], ")" ; argument = [ identifier, "=" ], expression ; numeric-cast-suffix = "as", numeric-type ; numeric-type = "int" | "int8" | "int16" | "int32" | "int64" | "int128" | "intsize" | "uint8" | "uint16" | "uint32" | "uint64" | "uint128" | "uintsize" | "float32" | "float64" ; ``` Conditional expressions associate to the right, their condition is an `or-expression`, and their two value arms may contain nested conditional expressions through grouping or the recursive alternative arm. Arithmetic, shift, bitwise, and Boolean chains are left-folded except for power, which associates to the right. Power binds more tightly than a unary operator on its left, while its right operand may begin with unary `-` or `~`. Equality, ordering, and membership share the one comparison level and chain the Python way rather than left-folding, so `a < b <= c` is one chain of two links over three operands. A chain of `n` operators means the conjunction of its `n` adjacent comparisons, with each operand evaluated at most once. `not a == b` means `not (a == b)`, because prefix `not` binds looser than the comparison level, while `a not in b` is one comparison operator. Casts bind more tightly than power and arithmetic. Comma-separated index expressions are accepted only for `Array[T]`, where one `int64` coordinate is required per runtime axis. Other indexable types retain one index expression. The one-colon bracket forms are owned slices. Each endpoint is optional, so `value[start:end]`, `value[:end]`, `value[start:]`, and `value[:]` all use the second `index-suffix` alternative. On `Array[T]`, the range copies the first axis. A second colon is reserved step syntax and is rejected with `AU2005`; it is not part of the accepted grammar. A slice suffix is an expression only and cannot be an assignment target. ## Primary Expressions And Literals ```ebnf primary-expression = identifier | INTEGER | DURATION | FLOAT | BOOLEAN | STRING | FSTRING | parenthesized-expression | list-literal | brace-literal | list-comprehension | set-comprehension | dictionary-comprehension ; list-literal = "[", [ expression, { ",", expression } ], "]" ; brace-literal = "{", "}" | "{", expression, { ",", expression }, "}" | "{", expression, ":", expression, { ",", expression, ":", expression }, "}" ; list-comprehension = "[", expression, comprehension-clauses, "]" ; set-comprehension = "{", expression, comprehension-clauses, "}" ; dictionary-comprehension = "{", expression, ":", expression, comprehension-clauses, "}" ; comprehension-clauses = comprehension-for, { comprehension-if | comprehension-for } ; comprehension-for = "for", loop-target, "in", comprehension-component ; comprehension-if = "if", comprehension-component ; comprehension-component = lambda-expression | or-expression ; parenthesized-expression = "(", expression, ")" | tuple-expression ; tuple-expression = "(", expression, ",", ")" | "(", expression, ",", expression, { ",", expression }, ")" ; ``` Lambda parameters receive their types from an expected structural function type, whose result also constrains the body. A zero-parameter lambda may infer its result from the body. The colon introduces one expression, not a suite. Lambda parameters do not accept annotations, defaults, generics, or a trailing comma. `(value)` is grouping and `(value,)` is a singleton tuple. Tuple value expressions require parentheses; an unparenthesized comma is accepted only in an unpack target. `()` and a trailing comma on a multi-element tuple are rejected. A nonempty brace literal is a set when its first element is not followed by `:`, otherwise it is a dictionary. `{}` is an empty dictionary. An empty set uses the typed `set[T]()` constructor. A comprehension has one or more `for` clauses. A clause may be followed by zero or more `if` filters before another `for` clause. Clause targets use `loop-target`, including recursive tuple targets, but the iterable position has no `mut` or `own` modifier: comprehension clauses always use the bare-loop contract. The non-conditional `or-expression` alternative keeps a following comprehension `if` distinct from a conditional expression; use parentheses when an iterable or filter itself needs a conditional expression. A lambda remains syntactically admissible as a component and is then subject to the ordinary iterable or exact-Boolean static rule. The result expression, or the dictionary key and value expressions, may be any expression. A comma after comprehension clauses, or a mixture of comma-separated literal entries and clauses, is invalid. Generator expressions are not part of this grammar. ## Explicit Specialization ```ebnf specialization-suffix = "[", type-list, "]" ; ``` Specialization and indexing use the same brackets, so parser and static context disambiguate them. Brackets form specialization when their contents scan as one or more type references and either: 1. `(` follows and the base is a name or member, or 2. `.` follows and the final target name begins with uppercase ASCII. A bare bracket suffix is initially an index expression. Static resolution reinterprets `function[Types]` as explicit specialization when `function` resolves to a generic named function and the complete expression is used as a function value. Otherwise the brackets remain indexing. Consequently, `Box[int32](value)` and `Result[int32, str].Ok(1)` specialize, `show[int32]` may produce one concrete function value, and `value[index]` indexes. A top-level colon inside the brackets selects slicing rather than specialization or indexing. Slice endpoints are expressions and are checked under the exact rules in [Static Semantics](/manual/static-semantics#indexing-slicing-and-members). ## Match Expressions ```ebnf match-expression = "match", [ "mut" | "own" ], expression, ":", NEWLINE, INDENT, match-expression-arm, { match-expression-arm }, DEDENT ; match-expression-arm = "case", pattern, [ "if", expression ], ":", ( expression, match-expression-arm-end | NEWLINE, INDENT, expression, statement-end, DEDENT ) ; match-expression-arm-end = NEWLINE | DEDENT | ")" | "]" | "}" | EOF ; ``` A match-expression arm contains exactly one expression, either inline after the colon or on one indented following line. It is not a general statement suite. A complete match expression may appear in a return, initializer, call argument, collection element, grouping expression, or other expression position. When it appears inside a continued delimiter, its header and arms form a layout island and retain their required layout tokens. The containing delimiter may close after the final inline arm or on its own following line. ## Syntactic Complexity Limits The implementation rejects source that exceeds the maintained parser complexity budget rather than risking host stack exhaustion: - nested expressions, prefix forms, parentheses, types, patterns, and statements are limited to 128 parser levels - binary-operator and postfix chains reject the 128th chained operation - one comprehension rejects a 128th combined `for` clause or `if` filter - f-string interpolation brace nesting is limited to 128 These are observable implementation limits of Aura 0.3. Inputs that exceed them must be rejected cleanly. ## Syntax Not In Aura 0.3 The grammar intentionally excludes: - semicolons and multiple statements on one physical line - backslash line continuation - multiline f-strings; multiline ordinary text uses triple quotes - local item declarations, decorators, and attributes - wildcard/aliased/relative import syntax - ordinary trailing commas other than the required singleton-tuple comma - collection, range, rest, and class patterns - call-site capability annotations - exception statements, `raise`, and `yield` - generator expressions and generator functions If a form is absent from this grammar, examples and books must not present it as implemented Aura. ## Source: docs/manual/io.md # I/O Module The `io` module covers standard input/output and the common error enum shared by filesystem and networking APIs. ```aura import io ``` The top-level `print(value)` builtin is separate. It renders a value, writes a newline, and is meant for simple line output. `float32` and `float64` values use their own shortest round-trip decimal spelling, including a decimal marker for integral values and preservation of `-0.0`. Use `io.write(...)` and `io.flush()` when you need prompt-style or protocol-style control. ## Standard Streams | API | Signature | Contract | | --- | --- | --- | | `io.write` | `write(text: str) -> Result[None, io.Error]` | Writes `text` to standard output without adding a newline. | | `io.flush` | `flush() -> Result[None, io.Error]` | Flushes standard output. | | `io.read_line` | `read_line() -> Result[Option[str], io.Error]` | Reads one strict UTF-8 line from standard input, removes trailing LF/CRLF, and returns `Ok(None)` on EOF. | Example: ```aura import io def prompt() -> Result[None, io.Error]: try io.write("name> ") try io.flush() return Result.Ok(None) match io.read_line(): case Result.Ok(Option.Some(line)): print("hello " + line.trim()) case Result.Ok(Option.None): print("no input") case Result.Err(error): print(error) ``` ## io.Error `io.Error` is used by `io`, `fs`, and `net`. It is also wrapped by `process.Error.Io(...)` when a subprocess operation fails because of an I/O condition. | Variant | Meaning | | --- | --- | | `NotFound` | A file, directory, socket path, or other target was not found. | | `PermissionDenied` | The operating system denied access. | | `AlreadyExists` | Creation failed because the target already exists. | | `IsDirectory` | A file operation was attempted on a directory. | | `ConnectionRefused` | A peer refused the connection. | | `ConnectionReset` | A connection was reset by the peer. | | `ConnectionAborted` | A connection was aborted. | | `NotConnected` | An operation requires a connected stream or socket. | | `AddrInUse` | A local address is already bound. | | `AddrNotAvailable` | The requested address is not available. | | `BrokenPipe` | The write side was closed by the peer. | | `TimedOut` | The operation timed out. | | `WouldBlock` | The operation would block in a non-blocking context. | | `UnexpectedEof` | The stream ended before the requested data was read. | | `InvalidInput` | The caller supplied invalid input, such as a negative byte count. | | `InvalidData` | Data could not be decoded or was malformed for the operation. | | `Closed` | The resource was already closed or closed while waiting. | | `Cancelled` | Cancellation interrupted the operation. | | `Other(message: own str)` | A remaining platform or runtime error with a message. | ## Matching Errors Handle specific cases when the program has specific policy: ```aura match io.read_line(): case Result.Ok(Option.Some(line)): print(line) case Result.Ok(Option.None): print("end of input") case Result.Err(io.Error.InvalidData): print("input was not valid text") case Result.Err(error): print(error) ``` Avoid turning `io.Error` into a string too early. Error variants carry useful control flow. ## Grammar The `io` module and top-level `print` builtin add no source-language grammar. They use ordinary imports, calls, `Result`, `Option`, `try`, and pattern matching. `io.Error.Other(message: own str)` uses the normal owned enum-payload rule; the other variants carry no payload. Line endings are runtime input, not token syntax. `io.read_line()` removes one trailing LF or CRLF sequence from the returned line; it does not strip other whitespace. ## Typing Rules `print(value)` accepts one value and returns `None`. `io.write` accepts `str`; `io.flush` accepts no arguments; both return `Result[None, io.Error]`. `io.read_line` returns `Result[Option[str], io.Error]`, distinguishing a line, clean EOF, and an I/O failure. The `io.Error` variants in the table above are the common typed failure vocabulary for `io`, `fs`, and `net`; `process.Error.Io` owns an `io.Error` payload. Exhaustiveness and payload ownership follow the ordinary enum and match rules. ## Runtime Semantics `print` renders its value, writes the rendered text, and appends a newline. Floating-point rendering uses the type-specific shortest finite decimal that round-trips to the same `float32` or `float64` value, retains a decimal marker for integral values, and preserves negative zero. `io.write` adds no newline, and `io.flush` requests that buffered standard output be delivered to the host. `io.read_line` reads from process standard input as strict UTF-8. It returns `Ok(Some(line))` after removing LF or CRLF, `Ok(None)` only when EOF is reached before any bytes are read, and `Err(io.Error.InvalidData)` for invalid text. Other host failures map to the closest `io.Error` variant. ## Ownership And Evaluation Order The argument to `print` or `io.write` is evaluated before output occurs. The write call shares its `str` for the duration of the operation and does not retain it. A successfully read line and every payload-bearing error are fresh owned values returned to the caller. Standard input and output are process-global resources. Output calls are observable in source evaluation order within one task, but ordering between concurrent tasks follows scheduling. A successful write or read is not rolled back if later evaluation fails. Pattern matching can move the owned message from `io.Error.Other`; matching payload-free variants introduces no owned payload. ## Diagnostics Unknown I/O members use `AU2001`, wrong types use `AU2002`, invalid argument binding uses `AU2004`, and remaining static rejections use `AU2999`. The documented stream failures are typed `Result.Err(io.Error)` values, not language diagnostics. Invalid UTF-8 therefore produces `io.Error.InvalidData`, and a broken stream produces the applicable error variant. An uncaught failure outside the typed stream boundary uses the general runtime categories, including `AU4005` for a resource or I/O trap. The `aura` CLI treats its own broken output pipe as clean termination so compiler commands compose with pipe consumers; that tooling policy does not change an Aura program's `io.write` return type. ## Backend Support `print`, all three `io` functions, and every `io.Error` variant are supported by the MIR runtime and direct native backend. Text decoding, line-ending removal, EOF distinction, floating-point spelling, and error mapping are backend-parity contracts. The actual standard streams are supplied by the host process. A backend may buffer them differently, but `io.flush` and each documented typed outcome must remain observable as specified. ## Limits And Implementation-Defined Behavior Aura 0.3 exposes line-oriented text input only; it has no standard-input byte API, terminal mode API, stream replacement API, asynchronous console API, or built-in formatted-output language. `io.read_line` has no separate Aura line-length cap and therefore allocates according to the incoming line and host memory limits. Terminal encoding before bytes reach the process, host pipe buffering, scheduling between concurrent writers, and the precise message stored in `io.Error.Other` are host-dependent. Stable control flow should match the specific non-message variants where possible. ## Status The standard-stream functions, `print` behavior, `io.Error` enum, strict UTF-8 policy, EOF distinction, and shortest-roundtrip float rendering are implemented and maintained in Aura 0.3. No I/O semantics on this page are provisional. Aura 0.3 has no binary standard input, async stream handles, terminal control, configurable formatting, or user-defined error derivation. ## Source: docs/manual/json.md # JSON Module Aura's `json` module represents arbitrary JSON data with one recursive enum. Parsing reports malformed or unsupported input as typed data; dumping produces one deterministic JSON string or traps when the supplied value cannot satisfy the serializer contract. The dynamic tree and typed `dict[str, str]` helpers serve separate data shapes. The module does not derive schemas from user classes or enums. | API | Signature | Contract | | --- | --- | --- | | `json.parse` | `parse(text: str) -> Result[json.Value, json.Error]` | Parses one strict JSON value from a shared string. | | `json.dumps` | `dumps(value: json.Value, indent: Option[int64] = None) -> str` | Deterministically serializes a shared JSON tree. | | `json.is_valid` | `is_valid(text: str) -> bool` | Reports whether the text is valid JSON. | | `json.stringify_map` | `stringify_map(value: dict[str, str]) -> Result[str, str]` | Serializes a flat string dictionary in sorted-key order. | | `json.parse_string_map` | `parse_string_map(text: str) -> Result[dict[str, str], str]` | Parses a JSON object whose values are strings. | ## Value And Error Model `json.Value` has exactly these variants: | Variant | Payload | Meaning | | --- | --- | --- | | `Null` | none | JSON null. | | `Bool` | `bool` | JSON true or false. | | `Int` | `int64` | A mathematically integral JSON number in the `int64` range. | | `Float` | `float64` | Any other finite JSON number representable by binary64. | | `String` | `str` | An owned decoded JSON string. | | `Array` | `list[json.Value]` | An owned ordered sequence of values. | | `Object` | `dict[str, json.Value]` | An owned string-keyed object with insertion slots. | `json.Error` is returned only by `json.parse` and represents input-data failures. Resource failures while parsing or materializing the runtime tree trap with `AU4005` instead of adding a resource variant to this enum: | Variant | Payload | Meaning | | --- | --- | --- | | `Syntax` | `message: str, line: int32, column: int32` | The input is not one strict JSON value. | | `NumberOutOfRange` | `line: int32, column: int32` | A number fits neither the `Int` rule nor a finite `float64`. | | `NestingTooDeep` | `limit: int32, line: int32, column: int32` | A container would exceed the depth limit. | | `InputTooLarge` | `actual_bytes: int64, limit_bytes: int64` | The encoded input exceeds the parse cap. | Lines and columns are one-based. Columns count Unicode scalar values from the start of their line, not UTF-8 bytes. The position identifies the offending token or container. `NumberOutOfRange` points at the first scalar of its number token. `NestingTooDeep` points at the opening bracket or brace that would exceed the limit. `Syntax` points at the first unexpected scalar or, for unexpected end of input, the position immediately after the last scalar. ## Typed Accessors Accessors never coerce between variants: | API | Signature | Contract | | --- | --- | --- | | `json.is_null` | `is_null(value: json.Value) -> bool` | `true` only for `Value.Null`. | | `json.as_bool` | `as_bool(value: json.Value) -> Option[bool]` | The Bool payload or `None`. | | `json.as_int` | `as_int(value: json.Value) -> Option[int64]` | The Int payload or `None`; Float is not converted. | | `json.as_float` | `as_float(value: json.Value) -> Option[float64]` | The Float payload or `None`; Int is not converted. | | `json.into_string` | `into_string(value: own json.Value) -> Option[str]` | Consumes the value and returns its str payload or `None`. | | `json.into_array` | `into_array(value: own json.Value) -> Option[list[json.Value]]` | Consumes the value and returns its Array payload or `None`. | | `json.into_object` | `into_object(value: own json.Value) -> Option[dict[str, json.Value]]` | Consumes the value and returns its Object payload or `None`. | The inspecting functions borrow `value`. The `into_*` functions take an explicit owned value. A failed consuming accessor still consumes its argument and returns `Option.None`. ## Parsing `json.parse` accepts exactly one RFC 8259 JSON value with optional JSON whitespace before and after it. It rejects comments, trailing commas, leading-zero integers, non-JSON string escapes, `NaN`, infinities, and any non-whitespace after the first value. Number classification uses the exact mathematical value of the source token before any binary64 rounding: - a mathematical integer in the `int64` range becomes `Value.Int` - every other number whose IEEE-754 binary64 conversion is finite becomes `Value.Float`, with normal binary64 rounding and underflow - a number whose conversion overflows returns `Error.NumberOutOfRange` Consequently `1`, `1.0`, `1e0`, `1.5e1`, and `-0.0` all parse as Int values; the last is integer zero. `1.5` parses as a Float. `1e400` returns `NumberOutOfRange` rather than infinity. Array elements retain source order. Object keys establish insertion slots on their first occurrence. A later occurrence of the same key replaces its value without moving that first slot. Duplicate comparison uses the decoded str, so `"a"` and `"\u0061"` are the same key. This ordering remains observable through the ordinary `dict` iteration APIs even though dumping applies its own sorted-key order. Depth counts arrays and objects, not scalar leaves. A root scalar has depth zero and a root container has depth one. Depth 128 is accepted. The first container that would have depth 129 returns `Error.NestingTooDeep(limit=128, ...)`. Input length is measured in UTF-8 bytes. At most 67,108,864 bytes are accepted, including the exact boundary. A larger input returns `Error.InputTooLarge(actual_bytes, limit_bytes=67108864)` before syntax, number, or depth analysis. Parsing and both runtime conversion directions share a structural materialization limit of 262,144 JSON value nodes. The root counts as one node, and every scalar, array, or object value counts as one more; object keys do not count separately. Exactly 262,144 nodes are accepted. The next value traps with `AU4005` rather than returning `json.Error`, because the input may be valid JSON while the fixed runtime materialization budget has been exhausted. ## Deterministic Dumping `json.dumps` emits arrays in element order and objects with keys sorted lexicographically by their UTF-8 encodings. Valid UTF-8 preserves Unicode scalar order under that comparison. The result therefore does not depend on object insertion order. `Value.Int` uses an ordinary base-ten integer spelling with no decimal point. A finite `Value.Float` uses Aura's maintained shortest binary64 spelling that round-trips to the same binary64 value. An integral finite float retains a decimal or exponent marker, and negative zero remains `-0.0`. Parsing that text still applies the exact mathematical-integer rule, so `parse(dumps(value))` is not promised to preserve an explicitly constructed integral Float variant or negative floating zero. Sorted object keys likewise need not preserve the original dictionary's insertion slots. Strings retain non-ASCII Unicode scalar values. Quotation mark and reverse solidus are escaped. Backspace, tab, line feed, form feed, and carriage return use `\b`, `\t`, `\n`, `\f`, and `\r`; other U+0000 through U+001F controls use lowercase `\u00xx`. Solidus is not escaped. With `indent=None`, the output contains no insignificant whitespace. With `indent=Some(n)`, `n` must be from 0 through 16 inclusive. Pretty output uses: - LF line endings - `n` ASCII spaces for each container level - one ASCII space after an object colon - compact `[]` and `{}` for empty containers - no final newline Every nonempty container places each element or member on its own line, uses a comma after every item except the last, and places its closing delimiter on a separate line aligned with the container's opening level. `Some(0)` therefore uses line breaks but no leading indentation. Dump depth uses the same container-only definition and limit as parse depth. The output has an independent 67,108,864-byte UTF-8 cap. The exact boundary is accepted; a serializer never returns a partial result. ## Example This executable example parses a dynamic object, inspects an exact integer variant, constructs a mixed nested tree, and prints deterministic compact and pretty output: ```aura import json def main(): match json.parse("{\"workers\":3,\"tags\":[\"compiler\",\"service\"]}"): case Result.Ok(value): print(json.dumps(value)) case Result.Err(error): print(error) integer = json.Value.Int(7) print(json.as_int(integer)) payload = json.Value.Object({"workers": json.Value.Int(3), "ready": json.Value.Bool(true), "tags": json.Value.Array([json.Value.String("compiler"), json.Value.String("service")])}) print(json.dumps(payload)) print(json.dumps(payload, indent=Option.Some(2))) ``` The same program is maintained as `examples/json/dynamic_values.au`. ## Grammar The module adds no source-language grammar. Imports, qualified enum variants, variant construction, method calls, `Result` and `Option` matching, maps, vectors, and named/default arguments use the ordinary grammar defined elsewhere in this Manual. JSON text is runtime `str` data; JSON object, array, string, number, Boolean, and null syntax is not Aura source syntax. ## Typing Rules The signatures and variant tables above are normative. `json.Value` and `json.Error` are module-qualified builtin enums. `json.Value` is a move type because its declaration contains owned str, list, dict, and recursive Value payloads. `json.Error` is also a move type because `Syntax` contains a str. Every variant payload uses the normal owned enum-construction rule. `json.parse` and `json.dumps` use ordinary bare parameters, which are shared borrows under Aura's declaration-stable parameter policy. `indent=None` is an `Option[int64]` default evaluated at the call boundary. An accessor's `value` parameter mode is part of its type. Inspecting accessors do not change ownership. Each `into_*` call consumes its argument even when the runtime variant does not match. No accessor converts Int to Float, Float to Int, or a scalar to text. The flat-dictionary helpers have their own exact types. They are not aliases for `parse` and `dumps`, and the dynamic API does not broaden `parse_string_map` to accept nested or non-string values. The `json.is_valid` and `json.parse_string_map` parsers remain bounded caller-side operations; neither is submitted to the dynamic-parse codec service. `json.stringify_map` likewise remains caller-side. ## Runtime Semantics Parse first enforces the UTF-8 byte cap, then validates and constructs one owned tree under the numeric, duplicate-key, position, depth, and node-budget rules above. It returns `Result.Ok(value)` on success and one exact `json.Error` variant for data failure. A codec or runtime-tree allocation failure, or exhaustion of the 262,144-node materialization budget, traps with `AU4005`; it is not malformed-input data and does not become a `json.Error` variant. The dependency-owned recursive parse used by dynamic `json.parse` runs on Aura's dedicated JSON codec service rather than on a lightweight task's coroutine stack. The service is process-global and independent of the protocol and generic blocking-I/O pools. It has two workers with 2 MiB native stacks and a total in-flight capacity of two operations, including work that has reserved capacity but has not yet entered a worker. Capacity is reserved before the fallible owned copy of the source is made, so saturation cannot accumulate unbounded waiting source copies. A lightweight task waiting to enter the service parks on a scheduler notification rather than spinning. After parsing, codec-to-runtime materialization uses an iterative traversal. JSON-aware runtime cloning and rendering are iterative as well. These traversals preserve the exact tree, ownership, diagnostic, and resource rules in this chapter without making host call depth proportional to JSON nesting. Dump validates indent, depth, and finite floating values while emitting into a capped destination. It applies the exact sorted-key, number, escape, and whitespace rules above. A successful call returns one fresh owned str. Validation or resource failure produces the diagnostic described below rather than a `json.Error`, because the public return type is not a `Result`. Before emission, runtime-to-codec conversion applies the same root-inclusive, key-exclusive 262,144-node materialization limit. Runtime-to-codec conversion and deterministic emission are iterative; dumping does not use the recursive parser service. Equality and pattern matching follow the ordinary enum and collection rules. Float equality remains IEEE equality. A program can explicitly construct a non-finite `Value.Float`; that value can be inspected and matched but cannot be dumped as JSON. ## Ownership And Evaluation Order `parse` shares its input only for the call and does not retain it. Every str key, str value, array, object, and enum payload in the returned tree is fresh owned data. `dumps` shares its tree only for the call, does not reorder or mutate object maps, and leaves the caller's value available afterward. Enum constructors evaluate payload expressions in source order and consume non-copy payloads. Array and object construction therefore uses the existing List, dictionary, and enum ownership rules apply. Inspecting accessors share their argument; consuming accessors transfer one payload out of the supplied value or consume the unmatched value. Parsing and dumping are synchronous observable calls. Argument and receiver expressions are evaluated in ordinary call-site source order. Once a `json.parse` call has been admitted, cancellation does not abandon the codec job: the call waits for its result, and the task observes cancellation at its next ordinary cancellation boundary. There is no process-global mutable parser configuration, serializer setting, or key-order configuration; the process-global codec service carries work, not language-visible parse state. ## Diagnostics `AU2001` reports an unavailable `json` name, enum variant, function, or accessor. `AU2002` reports argument, constructor payload, return, or annotation type mismatches. `AU2004` reports invalid arity, argument names, or positional/named binding. Ordinary ownership diagnostics apply to moved Values, Errors, strings, arrays, and objects. Malformed syntax, an out-of-range number, excessive parse depth, and oversized parse input return typed `json.Error` values and are not runtime diagnostics. Parse allocation failure or a value beyond the shared 262,144-node materialization limit traps with `AU4005`. `json.dumps` traps with `AU4003` when indent is outside `0..=16` or a value exceeds depth 128. It traps with `AU4001` for a NaN or infinite Float payload. It traps with `AU4005` when the shared node budget is exceeded, output would exceed 67,108,864 bytes, or a controlled conversion/output allocation fails. These failures return no partial string. ## Backend Support The MIR runtime and direct native backend use the same recursive enum identity, numeric classification, error positions, duplicate-key behavior, depth, node, and byte limits, key order, number spelling, escaping, indentation, and diagnostic categories. For one input or value, both backends MUST produce the same Aura result and exact dump bytes. Both backends use the same bounded codec service for `json.parse`. The direct backend holds value-table read access only long enough to validate and copy the shared source str; it does not hold that access while waiting for service admission or completion. Runtime, direct-codegen, analysis, language-server, fixture, and executable-reference coverage maintain this surface across the two backends. ## Limits And Implementation-Defined Behavior JSON numbers have only the specified `int64` and finite `float64` representations. There is no arbitrary-precision integer, decimal, lossless source-number token, or non-finite JSON encoding. Object keys are Strings. The human-readable message carried by `Error.Syntax` may evolve; the error variant, coordinate convention, and location are normative. Parse and dump are whole-value operations. There is no incremental parser, streaming encoder, caller-provided writer, configurable key order, alternate escape mode, comments mode, trailing-comma mode, or configurable depth or byte cap. The parser accepts at most 67,108,864 bytes and 128 container levels; dump independently accepts the same maximum output size and value depth. Parse/runtime materialization and dump/runtime conversion additionally accept at most 262,144 JSON value nodes, counting the root and values but not object keys. The dynamic-`json.parse` codec service admits two operations process-wide. Its two 2 MiB-stack workers are initialized lazily and intentionally live until process exit; Aura 0.3 has no codec-service shutdown, join, sizing, or capacity configuration API. The service capacity does not govern `json.is_valid`, `json.parse_string_map`, or `json.stringify_map`. Derived class/enum schemas and generated codecs remain deferred beyond Phase 6. Schema validation, MessagePack, CBOR, Protobuf, and other binary formats are also unavailable. Codec-controlled collection/string growth and both runtime conversion trees use fallible allocation and map failure to `AU4005`. An unrecoverable host allocator failure inside Rust, the operating system, or dependency-owned scratch work remains external and can still terminate the process; the language does not claim that every possible host out-of-memory condition is catchable. ## Status The recursive value/error model, parse/dump surface, accessors, ordering, formatting, and resource boundary are implemented Aura 0.3 behavior. Their exact gap-fill semantics are accepted under ADR-0021. `is_valid`, `stringify_map`, and `parse_string_map` are maintained bounded flat-dictionary operations. Aura 0.3 has no streaming JSON codec. ## Source: docs/manual/language-specification.md # Language Specification This Manual is the normative specification of the implemented Aura 0.3 development language. It defines the source language, static rules, ownership model, execution behavior, maintained runtime APIs, package model, and tool contracts that a conforming implementation must provide. The specification describes exactly the language implemented in this repository. ## Scope The specification covers: - UTF-8 `.au` source text, tokens, indentation, and the complete accepted grammar - declarations, statements, expressions, patterns, names, scopes, and visibility - types, inference, generics, traits, calls, and operator resolution - moves, copies, borrows, mutable places, resources, and owned returns - module loading, packages, entry modules, top-level execution, and `main` - evaluation order, control flow, runtime failures, cleanup, tasks, cancellation, and backend equivalence - maintained builtin functions, enums, modules, resources, and CLI/editor contracts - implementation limits that are observable by valid or invalid Aura programs The specification does not define the compiler's private Rust data structures, MIR encoding, native ABI, object-file layout, or internal optimization choices except where they affect an observable language or tool contract. ## Normative Language The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHOULD**, **SHOULD NOT**, and **MAY** are normative: - **MUST** and **MUST NOT** state requirements for conforming implementations or programs. - **SHOULD** and **SHOULD NOT** state strong recommendations; a deviation needs a documented reason and must not contradict a MUST-level rule. - **MAY** marks permitted behavior or an optional implementation technique. Ordinary present-tense statements are normative when they describe accepted syntax, static behavior, evaluation, runtime results, or public APIs. Examples are illustrative unless a surrounding paragraph says that their exact output or diagnostic is part of the contract. ## Specification Version This reference describes Aura 0.3 as implemented by the repository containing it. Aura is an advanced technical preview, so source and API contracts may change before a tagged stable release. Any behavior change MUST update the relevant reference page, conformance tests, examples, tutorials, and work record in the same pass. The repository commit identifies the precise revision of the specification. The rendered Manual is stamped with source version 0.3.2 (technical preview) and its implementation baseline commit. Release builds supply that commit without writing a self-referential hash into this source page; see the [Manual overview](/manual/) for the exact precedence and local fallback. ## Authority And Conformance The normative Manual and its executable conformance suite jointly define the maintained language: 1. This specification states the intended rule. 2. Compiler fixtures and regression tests make the rule executable. 3. The compiler, runtime, CLI, and language server are implementations of that rule. 4. Categorized examples and Learn chapters teach the rule without extending it. If the Manual, tests, and implementation disagree, the disagreement is a project defect. It must be resolved deliberately; undocumented behavior does not silently become a language feature, and proposal-only behavior does not override the maintained reference. See [Conformance](/manual/conformance) for the test mapping and [Status And Compatibility](/manual/status-and-compatibility) for the preview stability policy. ## Processing Model A source file passes through the following observable phases: 1. **Decoding and lexing.** The implementation accepts UTF-8, forms tokens, and emits indentation tokens. 2. **Parsing.** Tokens form a module AST according to the [complete grammar](/manual/grammar). 3. **Module and package loading.** Imports are resolved relative to the package source root and dependency graph. 4. **Static checking.** Names, types, trait implementations, calls, ownership, borrows, patterns, control flow, and entrypoint rules are validated. 5. **Lowering and execution.** `aura run` executes checked MIR. A direct build emits native code; the default auto build may package checked MIR in a native launcher when direct emission is unavailable. All maintained representations MUST agree on program behavior. A failure in phases 1–4 is a compile-time diagnostic. A checked program may still produce an explicit runtime error for operations such as checked integer overflow, division by zero, out-of-bounds mutation, I/O failure, recursion-depth exhaustion, or invalid resource state. Recoverable library failures use typed `Result` or outcome enums where the API specifies them. ## Terms **Module** : The declarations, imports, and optional top-level statements in one `.au` source file, together with its logical package-qualified name. **Entry module** : The source file selected by a run, build, check, test, analysis, or completion command. Entrypoint-only rules such as the `main` signature apply to this module. **Item** : A top-level class, enum, Aura function, extern function, extern opaque handle, trait, or trait implementation declaration. **Binding** : A name associated with a value, parameter, pattern payload, module, type parameter, or declaration. **Place** : A storage location that may be read, moved, assigned, or borrowed: a local binding, field path, or supported indexed location. **Copy type** : A type whose values are duplicated by assignment and by-value use instead of being consumed. **Move type** : A type whose by-value use transfers ownership and makes the source place unavailable until it is reinitialized. **Clone-producing operation** : An operation that creates a second owned structural value while retaining the original, including explicit collection copies, cloned collection reads, and task-result observations. **Clone-safety obligation** : An inferred callable requirement that a substituted type must not duplicate non-cloneable state through a clone-producing operation. Aura 0.3 protects `random.Rng` state under this contract. **Borrow** : Temporary access to an existing place without transferring ownership. A shared borrow permits reading; a mutable borrow permits exclusive mutation. **Owned position** : A source position that consumes a non-copy value, including an explicit `own` parameter or collection loop, a class field or enum payload constructor, assignment, return, and maintained storing APIs. **Default parameter mode** : The unmodified `value: T` spelling. Shared access for every type is the source contract; an implementation may pass copy bits directly without changing that source contract. The shared mode remains stable after generic specialization. **Resource** : A runtime-backed value with an explicit `close()` contract and, where documented, lexical cleanup through `with`. **Diverging path** : A control-flow path that returns, breaks, continues, propagates an error, or terminates through a runtime failure instead of reaching the next statement normally. ## Defined, Implementation-Defined, And Unspecified Behavior Aura aims to avoid undefined behavior at the language level. Programs that violate a static rule MUST be rejected. Checked operations that fail MUST produce the documented typed outcome or runtime diagnostic rather than memory-unsafe behavior. Some behavior is intentionally platform-dependent: - `intsize` and `uintsize` follow the host pointer width. - filesystem paths, process behavior, Unix sockets, available address families, and host error messages depend on the platform. - ordering of external events and concurrently ready tasks is not a deterministic language guarantee unless an API states otherwise. - dictionary and set iteration follow the maintained runtime's insertion-oriented representation today, but programs should rely only on ordering explicitly promised by the relevant API contract. Implementation-defined or platform-dependent behavior MUST remain within the constraints documented by the relevant Manual page. Behavior not granted by the specification, especially dependence on object layout, task scheduling order, hash identity, native symbol names, or diagnostic byte offsets, is unspecified and must not be required for portable Aura programs. ## Reference Organization Read the normative core in this order: 1. [Lexical Structure](/manual/lexical-structure) 2. [Grammar](/manual/grammar) 3. [Names And Scopes](/manual/names-and-scopes) 4. [Types](/manual/types) and [Static Semantics](/manual/static-semantics) 5. [Ownership And Borrowing](/manual/ownership-and-borrowing) 6. [Expressions](/manual/expressions), [Statements](/manual/statements), [Closures](/manual/closures), [FFI v0](/manual/ffi), and declaration chapters 7. [Execution Model](/manual/execution-model) 8. runtime/library chapters and the [API Index](/manual/api-index) 9. [Diagnostics](/manual/diagnostics), [Current Limits](/manual/current-limits), and [Conformance](/manual/conformance) The Learn track and a future book may reorder concepts pedagogically, but they MUST not contradict these rules. ## Source: docs/manual/lexical-structure.md # Lexical Structure This chapter defines how Aura source text becomes tokens and indentation markers. It is normative for source spelling. The complete token-level productions are collected in [Grammar](/manual/grammar); name binding and reserved builtin names are defined by [Names And Scopes](/manual/names-and-scopes) and [Static Semantics](/manual/static-semantics). ## Source Files And Text Aura source files conventionally use the `.au` extension and contain UTF-8 text. One UTF-8 byte-order mark is ignored only when it occurs at the beginning of the file. Source is processed as physical lines and logical lines. Outside an open source delimiter, a nonblank physical line normally ends one logical line. While a `(`, `[`, or `{` remains open, ordinary physical line boundaries are lexical whitespace and the logical line continues. The expression-form `match` layout island described below is the one exception that preserves block tokens inside an enclosing delimiter. ## Identifiers Identifiers are ASCII and case-sensitive. Their exact spelling is: ```ebnf ascii-letter = "A" … "Z" | "a" … "z" ; digit = "0" … "9" ; IDENT = (ascii-letter | "_"), { ascii-letter | digit | "_" } ; ``` Examples of identifiers are `count`, `_message`, `buffer`, `Result`, and `worker2`. `résultat` is not an identifier because non-ASCII letters are not accepted in names. Unicode remains valid inside strings. An identifier spelling can still be rejected by static checking. Builtin types and functions reserve maintained names, declarations cannot collide in the same namespace, and some positions impose additional rules. See [Names And Scopes](/manual/names-and-scopes). ## Token Words And Contextual Words The lexer recognizes these words specially: ```text class enum def trait impl import from mut own indirect public extern opaque return assert if elif else and or not match case for in while break continue pass try with as true false ``` `true` and `false` produce boolean-literal tokens. The other words introduce declarations, control flow, ownership forms, imports, or operators and cannot normally be used as ordinary identifiers. `extern` and `opaque` introduce the bodyless declarations described by [FFI v0](/manual/ffi). `own` is reserved everywhere; it marks consuming ordinary parameters, collection loops, and matches, as well as the consuming receiver spelling `own self`. `mut` marks mutable parameters, loops, matches, and the receiver spelling `mut self`, and also introduces a mutable local binding. `from` is contextual. At module level, a complete prefix of the form `from module.path import ...` begins an import. In other identifier positions, `from` can name a parameter, local binding, expression, member, type-path component, or named argument: ```aura def replace(from: str, to: str) -> str: return from + to def main(): mut from = "left" from = replace(from=from, to="right") ``` Several other spellings are lexed as ordinary identifiers and become special only in a defined context: | Spelling | Contextual meaning | | --- | --- | | `copy` | Modifies `class` when immediately before it. | | `self` | Declares or refers to a method receiver. | | `Self` | Refers to the current type in supported trait and implementation type positions. | | `None` | The unit value, or `Option.None` when an expected option type makes that interpretation unambiguous. | | `set` | Names the builtin set type and its constructor. | | `lambda` | Introduces a lambda when it appears at the start of an expression; it remains an identifier token for member and named-argument positions. | | `_` | The wildcard in a match pattern; elsewhere it is an identifier spelling subject to static rules. | ## Comments `#` begins a comment outside a string and consumes the remainder of the physical line: ```aura # A comment-only line. print("ready") # A trailing comment. ``` Aura 0.3 has no block comments. ## Spaces, Tabs, And Indentation Blocks are indentation-based: ```aura if ready: print("yes") else: print("no") ``` Indentation uses ASCII spaces. A physical tab character anywhere in a source line is a lexical error, including inside indentation, a comment, or a quoted string. The two-character escape `\t` is valid inside a string because it contains a backslash and `t` in source and creates a tab only in the decoded value. Blank and comment-only lines do not produce tokens and do not change indentation. Every other line is handled as follows: 1. The lexer counts its leading spaces. 2. When no ordinary delimiter continuation is active, a count greater than the current block count emits one `INDENT` and records the new count. 3. When no ordinary delimiter continuation is active, a smaller count emits one or more `DEDENT` tokens. The new count must equal a previously recorded indentation level. 4. The line contents are tokenized. The lexer emits `NEWLINE` only when the physical boundary is a logical boundary or belongs to a delimited expression-form `match` layout island. 5. End of file emits all outstanding `DEDENT` tokens and then `EOF`. Aura does not require an indentation width of four spaces, but sibling lines must return to exactly the same recorded count. The maintained examples use four spaces. A suite must contain at least one nonblank, non-comment line. Use `pass` for an intentionally empty suite. ## Physical And Logical Line Boundaries Inside an unmatched `(`, `[`, or `{`, an ordinary physical newline does not emit `NEWLINE`, `INDENT`, or `DEDENT`. The next nonblank physical line continues the same logical token sequence. Delimiters may be nested and mixed, but they must close in last-opened, first-closed order with the matching kind. ```aura def combine( left: int64, right: int64 ) -> int64: return left + right def main(): values = [ 20, 22 ] result = combine( values[0], values[1] ) print(result) ``` The verified program prints `42`. It deliberately has no trailing comma after `right`, `22`, or `values[1]`: newline continuation does not change the comma-separated-list grammar. Leading spaces on an ordinary continuation line are formatting rather than block indentation. They do not consult or modify the surrounding indentation stack. The maintained style uses one additional four-space level. Physical tabs remain invalid even when they appear only in continuation indentation. Blank and comment-only lines remain ignored, and a trailing comment may end a continued physical line. The newline after the outermost closing delimiter ends the logical line normally. A newline does not continue merely because the preceding token is an operator or comma: some `(`, `[`, or `{` must still be open at that physical boundary. An expression-form `match` inside a delimiter retains the layout tokens needed by its `case` arms. That arm block is a layout island inside the continued outer expression. It accepts both the existing closer after a final inline arm and a closer placed on its own line. See [Expressions](/manual/expressions#match-expressions) and [Grammar](/manual/grammar#match-expressions). Backslash continuation is not implemented. Ordinary, raw, and f-strings remain single-line; delimiters inside them do not continue source, and an f-string interpolation cannot cross a physical newline. ## Punctuation And Operators Aura 0.3 recognizes: ```text ( ) [ ] { } : , . ? = == != < <= > >= + += - -= * *= ** **= / /= // //= % %= & &= | |= ^ ^= ~ << <<= >> >>= -> ``` There is no semicolon. Multiple statements cannot share one physical line. Aura has no unary `+`, assignment expressions, or lambda arrow; lambdas use `lambda parameters: expression`. The lexer chooses the longest operator spelling, so `**=`, `<<=`, `>>=`, and `//=` are each one token. Comma-separated lists do not accept a trailing comma. This applies to arguments, parameters, imports, type arguments, generic parameters, enum payloads, collection elements, and trait lists. The tuple grammar is the one exception: its singleton value, type, target, and pattern forms require one comma, while multi-element tuples reject a trailing comma. ## Integer Literals Integer literals may use decimal, hexadecimal, binary, or octal digits: ```ebnf decimal-digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; binary-digit = "0" | "1" ; octal-digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" ; hex-digit = decimal-digit | "a" | "b" | "c" | "d" | "e" | "f" | "A" | "B" | "C" | "D" | "E" | "F" ; decimal-digits = decimal-digit, { decimal-digit } ; decimal-integer = decimal-digit, { decimal-digit | ("_", decimal-digit) } ; hex-integer = ("0x" | "0X"), hex-digit, { hex-digit | ("_", hex-digit) } ; binary-integer = ("0b" | "0B"), binary-digit, { binary-digit | ("_", binary-digit) } ; octal-integer = ("0o" | "0O"), octal-digit, { octal-digit | ("_", octal-digit) } ; INTEGER = decimal-integer | hex-integer | binary-integer | octal-integer ; ``` Examples include `0`, `42`, `1_000_000`, `0xFF`, `0b1010_0110`, and `0o755`. Hexadecimal digits are case-insensitive. An underscore is valid only between two digits from the literal's selected base. It cannot follow the prefix, begin or end the digit sequence, or repeat without an intervening digit. Separators and base prefixes do not apply to floating-point or duration literals. The lexical value must fit an unsigned 128-bit integer. Static checking selects an expected integer type when available and verifies that the value fits. It may instead select an expected `float32` or `float64` when the integer's value is exactly representable in that type; otherwise the literal defaults to `int64`. The source spelling `int` is an alias for `int64`. `-0x7F` is not one signed token. It is unary `-` applied to the positive integer literal `0x7F`. ## Floating-Point Literals Floating literals use a required fractional digit or an exponent: ```ebnf EXPONENT = ("e" | "E"), [ "+" | "-" ], decimal-digits ; FLOAT = decimal-digits, ".", decimal-digits, [ EXPONENT ] | decimal-digits, EXPONENT ; ``` Valid examples include `1.0`, `0.25`, `1e3`, `2.5e-1`, and `3E+4`. `.5` and `3.` are not floating literals. The lexical value must be finite as an `f64`. Static checking defaults it to `float64` or adopts an expected `float32`/`float64` type. ## Duration Literals A duration literal is a non-negative integral count followed immediately by `ms`, `s`, or `m`: ```ebnf DURATION = decimal-digits, ("ms" | "s" | "m") ; ``` `10ms`, `2s`, and `1m` represent 10, 2,000, and 60,000 milliseconds respectively and have type `Duration`. The lexer stores the exact value as signed 128-bit nanoseconds, so suffix scaling must fit that range. A duration literal itself is always non-negative and integral in its written unit. There is no `ns` suffix, fractional literal such as `1.5ms`, or unary `-Duration`; use the signed constructors and checked binary Duration operators described in [Expressions](/manual/expressions#arithmetic-and-comparison) for computed or negative values. ## Boolean And `None` `true` and `false` are the two `bool` literals. They are lowercase. `None` is lexically an identifier but statically denotes the unit value of type `None`, or the payload-free `Option.None` variant when an expected `Option[T]` type resolves the meaning. There is no null value distinct from these typed forms. ## str Literals Ordinary string literals use matching single or double quote delimiters and are single-line: ```aura double = "Aura" single = 'Aura' apostrophe = 'Aura\'s strings' quotation = 'the compiler said "ready"' ``` Both delimiters produce a `str` and support the same escapes: | Escape | Decoded value | | --- | --- | | `\n` | Line feed | | `\t` | Tab | | `\"` | Double quote | | `\'` | Single quote | | `\\` | Backslash | | `\0` | NUL | | `\xHH` | Scalar from exactly two hexadecimal digits | | `\u{H...}` | Unicode scalar from one or more hexadecimal digits | Unknown escapes, invalid Unicode scalars, missing hexadecimal digits, and missing or mismatched closing quotes are lexical errors. A one-character literal such as `'x'` is a `str`, not a distinct character type. Three matching quotes create an exact multiline string: ```aura prompt = """Classify this request. Return one label and one reason. """ ``` The value contains every scalar between the delimiters. Aura performs no dedent, margin calculation, trimming, leading-newline removal, trailing-newline removal, or Unicode normalization. Escapes retain their ordinary meaning. Physical tabs inside the delimiters are content. A lowercase `r` creates a single-line raw string: ```aura path = r"C:\agents\run" pattern = r'\d+\.\d+' ``` Backslashes are content. A backslash may retain the active quote inside the value, and both characters remain. A raw string cannot end in an odd run of backslashes or contain a physical newline. Raw triple strings and byte strings are unavailable. A string literal has type `str`. See [Types](/manual/types) for ownership and [Execution Model](/manual/execution-model#evaluation-order) for expression evaluation order. ## F-Strings An f-string begins with `f"` and is double-quoted and single-line: ```aura name = "aura" print(f"hello {name}") ``` Text inside `{` and `}` is parsed as an ordinary Aura expression. Interpolations may contain indexing, calls, nested braces used by expressions, and either form of ordinary string literal, including braces inside those strings. Empty or syntactically invalid interpolations are rejected. Use two consecutive opening braces for a literal opening brace. Two consecutive closing braces decode to one literal closing brace; Aura 0.3 also treats a lone closing brace outside an interpolation as literal text: ```aura print(f"{{name}} = {name}") ``` F-strings support the same escapes as ordinary strings and remain double-quoted. An interpolation accepts a static format specification after a top-level colon: ```aura def main(): count: int64 = 1234567 ratio: float32 = 0.875 label = "Aura" print(f"{count:>12,d}") print(f"{ratio:+.2%}") print(f"{label:·^16.8s}") ``` The grammar is `[[fill]align][sign][width][,][.precision][type]`. Alignment is `<`, `^`, or `>` and type is `d`, `f`, `e`, `x`, `X`, `b`, `o`, `%`, or `s`. Strings default to left alignment and numbers to right alignment. Width counts Unicode scalars and never truncates. `s` precision is the maximum scalar count. Numeric precision uses ties-to-even rounding. Decimal grouping is available with `d`, `f`, and `%`. Width and precision are limited to `1_000_000`. A numeric width beginning with `0` selects zero padding. With no explicit alignment, zeros follow the sign, so `f"{-1.25:09.3f}"` produces `-0001.250`. Aura parses the complete interpolation expression before recognizing the top-level separator. Colons inside nested slices, dictionaries, calls, and collection literals remain part of the expression. Dynamic specifications, nested fields, conversion flags, and single-quoted f-strings are unavailable. `rf"..."`, `fr"..."`, and `f"""..."""` receive `AU1002` guidance to the supported single-line `f"..."` spelling. Interpolations evaluate once from left to right and the result is an owned `str`. ## Complexity Limits The maintained parser rejects excessive nesting and expression chains instead of risking host stack exhaustion. The current 128-level limits for expressions, types, patterns, statements, f-string braces, and chained operators are defined in [Grammar](/manual/grammar#syntactic-complexity-limits) and summarized in [Current Limits](/manual/current-limits). ## Grammar The token productions, reserved words, indentation protocol, delimiters, operators, and literal forms in this chapter are normative. Their composition into declarations, statements, patterns, types, and expressions is defined by the complete [Grammar](/manual/grammar). A source spelling not accepted by those productions is not an extension point. ## Typing Rules Lexing does not assign expression types, but it preserves the literal kind and mathematical or decoded value used by static checking. Integer literals may later adopt an exact expected integer or floating type; floating literals may adopt `float32` or `float64`; duration, Boolean, ordinary-string, and f-string tokens enter checking as `Duration`, `bool`, `str`, and an interpolated `str` expression respectively. No lexical spelling performs a runtime coercion. ## Runtime Semantics Tokenization has no runtime side effects. Decoded string scalars, literal numbers, duration nanoseconds, and f-string text segments become constants or MIR inputs only after the complete module has parsed and checked. A lexical failure prevents execution. Suppressing a physical line boundary has no runtime action. The resulting token sequence evaluates exactly as the same tokens written on one physical line. ## Ownership And Evaluation Order Tokens do not own or borrow runtime values. Ordinary and f-string literals produce owned values when evaluated; f-string interpolation expressions run left to right as specified by [Expressions](/manual/expressions). Indentation, comments, and physical-line markers have no runtime evaluation. Physical-line placement and continuation indentation do not create, extend, or end a borrow and do not change move/copy decisions. Source-order evaluation follows the joined logical token sequence. ## Diagnostics `AU1001` reports invalid lexical input, including physical tabs, invalid escapes, malformed or unterminated literals, invalid characters, invalid block indentation, and delimiter pairing failures. An unexpected closer is primary at that closer. A mismatched closer names the expected delimiter and carries a labeled secondary span for its opener. An unclosed delimiter reports at EOF and likewise labels its opener. `AU1002` reports the focused single-quoted f-string spelling and directs the author to `f"..."`. Once tokenization succeeds, syntax failures belong to parser code `AU1101` rather than this page. ## Backend Support The compiler tokenizes source once before MIR lowering or native code generation. The MIR runtime and direct native backend therefore accept exactly the same lexical language; there is no backend-specific lexer. ## Limits And Implementation-Defined Behavior Identifiers are ASCII, source is UTF-8, physical tabs are rejected outside triple-quoted string content, continuation requires an unmatched source delimiter, ordinary lists reject trailing commas, backslash continuation and multiline f-strings are unavailable, and literal magnitude and parser-complexity caps are fixed by this chapter and [Current Limits](/manual/current-limits). Continuation indentation is not semantically significant, but delimiter matching, token spans, and the expression-match layout island are defined behavior rather than implementation choices. ## Status The forms described as accepted above are implemented. Delimiter continuation and its layout/diagnostic policy are accepted under ADR-0025. Raw triple strings, raw f-strings, byte strings, single-quoted f-strings, block comments, semicolons, ordinary trailing commas other than the required singleton-tuple comma, backslash continuation, and multiline f-string literals are unavailable. ## Source: docs/manual/math.md # Math Module The `math` module provides exact binary64 constants plus scalar `float64` rounding, exponentiation, exponential, logarithmic, and trigonometric functions. Every function input is explicitly `float64`; the module performs no implicit numeric conversion. ## Public API | API | Signature | Contract | | --- | --- | --- | | `math.pi` | `float64` constant | Nearest binary64 value to pi, bits `0x400921fb54442d18`. | | `math.e` | `float64` constant | Nearest binary64 value to Euler's number, bits `0x4005bf0a8b145769`. | | `math.inf` | `float64` constant | Positive infinity, bits `0x7ff0000000000000`. | | `math.nan` | `float64` constant | Canonical quiet NaN, bits `0x7ff8000000000000`. | | `math.floor` | `floor(value: float64) -> int64` | Greatest integer less than or equal to `value`. | | `math.ceil` | `ceil(value: float64) -> int64` | Least integer greater than or equal to `value`. | | `math.trunc` | `trunc(value: float64) -> int64` | Integer obtained by discarding the fractional part toward zero. | | `math.pow` | `pow(base: float64, exponent: float64) -> float64` | Binary64 exponentiation under the exceptional-value policy below. | | `math.exp` | `exp(value: float64) -> float64` | Binary64 base-e exponential. | | `math.log` | `log(value: float64) -> float64` | Binary64 natural logarithm. | | `math.log2` | `log2(value: float64) -> float64` | Binary64 base-2 logarithm. | | `math.log10` | `log10(value: float64) -> float64` | Binary64 base-10 logarithm. | | `math.sin` | `sin(value: float64) -> float64` | Binary64 sine with the input measured in radians. | | `math.cos` | `cos(value: float64) -> float64` | Binary64 cosine with the input measured in radians. | | `math.tan` | `tan(value: float64) -> float64` | Binary64 tangent with the input measured in radians. | ## IEEE-754, Domain, And Overflow Policy This table is normative for every maintained backend. | Operation or input | Result | | --- | --- | | `floor`, `ceil`, or `trunc` of finite in-range `x` | Corresponding mathematical integer as `int64`. | | `floor`, `ceil`, or `trunc` of NaN, infinity, or an out-of-range finite value | `AU4002`. | | `exp(nan)` | NaN. | | `exp(+inf)` / `exp(-inf)` | `+inf` / `+0.0`. | | `exp` of a finite input with finite representable result | Nearest binary64 result. | | `exp` of a finite input whose result overflows | `AU4002`. Underflow produces the correctly signed zero or subnormal value. | | `log* (nan)` | NaN. | | `log* (+inf)` | `+inf`. | | `log* (x)` for finite `x <= 0.0`, including either zero | `AU4001` domain error. | | `sin`, `cos`, or `tan` of NaN | NaN. | | `sin`, `cos`, or `tan` of either infinity | `AU4001` domain error. | | `pow(x, 0.0)` for any `x`, including NaN | `1.0`. | | `pow(1.0, y)` for any `y`, including NaN | `1.0`. | | `pow(nan, y)` or `pow(x, nan)` outside the two identities above | NaN. | | `pow(0.0, y)` for finite `y < 0.0` | `AU4001` domain error. | | `pow(x, y)` for finite `x < 0.0` and finite non-integral `y` | `AU4001` domain error. | | Finite `pow` inputs with an infinite-magnitude mathematical result | `AU4002`. | | Other libm results, including documented infinities from infinite inputs | The corresponding IEEE-754 binary64 value. | An exponent is integral for the negative-base rule when its binary64 value is finite and exactly equal to its truncation. Signed zero follows IEEE-754 sign rules. Subnormal inputs and results are preserved. Aura does not enable flush-to-zero as a language behavior. Finite transcendental results use the maintained target's binary64 math implementation. Portable programs may depend on the classifications and identities in the table. Last-bit finite approximation can vary between maintained target and libm pairs. ## Example ```aura import math def main() -> int32: print(math.pi) print(math.e) print(math.inf) print(math.nan) print(math.floor(-1.25)) print(math.ceil(-1.25)) print(math.trunc(-1.75)) print(math.pow(2.0, -3.0)) print(math.exp(0.0)) print(math.log(1.0)) print(math.log2(8.0)) print(math.log10(1000.0)) print(math.sin(0.0)) print(math.cos(0.0)) print(math.tan(0.0)) return 0 ``` This program prints: ```text 3.141592653589793 2.718281828459045 inf NaN -2 -1 -1 0.125 1.0 0.0 3.0 3.0 0.0 1.0 0.0 ``` The maintained program is `examples/numbers/scalar_math.au`. ## Grammar The module adds no source-language grammar. `import math`, qualified member access, calls, named arguments, and negative numeric expressions use the ordinary forms defined by this Manual. ## Typing Rules The four constants have exact type `float64` and the bit patterns shown in the Public API table. They support qualified reads and direct imports with ordinary import aliases. Every function parameter has type `float64`. `floor`, `ceil`, and `trunc` return `int64`; every other function returns `float64`. A value of any other numeric type requires an explicit conversion before the call. Normal argument-count, argument-name, and exact-type checks apply. The module namespace contains every constant and function in the Public API table. ## Runtime Semantics Each function applies the IEEE-754, domain, and overflow policy above. `floor`, `ceil`, and `trunc` first compute the specified mathematical integer and then require it to fit `int64`. `pow` classifies its identities, NaN, domain, and finite-overflow cases before returning the maintained binary64 result. The exponential, logarithmic, and trigonometric functions preserve the table's NaN, infinity, signed-zero, and subnormal outcomes. Each constant has one immutable module storage location initialized once before application execution. Every read uses that shared location. Copy-scalar use preserves the stored binary64 bits, including the canonical NaN payload. For one maintained target and math implementation, repeated calls with the same binary64 inputs produce the same binary64 result. The functions perform no I/O and observe no process-global mutable state. ## Ownership And Evaluation Order Constant reads are shared and cannot be assigned or used through mutable access. Call arguments evaluate left to right and exactly once before the function executes. `math.pow` evaluates `base` before `exponent`. Every parameter and result is a Copy scalar, so calls do not move or mutate caller bindings. A failed call leaves all already completed argument effects observable and produces no result value. ## Diagnostics - `AU2001` reports an unknown module member. - `AU2002` reports an argument whose type is not exactly `float64`. - `AU2004` reports invalid argument binding, including a wrong argument count or name. - `AU4001` reports the domain errors named in the normative table. - `AU4002` reports a finite overflow or a rounding result that cannot be represented as `int64`. ## Backend Support All listed functions are supported by the MIR runtime and direct native backend. Both backends use shared exceptional-value classification and must agree on result classification, signed zero, and diagnostic code. They use the same maintained host math implementation for finite results on one target. ## Limits And Implementation-Defined Behavior The module is scalar and `float64` only. It provides no complex, decimal, arbitrary-precision, vectorized, combinatorial, or random operations. The logarithm functions accept one value and do not accept an alternate base. The final bits of finite transcendental approximations can vary across target and libm pairs. The exact host diagnostic rendering around an `AU4001` or `AU4002` failure follows the general runtime diagnostic contract. ## Status The constants, functions, exact bits and signatures, exceptional-value classifications, initialization and evaluation order, diagnostics, and MIR/direct backend behavior on this page are implemented and maintained in Aura 0.3. ## Source: docs/manual/names-and-scopes.md # Names And Scopes Aura resolves names statically. A name denotes a local binding, parameter, pattern payload, type parameter, module, function, class, enum, trait, enum variant through a qualified path, or maintained builtin. Name resolution never falls back to dynamic lookup. ## Identifiers And Reserved Names Identifiers are ASCII letters or `_`, followed by ASCII letters, digits, or `_`. The lexer reserves the words listed by [Lexical Structure](/manual/lexical-structure). `copy`, `self`, `None`, and `_` are contextual identifiers whose special meaning depends on their grammatical position. Builtin type names and builtin top-level function names cannot be redefined by user items. `Self` is reserved within trait and implementation type contexts and cannot be declared as a type parameter. ## Module Scope One `.au` file defines one module scope. Its top-level item namespace contains: - classes - enums - functions - extern functions and extern opaque handle types - traits - module constants - imported names - imported module aliases These categories share the same top-level item name space. A local item cannot reuse a name already imported or declared as another item kind. Trait implementation blocks do not introduce a top-level name; they attach behavior to an existing trait/type combination. Imports are module-level regardless of their textual position in the file. They are resolved before static checking of function bodies and top-level statements. ## Module Constants A bare binding at module level declares an immutable module constant. The initializer is required. The annotation is optional, and `public` exposes the constant to qualified imports and from-imports. max_attempts: int64 = 5 service_name = "planner" public default_region = "eu-west" def main(): print(service_name) Functions and types are available to every constant initializer regardless of their textual item position. Constants become available in declaration order. An initializer may read an earlier constant in the same module. Reading itself or a later constant is `AU2001` use before initialization. Aura initializes reachable modules before entry execution. Dependencies run before importers, sibling dependencies follow first import order, and constants within one module follow declaration order. A module reached through several imports initializes once. Initializer failure prevents entry execution. Copy-typed reads produce ordinary copied values. A non-Copy read provides shared access to the one value stored by its defining module. It cannot move the value into owned storage, pass it to an `own` parameter, or request mutable access. Use an explicit supported `.clone()` or constructor when independent owned data is required. Module storage is immutable. Module-level `mut`, reassignment, compound assignment, and mutable access are `AU3003` errors. Stateful application data belongs in a local value owned by `main` or another explicit owner. An entry script's top-level local environment is separate from module storage. The statement `mut count = 0` creates a mutable local in that environment; later `count = count + 1` and `count += 1` both reassign it. A fresh bare top-level binding still declares a module constant. That constant cannot read a top-level script local because constant initialization happens before entry statements execute. Use `mut` on the fresh binding to make it another script local, or place the computation in `main`. ## Imports An unaliased module import binds the first path component as a namespace: ```aura import tools.text value = tools.text.parse("input") ``` An aliased module import binds the complete module under the alias and does not introduce the path's first component: ```aura import tools.text as text_tools value = text_tools.parse("input") ``` From-imports bind the requested public items directly: ```aura from tools.text import parse, ResultRow ``` Each from-import entry may bind a local alias. Direct and aliased entries may appear together: ```aura from tools.text import parse as parse_text, ResultRow ``` An alias occupies the same module-level namespace as items, module constants, and other imports. Duplicate aliases, collisions, reserved names, `_`, and duplicate imports of one target in a declaration are rejected. Aliasing changes only the local spelling. The target keeps its defining-module and nominal identity, visibility, trait implementations, initialization storage, and documentation target. An import path consists of dot-separated identifiers and maps to a module path inside the current package/dependency graph. Filesystem path traversal is not part of import syntax. Package roots and dependency aliases are described by [Packages](/manual/packages). Only `public` top-level classes, enums, Aura functions, extern functions, extern opaque handle types, traits, and module constants may be imported from another module. Class fields and methods also have individual visibility. A non-public member remains accessible inside its defining module but is rejected across a module boundary. An alias does not bypass that boundary. Imports do not mean "include this file". Imported declarations retain their defining module identity, which is used for private access, qualified type names, diagnostics, trait implementations, and go-to-definition. ## Type Names Types are resolved from: 1. type parameters in the innermost declaration 2. `Self` in a trait or trait-implementation method where it is permitted 3. local and directly imported class, enum, extern opaque handle, and trait names 4. module-qualified public types 5. builtin and builtin-module type names Type arguments must have exactly the arity declared by the target type. Generic type parameters are in scope throughout their owning class, enum, function, trait, implementation, or method as appropriate. A method may add type parameters to those inherited from its enclosing declaration, but a parameter name cannot duplicate another parameter in the same declaration and `Self` cannot be reused. The implementation rejects duplicate type parameters. Type parameter shadowing between an enclosing generic declaration and an inner method is not a portable language technique; declarations should use distinct names. ## Function And Method Scope A function body begins with bindings for its ordinary parameters. A method body additionally binds the contextual receiver `self` when a receiver was declared. Parameter names, `self`, local bindings, loop bindings, `with` bindings, and pattern bindings occupy the function's value namespace. A use is valid only after the binding has been introduced on the current control-flow path. Aura 0.3 does not support local function, class, enum, or trait declarations. Items are module-level or members of their permitted enclosing declaration. ## Local Bindings An assignment to a previously unseen simple name introduces a binding: ```aura def main(): name = "Aura" mut count: int32 = 0 ``` The initializer is checked before the new name becomes available. A binding without `mut` is immutable. A later assignment to an existing name is reassignment, not a new shadowing declaration, and is valid only for a mutable place with the same type. `mut` is permitted only when introducing a simple local binding. It cannot redeclare an existing name and cannot prefix a field or index assignment. Bindings introduced inside an `if` branch, loop body, match arm, or `with` body do not escape that body. Effects on ownership state of an outer binding are merged conservatively at control-flow joins. ## Lambda Scope Each lambda creates one parameter scope for its expression body. Parameters become visible only after the colon, cannot duplicate one another, and follow the ordinary no-shadowing rules. The body may resolve outer locals and owned parameters. Those resolved owned values become by-value captures; module items, builtins, imports, and the lambda's own parameters do not. A bare or `mut` parameter of an enclosing function is a capability into the caller's storage rather than an owned value and cannot be captured. Lambda parameters and captured outer names retain hover and definition identity in compiler analysis. See [Closures](/manual/closures). ## Comprehension Scope A comprehension establishes one nested expression scope. Clause targets enter that scope progressively in runtime order even though the output expression is written before them: pairs = [ (left, right) for left in values for right in values if right > left ] The iterable expression of a clause cannot see that clause's own target. Once bound, the target is visible in the clause's filters, every later iterable and filter, and the output key/value or element expression. An earlier target is therefore visible while selecting an inner iterable. Targets use the ordinary `loop-target` grammar and no-shadowing rule. A target cannot reuse a local already visible outside the comprehension or an earlier clause target. Tuple target leaves enter together after the source item is selected. No comprehension target is visible after the closing `]` or `}`. A lambda enclosing a comprehension captures names used by source, filter, and output expressions under ADR-0037, but it does not capture comprehension targets: those are local to the lambda body. A lambda created inside a comprehension may capture a currently bound target only when ADR-0037 permits that by-value capture. ## No-Shadowing Rules Aura deliberately rejects several ambiguous forms of shadowing: - a `for` binding cannot reuse a visible name - a comprehension target cannot reuse a visible name or an earlier clause target - a `with` binding cannot reuse a visible name - a match payload binding cannot reuse a visible name - a second `mut name = ...` cannot redeclare `name` - assignment to an existing immutable name is not interpreted as a new inner binding This means a reader can normally associate one local spelling with one logical binding for the duration of a function. Use a distinct name when transforming a value. ## Block Scope And Control Flow Each branch, loop body, match arm, and `with` body is checked with a child view of the current local environment. Reads and writes must be valid on every reachable path. When control flow joins, a move or partial move that may have happened on any reachable path makes the affected outer place unavailable unless it was definitely reinitialized on all relevant paths. A binding created only inside a child block is never introduced into the parent scope. The compiler recognizes constant `true`, constant `false`, and their grouped/`not` forms for limited reachability and loop-flow reasoning. Programs should still express clear control flow rather than depend on aggressive compile-time evaluation. ## Pattern Scope Each match arm has its own payload-binding scope. Bindings become available only in that arm's body or value expression. ```aura match result: case Result.Ok(value): print(value) case Result.Err(message): print(message) ``` `_` binds nothing. A lowercase unqualified pattern name is a binding pattern. Variant patterns may be unqualified when the scrutinee type supplies the enum identity; otherwise they use `Enum.Variant` or a module-qualified path. Borrowed and mutable-borrowed matches attach borrow provenance to payload bindings. Those bindings cannot be used after a mutation invalidates the matched place. ## Class And Trait Member Lookup For a class value, member lookup considers fields and methods declared by the class. Public access rules apply across modules. A method call also considers trait methods from implementations visible through the current module/package context. Trait method selection uses the receiver type, explicit or inferred trait arguments, type-parameter bounds, and implementation specificity. Multiple equally applicable implementations are ambiguous and MUST be rejected instead of selected by source order. Associated methods are methods without a receiver and are referenced through the type, for example `Worker.create(...)`. Instance methods require a receiver compatible with their declared shared (`self`), consuming (`own self`), or mutable (`mut self`) contract. ## Builtin Names And Modules Top-level builtin functions such as `print`, `range`, `sleep`, and `select` are available without import. Builtin enum names such as `SelectOutcome` are also reserved and available without import. Builtin modules such as `fs`, `io`, `net`, `process`, `random`, `sys`, `path`, `bytes`, `json`, `toml`, `log`, `trace`, and `metrics` must be imported before their module-qualified members are used. `random.Rng` is the builtin type and constructor spelling for a deterministic generator. Its methods remain module-qualified through the receiver type; there is no implicit global random-stream name. The secure operations are `random.secure_int` and `random.secure_bytes`. Builtin behavior follows declaration origin, not a coincidental module/type spelling. A user source file whose logical module name is `random` may declare its own `Rng` class; that class remains an ordinary user class in checking, analysis, MIR lowering, clone-safety classification, and both backends. Builtin enum types such as `Option`, `Result`, `QueueReceive`, and `process.Error` use the same qualified-member model as user enums. Short-form variant patterns and constructors are available only where the checker can determine a unique expected enum type. ## Top-Level Statement Scope An entry module may contain executable top-level statements instead of a local `main`. Those statements share one top-level local environment and execute in source order after reachable module constants finish initialization. A `mut` simple-name assignment declares a mutable top-level local. Later plain and compound assignments to that name remain in the statement stream and update the same local. A bare assignment to a new name is a module constant, even when it appears after an executable statement in source order. Imported modules contribute items and eagerly initialized constants. Their top-level executable statements are checked as source and do not run as import side effects. Reusable executable work belongs inside public functions. ## Grammar Identifier spelling is defined by [Lexical Structure](/manual/lexical-structure). The binding positions are module declarations and imports, function and lambda parameters, receivers, simple-name assignments, statement-`for`, comprehension-clause, and `with` targets, match payloads, and generic parameter lists in the [Grammar](/manual/grammar). Member access uses a dot-separated syntactic path; it does not add dynamic lookup syntax. ## Typing Rules Every value and type name is resolved statically in the priority and namespace rules above. A resolved value binding carries one fixed type. Reassignment requires the existing mutable binding and the same type; it never creates a shadow. Generic and `Self` resolution occurs before substitution and bound checking. Ambiguous trait implementations and unavailable or private names are rejected rather than selected by source order. ## Runtime Semantics Local and parameter references read their statically selected storage place; module, type, function, and associated-member names select compiler metadata and do not perform a runtime dictionary lookup. Module constants initialize once in the dependency and source order defined above. Entry-module executable statements then run in source order. Imports do not execute imported top-level statements as initialization side effects. ## Ownership And Evaluation Order Resolving a name has no side effect, but evaluating the resolved place may copy, borrow, mutate, or move it according to its type and the surrounding expression. Initializers are evaluated before a new local enters scope. Block and pattern scopes are entered only for the selected runtime path; ownership state from continuing paths is merged conservatively by the checker. Comprehension clause scopes enter progressively. A target is established only after its iterable value is selected and only for the current item. Filters and inner clauses that are not reached establish no bindings. The complete scope is discarded with the expression. ## Diagnostics `AU2001` reports unknown, unavailable, or unresolved names, including a module constant read before initialization and a comprehension target used outside its expression. `AU2002` covers type-name arity and related expected-type failures. `AU2999` covers duplicate, reserved, private, ambiguous, or otherwise invalid name/scope declarations not assigned a narrower code. Reads of places invalidated after resolution use `AU3001` for a moved place or attempted move from non-Copy module storage, `AU3002` for a borrow conflict, `AU3003` for an immutable place or mutable module-storage request, and `AU3004` for an invalid ownership mode, with related source spans and repair guidance where applicable. Runtime module initialization re-entry is `AU4001`. ## Backend Support Name, visibility, trait, module, and scope resolution are compiler-front-end operations shared by MIR execution and direct native builds. Both backends receive the same resolved targets and substituted types. Compiler-backed LSP hover, definitions, and diagnostics use that same resolution result. ## Limits And Implementation-Defined Behavior Local declarations and comprehension targets cannot shadow visible locals in the positions listed above; items cannot be nested in function suites; wildcard or relative-dot imports and imported top-level execution are unavailable. Import aliases remain subject to the ordinary no-collision and visibility rules. Package filesystem mapping is specified by [Packages](/manual/packages), not left to implementation-defined name lookup. ## Status Static lexical scope, module imports and aliases, module constants, visibility, generic/type namespaces, member lookup, comprehension scopes, and the documented entry-module top-level scope are implemented. Dynamic names, reflection-based lookup, nested items, import side effects, wildcard imports, and user-selectable shadowing are unavailable. No future name-resolution form is implied by an identifier that happens to lex today. ## Source: docs/manual/network.md # Network Module The `net` module exposes scheduler-aware networking resources: - TCP listeners and streams - UDP sockets and datagrams - HTTP listeners, exchanges, and client responses - WebSocket listeners and sockets - Unix domain sockets on Unix hosts - TLS listeners and streams ```aura import net import io ``` Most operations return `Result[..., io.Error]`. Waiting operations usually accept `timeout: Duration = ...`; omitting it means no caller deadline unless a protocol-specific hard limit is stated below. Pass explicit timeouts for services that need bounded latency or clean shutdown behavior. An explicit timeout must be non-negative, fit the host timer range, and produce a representable deadline; otherwise the operation returns `io.Error.InvalidInput`. Deadline overflow never means no deadline. This input policy is accepted under ADR-0019. Hostname resolution, socket binding, UDP destination resolution, and blocking TCP or Unix connect syscalls run on Aura's generic blocking-I/O pool rather than on the lightweight-task scheduler. The pool has a configurable worker count and optional pending-queue bound; a full bounded queue parks Aura callers through FIFO scheduler-aware admission. A connect timeout is one end-to-end budget: it includes admission and DNS resolution, is shared by every resolved-address attempt, and then covers any remaining TLS, HTTP, or WebSocket handshake work. Cancellation or expiry before acceptance prevents submission. Accepted host work cannot be interrupted, may finish later, and has its result discarded safely. Text reads decode UTF-8 strictly and return `io.Error.InvalidData` for invalid bytes. TCP, Unix, and TLS deadlines return `io.Error.TimedOut`; a UDP receive deadline returns `Ok(None)`. Cancellation is reported as `io.Error.Cancelled` where the operation participates in scheduler cancellation. ## Constructors | API | Signature | Contract | | --- | --- | --- | | `net.connect` | `connect(address: str) -> Result[net.TcpStream, io.Error]` | Opens a TCP connection to `host:port`. | | `net.connect_timeout` | `connect_timeout(address: str, timeout: Duration) -> Result[net.TcpStream, io.Error]` | Opens a TCP connection with a deadline. | | `net.listen` | `listen(address: str) -> Result[net.TcpListener, io.Error]` | Binds a TCP listener. Use `127.0.0.1:0` to request an available local port. | | `net.udp_bind` | `udp_bind(address: str) -> Result[net.UdpSocket, io.Error]` | Binds a UDP socket. | | `net.http_listen` | `http_listen(address: str) -> Result[net.HttpListener, io.Error]` | Binds a simple HTTP listener. | | `net.websocket_listen` | `websocket_listen(address: str) -> Result[net.WebSocketListener, io.Error]` | Binds a WebSocket listener. | | `net.websocket_connect` | `websocket_connect(url: str) -> Result[net.WebSocket, io.Error]` | Connects to a WebSocket URL. | | `net.websocket_connect_timeout` | `websocket_connect_timeout(url: str, timeout: Duration) -> Result[net.WebSocket, io.Error]` | Connects to a WebSocket URL with a deadline. | | `net.unix_listen` | `unix_listen(path: str) -> Result[net.UnixListener, io.Error]` | Binds a Unix domain socket path on Unix hosts. | | `net.unix_connect` | `unix_connect(path: str) -> Result[net.UnixStream, io.Error]` | Connects to a Unix domain socket path. | | `net.unix_connect_timeout` | `unix_connect_timeout(path: str, timeout: Duration) -> Result[net.UnixStream, io.Error]` | Connects to a Unix domain socket path with a deadline. | | `net.tls_listen` | `tls_listen(address: str, cert_pem_path: str, key_pem_path: str) -> Result[net.TlsListener, io.Error]` | Binds a TLS listener using PEM certificate and key files. | | `net.tls_connect` | `tls_connect(address: str, server_name: str, ca_pem_path: str) -> Result[net.TlsStream, io.Error]` | Connects with TLS verification using a CA PEM file. | | `net.tls_connect_timeout` | `tls_connect_timeout(address: str, server_name: str, ca_pem_path: str, timeout: Duration) -> Result[net.TlsStream, io.Error]` | Connects with TLS verification and a deadline. | ## TCP `net.TcpListener`: | API | Signature | Contract | | --- | --- | --- | | `accept` | `accept(timeout: Duration = ...) -> Result[net.TcpStream, io.Error]` | Waits for the next incoming connection. | | `local_addr` | `local_addr() -> Result[str, io.Error]` | Returns the bound local address. | | `close` | `close() -> None` | Closes the listener. | `net.TcpStream`: | API | Signature | Contract | | --- | --- | --- | | `read_all` | `read_all(timeout: Duration = ...) -> Result[str, io.Error]` | Reads strict UTF-8 text until EOF, capped at 64 MiB. Use byte APIs for arbitrary data. | | `read_line` | `read_line(timeout: Duration = ...) -> Result[Option[str], io.Error]` | Reads one strict UTF-8 line without its trailing LF/CRLF, capped at 64 MiB. Returns `Ok(None)` only on EOF. | | `read_bytes` | `read_bytes(max_bytes: int32, timeout: Duration = ...) -> Result[Option[list[uint8]], io.Error]` | Reads up to `max_bytes` raw bytes. The count must be in `1..=67108864`; `Ok(None)` means EOF. | | `read_exact` | `read_exact(count: int32, timeout: Duration = ...) -> Result[list[uint8], io.Error]` | Reads exactly `count` bytes or returns an error. The count must be in `1..=67108864`. | | `write_all` | `write_all(text: str, timeout: Duration = ...) -> Result[None, io.Error]` | Writes all UTF-8 text. | | `write_bytes` | `write_bytes(bytes: list[uint8], timeout: Duration = ...) -> Result[None, io.Error]` | Writes all raw bytes. | | `flush` | `flush() -> Result[None, io.Error]` | Flushes pending stream writes. | | `local_addr` | `local_addr() -> Result[str, io.Error]` | Returns the local socket address. | | `peer_addr` | `peer_addr() -> Result[str, io.Error]` | Returns the peer socket address. | | `shutdown_read` | `shutdown_read() -> Result[None, io.Error]` | Shuts down the read half. | | `shutdown_write` | `shutdown_write() -> Result[None, io.Error]` | Shuts down the write half. | | `shutdown_both` | `shutdown_both() -> Result[None, io.Error]` | Shuts down both halves. | | `close` | `close() -> None` | Closes the stream handle. | Example echo handler: ```aura import io import net def handle(stream: own net.TcpStream) -> Result[None, io.Error]: with conn = stream: match try conn.read_line(timeout=5s): case Option.Some(line): try conn.write_all(line, timeout=5s) case Option.None: pass return Result.Ok(None) ``` ## UDP `net.UdpSocket`: | API | Signature | Contract | | --- | --- | --- | | `send_text` | `send_text(address: str, text: str, timeout: Duration = ...) -> Result[None, io.Error]` | Sends UTF-8 text to an address. | | `send_bytes` | `send_bytes(address: str, bytes: list[uint8], timeout: Duration = ...) -> Result[None, io.Error]` | Sends raw bytes to an address. | | `recv` | `recv(max_bytes: int32, timeout: Duration = ...) -> Result[Option[list[uint8]], io.Error]` | Receives bytes from a connected UDP socket. `Ok(None)` means the deadline expired. | | `recv_from` | `recv_from(max_bytes: int32, timeout: Duration = ...) -> Result[Option[net.UdpDatagram], io.Error]` | Receives a datagram plus source address. `Ok(None)` means the deadline expired. | | `local_addr` | `local_addr() -> Result[str, io.Error]` | Returns the local address. | | `peer_addr` | `peer_addr() -> Result[str, io.Error]` | Returns the connected peer address when available. | | `close` | `close() -> None` | Closes the socket handle. | `net.UdpDatagram`: | API | Signature | Contract | | --- | --- | --- | | `address` | `address() -> str` | Returns the source address. | | `bytes` | `bytes() -> list[uint8]` | Returns the datagram payload as raw bytes. | | `text` | `text() -> Result[str, io.Error]` | Decodes the payload as UTF-8 text. | UDP preserves datagram boundaries. `max_bytes` must be in `1..=65535`; zero or a larger request returns `io.Error.InvalidInput` before receiving. A receive with a small buffer may truncate data according to platform behavior. Sends larger than the host datagram limit return `InvalidInput` where the host exposes that condition. ## HTTP Server `net.HttpListener`: | API | Signature | Contract | | --- | --- | --- | | `accept` | `accept(timeout: Duration = ...) -> Result[net.HttpExchange, io.Error]` | Waits for the next HTTP request and returns an exchange. | | `local_addr` | `local_addr() -> Result[str, io.Error]` | Returns the bound local address. | | `close` | `close() -> None` | Closes the listener. | `net.HttpExchange`: | API | Signature | Contract | | --- | --- | --- | | `method` | `method() -> str` | Returns the request method. | | `path` | `path() -> str` | Returns the request path. | | `headers` | `headers() -> dict[str, str]` | Returns request headers. | | `body_text` | `body_text() -> Result[str, io.Error]` | Decodes the request body as UTF-8. | | `body_bytes` | `body_bytes() -> list[uint8]` | Returns the raw request body. | | `respond_text` | `respond_text(status: int32, text: own str, headers: own dict[str, str]) -> Result[None, io.Error]` | Consumes and sends a text response. | | `respond_bytes` | `respond_bytes(status: int32, bytes: own list[uint8], headers: own dict[str, str]) -> Result[None, io.Error]` | Consumes and sends a byte response. | Malformed HTTP requests are rejected by the listener path and do not permanently poison the listener. Content-length and chunked request bodies are supported. An incoming parsed HTTP message is limited to 16 MiB of wire data and 64 headers; oversized or invalid requests are surfaced as HTTP errors where the protocol allows it. Headers are exposed as `dict[str, str]`. This boundary cannot faithfully represent repeated fields such as multiple `set-Cookie` lines. The current conversion can expose duplicate equal keys internally despite the normal `dict` uniqueness rule, so applications that require lossless or canonical repeated-header handling must not use this 0.3 high-level HTTP surface. ## HTTP Client | API | Signature | Contract | | --- | --- | --- | | `net.http_request_text` | `http_request_text(method: str, url: str, body: str, headers: dict[str, str]) -> Result[net.HttpResponse, io.Error]` | Sends an HTTP request with a text body. | | `net.http_request_text_timeout` | `http_request_text_timeout(method: str, url: str, body: str, headers: dict[str, str], timeout: Duration) -> Result[net.HttpResponse, io.Error]` | Sends a text request with a deadline. | | `net.http_request_bytes` | `http_request_bytes(method: str, url: str, bytes: list[uint8], headers: dict[str, str]) -> Result[net.HttpResponse, io.Error]` | Sends an HTTP request with a byte body. | | `net.http_request_bytes_timeout` | `http_request_bytes_timeout(method: str, url: str, bytes: list[uint8], headers: dict[str, str], timeout: Duration) -> Result[net.HttpResponse, io.Error]` | Sends a byte request with a deadline. | `net.HttpResponse`: | API | Signature | Contract | | --- | --- | --- | | `status` | `status() -> int32` | Returns the numeric status code. | | `reason` | `reason() -> str` | Returns the reason phrase. | | `headers` | `headers() -> dict[str, str]` | Returns response headers. | | `text` | `text() -> Result[str, io.Error]` | Decodes the body as UTF-8. | | `bytes` | `bytes() -> list[uint8]` | Returns the raw response body. | Use byte request and response APIs for binary payloads or unknown encodings. Client URLs may use `http://` or certificate-validated `https://`; responses support content length, chunked transfer encoding, and connection-close framing. The same 16 MiB incoming-message and 64-header limits apply. Redirect following, connection pooling, HTTP/2, proxies, decompression, and custom-CA arguments on the high-level HTTP helpers are not part of 0.3. ## WebSocket `net.WebSocketListener`: | API | Signature | Contract | | --- | --- | --- | | `accept` | `accept(timeout: Duration = ...) -> Result[net.WebSocket, io.Error]` | Waits for the next WebSocket connection. | | `local_addr` | `local_addr() -> Result[str, io.Error]` | Returns the bound local address. | `net.WebSocketListener` has no explicit `close()` member in Aura 0.3. It is released when its value is dropped, but it cannot currently be used as a user-defined `with` resource. This is a known resource-surface limitation. `net.WebSocket`: | API | Signature | Contract | | --- | --- | --- | | `send_text` | `send_text(text: str, timeout: Duration = ...) -> Result[None, io.Error]` | Sends a text frame. | | `send_bytes` | `send_bytes(bytes: list[uint8], timeout: Duration = ...) -> Result[None, io.Error]` | Sends a binary frame. | | `recv_text` | `recv_text(timeout: Duration = ...) -> Result[Option[str], io.Error]` | Receives the next text or binary message decoded as strict UTF-8; `Ok(None)` on close. | | `recv_bytes` | `recv_bytes(timeout: Duration = ...) -> Result[Option[list[uint8]], io.Error]` | Receives the next text or binary message as bytes; `Ok(None)` on close. | | `close` | `close() -> None` | Closes the WebSocket. | Use text receive when the payload must be valid UTF-8 and bytes otherwise. Messages are capped at 64 MiB; individual frames and the write buffer are capped at 16 MiB. WebSocket accept/send/receive cancellation is not yet as complete as the TCP/UDP scheduler surface, and `close()` currently discards host close errors. ## Unix Domain Sockets Unix socket APIs are available on Unix hosts. `net.UnixListener`: | API | Signature | Contract | | --- | --- | --- | | `accept` | `accept(timeout: Duration = ...) -> Result[net.UnixStream, io.Error]` | Waits for the next Unix stream connection. | | `close` | `close() -> None` | Closes the listener. | `net.UnixStream`: | API | Signature | Contract | | --- | --- | --- | | `read_line` | `read_line(timeout: Duration = ...) -> Result[Option[str], io.Error]` | Reads one strict UTF-8 line without its trailing LF/CRLF, `Ok(None)` on EOF. | | `read_exact` | `read_exact(count: int32, timeout: Duration = ...) -> Result[list[uint8], io.Error]` | Reads exactly `count` bytes; count must be in `1..=67108864`. | | `write_all` | `write_all(text: str, timeout: Duration = ...) -> Result[None, io.Error]` | Writes all text. | | `close` | `close() -> None` | Closes the stream. | `net.unix_listen(...)` refuses to clobber a non-socket filesystem path. ## TLS TLS APIs use PEM files for certificates and keys. Maintained examples keep test certificates under `examples/io/certs`. `net.TlsListener`: | API | Signature | Contract | | --- | --- | --- | | `accept` | `accept(timeout: Duration = ...) -> Result[net.TlsStream, io.Error]` | Waits for a TLS connection and handshake. | | `local_addr` | `local_addr() -> Result[str, io.Error]` | Returns the bound local address. | | `close` | `close() -> None` | Closes the listener. | `net.TlsStream`: | API | Signature | Contract | | --- | --- | --- | | `read_line` | `read_line(timeout: Duration = ...) -> Result[Option[str], io.Error]` | Reads one strict UTF-8 line without its trailing LF/CRLF, `Ok(None)` on EOF. | | `read_exact` | `read_exact(count: int32, timeout: Duration = ...) -> Result[list[uint8], io.Error]` | Reads exactly `count` decrypted bytes; count must be in `1..=67108864`. | | `write_all` | `write_all(text: str, timeout: Duration = ...) -> Result[None, io.Error]` | Writes all text through the TLS stream. | | `close` | `close() -> None` | Closes the TLS stream. | Handshake and accept paths use scheduler-aware waits. A TLS handshake also has a hard 10-second cap even when the caller omits a shorter timeout. Use explicit timeouts for public-facing services. ## Resource Cleanup Network listeners and streams are owned resources. Prefer `with` when the lifetime is lexical: ```aura import io import net def show_addr() -> Result[None, io.Error]: with listener = try net.listen("127.0.0.1:0"): print(try listener.local_addr()) return Result.Ok(None) ``` When a resource is not scoped with `with`, call its `close()` method when one is provided by the type. Cancellation stops Aura's wait but cannot roll back host I/O that already completed. ## Grammar The network module adds no source-language grammar. Network programs use ordinary imports, calls, named arguments, `Duration` literals, `Result`, `Option`, `try`, `match`, task constructs, and `with`. Addresses, URLs, server names, and Unix socket paths are runtime `str` values, not specialized literals. Omitting a parameter displayed with `= ...` selects its builtin default. In particular, an omitted timeout means no caller-supplied deadline unless this page states a protocol hard cap. Text and byte operations are distinct members; the selected member determines UTF-8 decoding. ## Typing Rules The constructor and method signatures in all tables above are normative. Listeners, streams, sockets, exchanges, and WebSockets are non-copy resource values. Fallible operations return `Result[..., io.Error]`; EOF and UDP receive timeout use `Option` only in the positions explicitly documented. `Duration` is required for timeout parameters, and byte-count parameters are `int32` checked against each API's runtime range. Text members accept or return `str` and enforce UTF-8. Byte members accept or return `list[uint8]`. HTTP headers use `dict[str, str]`. `HttpExchange.respond_text` and `respond_bytes` consume their response body and header dictionary. Other data arguments are shared for the call unless their displayed signature explicitly says `own`. ## Runtime Semantics Resolution, binding, and connect work use the generic blocking-I/O pool. One explicit connect timeout is an end-to-end budget shared across queue admission, name resolution, resolved-address attempts, and remaining protocol handshake work. Before host work begins, the runtime rejects a negative, host-unrepresentable, or deadline-overflowing timeout as `io.Error.InvalidInput`; it never treats such a value as omission. TCP, Unix, TLS, HTTP, and WebSocket waiting failures return typed errors as specified; UDP receive timeout returns `Ok(None)`. Cancellation ends the Aura wait and returns `io.Error.Cancelled` on cancellation-aware operations. Before pool acceptance it prevents submission; after acceptance, host work may complete later and its result is discarded. TCP is a byte stream; UDP preserves datagrams. Text reads decode strictly and remove only their documented line ending. HTTP supports content-length, chunked, and connection-close framing under the stated parser caps. WebSocket receives complete text or binary messages, with text mode enforcing UTF-8. TLS verifies the named peer using the configured CA file or, for the high-level HTTPS client, the maintained Web PKI root set. ## Ownership And Evaluation Order Arguments are evaluated left to right. Successful constructors and accept operations return fresh owned resources. Moving a resource invalidates the source binding. Read and accept operations mutate host protocol state internally but are callable through their documented shared receiver; write, send, shutdown, response, and explicit close operations require a mutable receiver place. Response bodies and header maps marked `own` are moved before the response operation begins. `with` closes a resource exactly once on every lexical scope exit when the type has `close()`. Cleanup cannot undo bytes already sent or host operations already completed. `WebSocketListener` has no `close()` member and therefore cannot satisfy the user-visible `with` resource contract; dropping its owned value is its only current release path. ## Diagnostics Unknown network members use `AU2001`, type mismatches use `AU2002`, invalid argument binding uses `AU2004`, and remaining static rejections use `AU2999`. Use after moving a resource uses `AU3001`, borrow conflicts use `AU3002`, and a mutating network method called through an immutable place uses `AU3003`. DNS failures, connection refusal, timeout, invalid UTF-8, invalid byte counts, invalid timeout values or deadlines, closed resources, cancellation, TLS verification failure, and protocol errors are documented typed `Result.Err(io.Error)` outcomes, not language diagnostics. An invariant failure escaping that typed boundary uses the general runtime registry, including `AU4005` for a resource or I/O trap. ## Backend Support TCP, UDP, HTTP, WebSocket, and TLS APIs are implemented by the MIR runtime and direct native backend. Unix domain sockets are implemented by both execution backends on maintained Unix hosts. Timeout accounting, typed error mapping, read caps, protocol parsing, ownership, and cleanup are backend-parity contracts. Address selection, DNS answers, socket options chosen by the host libraries, and exact host error messages may differ by machine. The high-level HTTPS client uses the same platform-independent Web PKI root policy in both backends. ## Limits And Implementation-Defined Behavior Whole TCP text reads, TCP line reads, and individual byte-count reads are capped at 64 MiB; TCP/Unix/TLS exact counts must be `1..=67108864`. UDP receive counts must be `1..=65535`, and truncation with a smaller receive buffer follows the host. Incoming parsed HTTP messages are capped at 16 MiB of wire data and 64 headers. The parser cap includes the start line, headers, transfer framing, trailers, and body; outbound HTTP writers have no separate size cap. The string-dictionary header boundary is not lossless for repeated fields and can currently expose duplicate equal keys internally. WebSocket messages are capped at 64 MiB; frames and the write buffer are capped at 16 MiB. WebSocket listener close is unavailable, WebSocket cancellation coverage is incomplete, and WebSocket close currently discards host close errors. TLS handshakes have a hard 10-second cap in addition to any shorter caller deadline. Unix sockets are unavailable on non-Unix hosts, and `unix_listen` will not replace a non-socket path. Redirects, pooling, HTTP/2, proxies, decompression, high-level custom-CA arguments, and lossless repeated-header APIs are absent. ## Status The constructors, protocols, resources, typed errors, timeouts, cancellation behavior, scheduler integration, cleanup rules, and caps documented on this page are implemented and maintained for Aura 0.3. Nonblocking descriptors remain registered with the persistent reactor across scheduler turns; timeout deadlines share its timer heap, and an idle scheduler blocks until readiness, another runtime event, or the next deadline without a periodic tick. The fixed resource-cap policy recorded by ADR-0018 is Accepted, as is the invalid host-timer policy recorded by ADR-0019. The repeated-header representation, missing WebSocket-listener close operation, incomplete WebSocket cancellation, and discarded WebSocket close errors are documented current limitations. The protocol surface is exactly the API documented on this page. ## Source: docs/manual/numeric-arrays.md # Numeric Arrays `Array[T]` is Aura's owned contiguous CPU numeric container. It is intended for local preprocessing, postprocessing, evaluation, and batch-shaped numeric work. It is smaller than a general tensor framework: shape is runtime metadata, storage is row-major and host-only, and results own their buffers. The only dtypes are `int32`, `int64`, `float32`, and `float64`. ```aura def main() -> int32: left = Array[float64].from_list([1.0, 2.0, 3.0, 4.0], [2, 2]) right = Array[float64].full([2, 2], 0.5) combined = left + right first_row = combined[0:1] print(combined.shape()) print(combined[1, 0]) print(first_row.sum()) print(combined.mean()) return 0 ``` ## Grammar `Array` is a global builtin generic type rather than a module. It uses the ordinary specialization, call, member-call, indexing, indexed-assignment, and one-colon slice grammar: ```text Array [ dtype ] Array [ dtype ] . constructor ( arguments ) array [ expression { , expression } ] array [ [ expression ] : [ expression ] ] ``` The supported `dtype` names are exactly `int32`, `int64`, `float32`, and `float64`. Comma-separated Array indexing is distinct from a list index. One-colon slicing selects a first-axis range. The complete syntax remains defined by [Grammar](/manual/grammar). There is no Array literal, dtype value, rank annotation, array-shape broadcast syntax, view syntax, step slice, or multidimensional slice tuple. ## Typing Rules ### Constructors The complete constructor surface is: | Constructor | Result | | --- | --- | | `Array[T].zeros(shape: list[int64])` | `Array[T]` | | `Array[T].full(shape: list[int64], value: T)` | `Array[T]` | | `Array[T].from_list(values: list[T], shape: list[int64])` | `Array[T]` | `T` must be one of the four maintained dtypes. Shape is a runtime `list[int64]`, so rank and dimensions are not part of the static type. `from_list` requires exact `list[T]`, copies its scalar elements, and leaves the shared source list usable. Constructors never infer a different dtype from a mixed numeric source. ### Members | Member | Result and contract | | --- | --- | | `shape()` | `list[int64]`; owned shape snapshot | | `len()` | `int64`; total element count | | `clone()` | fresh `Array[T]` | | `get(index: list[int64])` | `Option[T]` | | `set(index: list[int64], value: T)` | mutable receiver; `Some(T)` replaced value or a coordinate/rank trap | | `fill(value: T)` | mutable receiver; returns `None` | | `map[U](f: def(T) -> U)` | `Array[U]`; `U` is one of the four dtypes | | `sum()` | `T` | | `min()` | `T` | | `max()` | `T` | | `mean()` | `float64` for every input dtype | `map` requires a repeatable callable whose bare parameter and return type match exactly. A consuming closure, `mut`/`own` parameter, or unsupported result dtype is rejected. `set` and `fill` require a mutable Array place. Direct indexing uses exactly one `int64` coordinate per runtime axis. `array[i, j]` has type `T`; indexed assignment requires a mutable Array place and a value of exactly `T`. `array[start:end]` returns `Array[T]`. ### Operators `+`, `-`, and `*` accept same-dtype exact-shape Array/Array operands or one Array and one scalar of exactly `T`, in either order. They return a fresh `Array[T]`. `/` has those forms only for floating Arrays. Integer Array `/` is rejected with `AU2003`, as required by ADR-0002. There is no array-shape broadcasting or mixed promotion. An `Array[int32]` and `Array[int64]` do not combine, and a bound scalar is never implicitly widened or narrowed for an Array operation. Every scalar integer type, plus `Array[int32]` and `Array[int64]`, provides `wrapping_add`, `wrapping_sub`, `wrapping_mul`, `saturating_add`, `saturating_sub`, and `saturating_mul`. An Array method accepts either one same-dtype scalar or one same-shape Array. Ordinary arithmetic stays checked. ## Runtime Semantics An Array has rank at least one and owns one contiguous row-major buffer. Dimensions are `int64`, may be zero, and may not be negative. `len()` is the checked product of all dimensions. A zero dimension therefore makes the Array empty while preserving its complete shape. `zeros`, `full`, and `from_list` lay out elements in row-major order. Direct coordinates are translated in that same order. A negative coordinate normalizes once against its own axis. `get` returns `None` for an invalid coordinate; method `set`, direct indexed read, and direct indexed assignment trap. Valid `set` returns the previous scalar in `Some`. `array[start:end]` selects complete rows along axis zero. Written endpoints have exact type `int32`; omitted bounds and one-time negative normalization follow the owned-slice rules. Endpoints never clamp. The fresh result shape is `[end - start]` followed by the source's remaining dimensions. Its storage never aliases the source. Elementwise Array/Array operations require exactly equal shapes. Scalar forms apply the scalar to every row-major element. Results own fresh contiguous storage. Floating `/` uses the ordinary floating operator contract. Integer `+`, `-`, `*`, and `sum` retain checked overflow. Wrapping operations use fixed-width two's-complement modular arithmetic; saturating operations clamp at the declared integer width. `map`, reductions, `fill`, and elementwise kernels traverse row-major storage. `sum()` of an empty Array returns the dtype's zero. `min()`, `max()`, and `mean()` require at least one element. Floating reductions visit elements left to right with deterministic dtype rounding and propagate NaN. `mean()` accumulates and reports a `float64` result for every source dtype. The contract promises no reassociation or vectorized reduction order. ## Ownership And Evaluation Order `Array[T]` is non-Copy and cloneable. Assignment and owned argument passing transfer the buffer; `.clone()` is the explicit full-buffer duplicate. It is always structurally `Transfer` because its dtype is one of four Transfer scalars, but a Task result containing an Array retains the ordinary single-consumer observation right. Bare parameters and receivers provide shared access. `set` and `fill` require exclusive mutable access. Constructors evaluate arguments left to right and once. Binary operations evaluate the left operand before the right, retain both reached Arrays for the kernel, and consume neither shared operand. Coordinates evaluate left to right. A direct indexed assignment captures its coordinate before evaluating the replacement value. Elementwise operations, `map`, and first-axis slices allocate a fresh result. `map` invokes its repeatable callback once per element in row-major order and moves or copies each scalar result into the output. A trap cleans up any partial output. Shape snapshots and first-axis slices are owned copies, not views. ## Diagnostics `AU2001` reports an unknown Array member or constructor. `AU2002` reports an unsupported dtype, exact argument/callback/result mismatch, or mixed dtype. `AU2003` reports unsupported operators, including integer Array `/`, and preserves the ordinary checked-integer guidance. `AU2004` reports invalid argument binding. `AU2005` reserves slice steps and slice assignment with the same owned-copy guidance as list/str slices. `AU3002` reports mutation while shared access is active; `AU3003` reports `set`, `fill`, or indexed assignment through an immutable place. `AU4003` reports an out-of-range direct coordinate or invalid/reversed first-axis slice. `get` returns `None` instead of emitting that diagnostic; method `set` traps. `AU4002` reports checked integer Array arithmetic overflow. `AU4004` reports floating Array division when any divisor is zero. `AU4005` reports shape-product/element-count overflow and allocation failure. `AU4007` (`numeric array shape or reduction violation`) reports: - rank-zero or negative-dimension construction - `from_list` element-count mismatch - exact-shape Array/Array operation mismatch - direct coordinate-count/runtime-rank mismatch - empty `min`, `max`, or `mean` These failures are language behavior, not permission for a backend-specific panic. ## Backend Support Constructors, indexing, mutation, first-axis copies, mapping, reductions, checked/wrapping/saturating arithmetic, scalar forms, and exact-shape elementwise operations are implemented for MIR and direct execution. Direct native execution uses dtype-specialized contiguous kernels. The two backends share checked types, evaluation order, row-major results, cleanup, and exact `AU4003`/`AU4007` behavior. Compiler analysis and the language server expose the same constructors, member signatures, result types, hover, definitions, completions, and diagnostics. The bundled extension uses that compiler-owned semantic surface. ## Limits And Implementation-Defined Behavior Aura 0.3 Arrays are CPU-only, contiguous, row-major, and rank-at-least-one. They have no array-shape broadcasting, mixed promotion, views, reshape, transpose, matrix multiplication, equality, ordering, multidimensional slicing, step slices, slice assignment, autograd, device placement, distributed storage, or foreign-buffer aliasing. Shape metadata is dynamic; the checker does not prove shape compatibility. Allocation is limited by host memory and the maintained element-count checks. Floating arithmetic follows the existing host IEEE-754 contract. This surface is narrower than NumPy's API. The maintained one-million-element `float64` add/sum comparison records post-reboot measurements from one named Mac14,9 host. On the post-reboot Mac14,9 M2 Pro host at commit `0511adf`, across 11 paired single-thread observations, the direct native backend measured these medians per one-million-element operation: | workload | Aura | NumPy 2.0.2 | Aura / NumPy | | --- | ---: | ---: | ---: | | fresh owned `float64` add | 1.142461 ms | 0.251602 ms | 4.540751× | | existing-array `float64` sum | 1.150392 ms | 0.174065 ms | 6.608975× | Release disassembly showed scalar floating-point instructions for these kernels. The table covers the two operations named above; Aura's Array API is narrower than NumPy's. ## Status Contiguous numeric Arrays and explicit scalar/Array integer arithmetic modes are Accepted for Aura 0.3 under `architecture_docs/decisions/0041-contiguous-numeric-arrays.md`. The maintained contract is the exact surface on this page and contains no broader tensor placement, views, shape transformations, or distributed execution. ## Source: docs/manual/ownership-and-borrowing.md # Ownership And Borrowing Aura statically tracks whether an operation copies, moves, shares, or mutates a value. The rules apply to local bindings, parameters, method receivers, fields, supported indexed operations, collection iteration, pattern matching, task starts, and resources. A **place** is a storage location such as a local binding or field path. A copy use duplicates a value. A move use transfers ownership from a place. A borrow temporarily grants access without transferring ownership. ## Copy Types Copy values are duplicated by assignment, by-value argument passing, returns, collection insertion, and other value uses. The source remains usable. Current copy categories are: - all signed and unsigned integer types - `float32` and `float64` - `bool` - `Duration` - `Queue[T]` handles and, under Accepted ADR-0033, `Task[T]` handles whose result type is repeatable - tuples whose every element type is copyable - `copy class` values whose fields are copyable - user enums whose every declared payload type is statically copyable - `Option[T]`, `Result[T, E]`, `SendError[T]`, and `QueueReceive[T]` when every payload type is copyable ```aura a = 1 b = a print(a) print(b) ``` `Queue[T]` is a copy handle to shared runtime state. Accepted ADR-0033 makes `Task[T]` copyable only when `T` is repeatable. Copying an allowed handle does not duplicate the underlying queue, task, queued values, or stored result. ## Move Types Move values transfer ownership on by-value use. Current move categories include: - tuples with at least one move element - `str` - `list[T]`, `dict[K, V]`, and `set[T]` - `random.Rng` - ordinary user classes - user or builtin enums with any move payload - `TaskResult[T]`, `SelectOutcome[Q, T]`, `WaitAny[T]`, and `WaitAll[T]` even when every payload type is copyable - `Range` - `TaskGroup` - file, process, supervisor, pipe, and network resources ```aura def main(): name = "aura" other = name print(other) # print(name) would be rejected: name was moved ``` A generic payload whose declared type is an unconstrained type parameter is not assumed copyable. The canonical category list and builtin generic types are in [Types](/manual/types#copy-and-move-categories). ## Operations That Move A non-copy value is consumed when used in an owned position, including: - assignment into a new owned binding - an `own` function parameter or an `own self` method receiver - a by-value return - a class or enum payload, collection literal, mutating collection method, or simple dict indexed assignment that stores the value - by-value enum matching - `own` iteration over `list[T]` or `set[T]` - the resource expression of `with` - a task-start argument copied or moved into task-owned capture storage An expression is evaluated before its move is recorded at that boundary. Aura also rejects an expression that tries to borrow and move overlapping places in incompatible subexpressions. List slicing is a clone-producing shared read. It does not move elements. `values[start:end]` retains `values` while the endpoint expressions run, then copies Copy elements or clones clone-safe non-Copy elements into a fresh owned list. A type containing `random.Rng` cannot be sliced because it cannot be safely duplicated; a non-repeatable Task observation right likewise cannot be duplicated. String slicing copies a Unicode-scalar range into a fresh owned `str`. Neither result aliases its source or acts as an assignable place. After creation, that fresh result follows the ordinary move rules for any other owned non-copy list or str value. Unpacking a non-copy tuple is one whole-source move. The target leaves receive owned elements, but the source does not become a set of independently reusable positional partial-move places. A later use of the source is rejected with the ordinary move diagnostic. Unpacking a copy tuple copies its elements and keeps the source usable. ## Borrow Forms | Form | Meaning | | --- | --- | | `value: T` | Shared access for every `T`; copy bits may be passed directly as an implementation detail. | | `value: own T` | Explicit owned ordinary parameter. | | `value: mut T` | Exclusive mutable borrowed ordinary parameter. | | `self` | Shared method receiver and the default receiver spelling. | | `own self` | Consuming method receiver. | | `mut self` | Exclusive mutable method receiver. | | `for value in collection:` | Default shared iteration for `list` and `set`. | | `for value in own collection:` | Consuming iteration for `list` and `set`. | | `for value in mut collection:` | Mutable-borrow iteration where supported. | | `match value:` | Shared borrowed pattern matching. | | `match own value:` | Consuming pattern matching. | | `match mut value:` | Mutable borrowed pattern matching with writeback. | | `-> T` | Owned result. A copy result is an ordinary independent copy. | The spelling asymmetry is intentional: parameter ownership occupies the type position as `value: own T`, parallel to `value: T`, while loop ownership prefixes the iterable as `for value in own values` because loops have no type position. Call sites never prefix arguments with a capability. The parameter or receiver declaration selects the mode: ```aura def render(name: str) -> str: return name.to_upper() name = "aura" print(render(name)) print(name) ``` A shared borrow permits reading but cannot be moved and cannot be used as a mutable place. A mutable borrow is exclusive and may mutate its source through the borrowed binding. Shared-borrow and `own` parameters may have defaults. An omitted shared default creates a fresh temporary that lives through the call; an omitted owned default creates a fresh value that the call consumes. A `mut` parameter cannot have a default, even for a copy type: its caller-invisible temporary would make every mutation a silent lost write. Require the caller to pass a mutable value, or take `own T` and return the result. ```aura def add_name(names: mut list[str], name: own str): names.append(name) def main(): mut names = list[str]() add_name(names, "Ada") ``` Only a mutable place can satisfy `mut T`. A local becomes mutable with `mut`; a field is mutable when its base place is mutable; a `mut` receiver or parameter is a mutable place inside its body. Parameter bindings themselves are not reassigned. ## Call-Boundary Exclusivity All receiver and argument accesses for one call are checked together. Shared borrows may overlap other shared borrows. Every mutable borrow and every move must be exclusive with respect to an overlapping place. ```aura class Acc: value: int32 def add_from(mut self, source: Acc): self.value += source.value def main(): mut acc = Acc(value=1) # acc.add_from(acc) is rejected: mutable self overlaps shared source ``` Place overlap is prefix-based for tracked name/field paths. `value` overlaps `value.field`, and `value.field` overlaps `value.field.inner`. Distinct roots do not overlap. Sibling fields such as `pair.left` and `pair.right` are distinct when the checker can prove those paths. The same exclusivity rule applies when one argument consumes a value and another argument borrows it. Argument evaluation order does not make an otherwise overlapping call legal. ## Partial Moves And Reinitialization Moving a non-copy field from an owned class marks that field path moved while preserving disjoint fields: ```aura class User: name: str id: int32 def main(): mut user = User(name="Ada", id=1) name = user.name print(user.id) user.name = "Grace" print(user.name) ``` The complete class value cannot be used while any field remains moved. Assigning the exact moved field reinitializes that path. Assigning a fully moved mutable binding reinitializes the binding and clears its moved-field state. Moving a non-copy field through a shared or mutable borrow is rejected because the borrower does not own the containing value: ```aura def bad(user: User) -> str: return user.name # rejected ``` Use `.clone()` for a new owned value when the type supports it, or expose an owner method that performs the read or mutation: ```aura def good(user: User) -> str: return user.name.clone() ``` ## Flow-Sensitive Move Checking Branches and match arms are checked independently. At a reachable join, a binding or field is considered moved if it may have been moved on any incoming path unless it was definitely reinitialized on all relevant paths. Moves inside a loop need an additional invariant: the loop may execute again. Aura rejects a first move or partial move from an outer value in a repeatable loop when the next iteration could reuse the moved place. Limited constant-boolean reasoning recognizes forms based on `true`, `false`, grouping, and `not`; programs should not depend on broader compile-time evaluation. Block-local bindings do not escape their branch, arm, loop, or `with` body. See [Names And Scopes](/manual/names-and-scopes#block-scope-and-control-flow). ## Owned Returns Every function return transfers an owned value to its caller. Copy values are ordinary independent copies. A non-copy return must come from an owned source: ```aura def identity(value: int32) -> int32: return value class User: name: str def copy_name(user: User) -> str: return user.name.clone() def into_name(user: own User) -> str: return user.name ``` A function can construct a fresh non-copy value, clone a clone-safe value, accept an `own` parameter, or consume an owner through an `own self` method. Shared or mutable access does not transfer ownership of a non-copy field, so returning that field directly is rejected as an invalid move through access the function does not own. Every result is owned. Return syntax carries only a result type and no source label or access capability. The detailed rules are in [Functions](/manual/functions#owned-returns). ## Borrowed Pattern Matching `match own` consumes a non-copy enum scrutinee. Bare `match` retains the enum and gives non-copy payload bindings shared-borrow provenance: ```aura result: Result[str, str] = Result.Ok("ready") match result: case Result.Ok(value): print(value) case Result.Err(error): print(error) ``` `match mut` requires a mutable place. Its non-copy payload bindings are mutable borrows, and mutations are written back by reconstructing the enum on normal arm exit, `return`, `break`, `continue`, and `try` propagation. A nested mutable match cannot overlap an already active mutable match. Reassigning the exact scrutinee, its root, or an ancestor field invalidates payload bindings tied to the old value. A write to a proven-disjoint sibling field does not invalidate them. Tuple patterns follow a smaller rule. A `match own` tuple match consumes the whole non-copy scrutinee and gives owned leaf bindings. Bare `match` retains the tuple and gives shared leaf provenance. Tuple patterns are rejected under `match mut`; Aura does not reconstruct and write back recursive tuple targets. Payload bindings are arm-local and cannot shadow a visible binding. Match typing and exhaustiveness are specified in [Enums And Pattern Matching](/manual/enums-and-match). ## Borrowed Iteration Bare `list` and `set` iteration retains the collection and yields shared-borrowed non-copy elements. `for value in own collection` moves the collection once into a loop-private source and yields owned elements. Reinitializing the consumed source binding in the body cannot switch or truncate that active iteration. That one-time source selection is accepted under ADR-0017. `for value in mut values` requires a mutable list place and yields mutable-borrowed elements. The place selected by bare iteration is frozen against overlapping mutation for the loop body. Mutable-borrow set iteration is not supported; mutate a set through `add` and `remove` outside borrowed iteration. Queue iteration receives values; it is a scheduler operation, not a place traversal. The bare form copies the Queue handle once at loop entry and yields owned items without freezing the source binding; rebinding that source does not switch later receives. All three explicit ownership modifiers are rejected. The one-time handle selection is also accepted under ADR-0017. See [Concurrency](/manual/concurrency). When an iteration item is a tuple, recursive target leaves inherit the item provenance. Shared collection iteration gives shared non-copy leaves, `own` collection iteration gives owned leaves, and bare Queue iteration gives owned leaves because it receives the item. A tuple target is rejected with `mut` iteration; recursive mutable tuple writeback is not defined. ## Clone `.clone()` explicitly creates another owned structural value where the maintained type exposes cloning: ```aura name = "aura" copy = name.clone() print(name) print(copy) ``` Text clones and collection copies create owned contents. Cloning a runtime-backed resource handle does not necessarily create an independent host resource; rely on the resource's documented API. Not every move type supports cloning. `random.Rng` is deliberately non-cloneable, and wrapping it in a class, enum, or collection does not make the stored generator cloneable. Clone-producing collection reads and task observations follow the same structural rule. Copying a `Task[T]` or `Queue[T]` handle is different because it copies only the handle, not a stored `T`. When a clone-producing operation depends on an unresolved generic type, the callable acquires an inferred clone-safety obligation. Safe specializations remain valid; a specialization that would duplicate `random.Rng` is rejected with `AU3007`. ## Closures And Capture Closure capture is an ownership operation at lambda creation. A referenced outer Copy value is copied into the closure environment. A referenced outer non-Copy owned value is moved, so the source cannot be used afterward unless the program cloned before creation. A read-only closure borrows its environment for each call and is repeatable, including when it owns non-Copy data. A closure whose body consumes any non-Copy capture is itself consumed by the call and is single-use under `AU3001`. Capturing closures are non-Copy. Their environment is Transfer only when every captured value is Transfer. Enclosing bare and `mut` parameters are shared and mutable capabilities rather than owned values and cannot be captured. Captured state is read-only. See [Closures](/manual/closures) and Accepted ADR-0037. ## FFI Views And Opaque Handles FFI v0 views are temporary call-boundary capabilities, not first-class Aura references. Bare `str` and `list[uint8]` retain their owner while exposing a const pointer and byte length for one synchronous foreign call. `mut list[uint8]` requires an exclusive mutable list place and copies the initial bytes into a same-length scratch buffer; exactly that length is written back after the foreign function returns. Empty views use a null pointer with length zero. Foreign code must not retain any view pointer. An opaque FFI handle is a non-Copy, non-cloneable, non-Transfer owned wrapper for one non-null foreign pointer. A bare handle parameter retains it; `own Handle` consumes it; `mut Handle` is unavailable. Aura does not automatically call a foreign destructor, so a binding must invoke its explicit consuming close/free declaration. Opaque handles cannot be task captures, task results, or Queue payloads. See [FFI v0](/manual/ffi) for the complete ABI and failure boundary. ## Tasks And Borrowing The four `TaskGroup` start methods accept named functions or associated methods with bare shared or `own` parameters. `mut` targets are rejected. The two `_with_stack` forms add an `int64` capacity argument before the callable; they do not change capture ownership. ```aura def worker(label: str): print(label) with group = TaskGroup(): label = "compile" group.start_soon(worker, label.clone()) print(label) ``` Each task argument is copied or moved into task-owned capture storage before the child runs. The target then shares or consumes that capture according to its declared mode. Copy task and queue handles still refer to shared runtime state. See [Concurrency](/manual/concurrency) and [Execution Model](/manual/execution-model#tasks-and-scheduler). Accepted ADR-0033 adds a separate `Transfer` check to task captures, results, Queue construction, and Queue `put`/`try_put`. Handle-only Queue operations do not recheck the payload. A bare target parameter can still borrow its child-owned capture for the call, but the captured value itself must be transferable. A shared or mutable capability view cannot cross the boundary: pass owned structural data instead. Copy values, `str`, and aggregates made entirely from transferable components qualify; `random.Rng`, `TaskGroup`, and live host resources do not. `process.Completed`, `net.HttpResponse`, and `net.UdpDatagram` are owned snapshot data rather than live resources, so they qualify. Their live `process.Child`, `net.HttpExchange`, and `net.UdpSocket` sources do not. A Copy value read through shared or mutable access is a narrow exception: task capture materializes an independent owned snapshot, so no capability crosses. A non-copy access cannot be captured this way because the child would need ownership of the value. The same decision statically divides task results into repeatable values and single-consumer values. `Task[T]` is copyable only when `T` is copyable, a `Queue[...]`, or a recursively repeatable `Task[...]`. Otherwise each result method consumes the unique observation right even when it reports timeout, cancellation, or failure. Multi-task waits consume their entire task list, and `wait_any` abandons unchosen rights. These rules are required before the pinned-worker runtime can safely run sibling task bodies on different host threads. Queue and Task handle identity may cross workers, while every other capture or result remains an owned `Transfer` value rather than a shared capability. ## Resources And `with` Resource ownership should normally be lexical: ```aura import fs import io def show_file() -> Result[None, io.Error]: with file = try fs.open("data.txt"): text = try file.read_all() print(text) return Result.Ok(None) ``` `with` consumes the resource expression and creates a fresh mutable managed binding. A managed resource or its non-copy fields cannot be moved out in a way that would prevent cleanup. The registered `close` runs on normal fallthrough, `return`, escaping loop control, `try` propagation, and maintained runtime failure; nested cleanups run in reverse order. Builtin resource behavior is defined by its module chapter. A user class must be non-generic and define `close(mut self) -> None` with no ordinary parameters. Full cleanup ordering and failure precedence are specified in [Execution Model](/manual/execution-model#resource-lifetime-and-cleanup). ## Grammar The normative capability spellings are bare, `own`, and `mut` ordinary parameters; `self`, `own self`, and `mut self` receivers; bare, `own`, and `mut` collection loops where the iterable supports them; bare, `match own`, and `match mut` matching; mutable bindings; owned return annotations; and `with`. Their productions are in [Grammar](/manual/grammar). Call arguments themselves never carry a capability prefix. ## Typing Rules Every expression has one static copy/move category and every parameter has one declaration-stable passing mode. Bare parameters grant logical shared access for every type; an implementation may pass copy bits directly. Explicit `own` consumes; `mut` requires one exclusive mutable place. Shared and owned defaults are legal, with shared temporaries lasting through the call; `mut` defaults are rejected. Place-prefix overlap, partial moves, control-flow joins, loop repetition, owned-return moves, borrowed matches, borrowed iteration, task capture, and managed-resource containment are checked before lowering. Clone-producing generic operations infer obligations that are propagated through calls and discharged after specialization. ## Runtime Semantics A copy use duplicates a value and a move transfers it. Shared and mutable borrows are statically enforced access contracts rather than first-class runtime reference values in Aura 0.3. Mutable borrowed calls and list iteration write through the original place; `match mut` reconstructs and writes back on every arm exit. Simple dict indexed assignment accepts and owns any value type; direct compound indexed assignment requires a copy `list` element or `dict` value. Task start first transfers captures into child-owned storage. `with` owns one cleanup registration and runs it exactly once on every maintained scope exit under the documented failure-precedence rules. ## Ownership And Evaluation Order Subexpressions evaluate in the order defined by [Execution Model](/manual/execution-model#evaluation-order), then a copy, move, or borrow is applied at its typed boundary. All receiver and argument accesses for one call are checked together, so source order cannot legalize overlapping shared, mutable, and owned uses. A partial move preserves proven-disjoint fields; reinitializing the exact moved place restores it. Control-flow merging never silently restores ownership, and Aura never inserts a clone or coercion to repair an invalid use. Capturing a copy place duplicates its value. A non-copy place selected as a binary left operand, index base, method receiver, or indexed-assignment target remains borrowed until that operation consumes all of its inputs. A later shared borrow is permitted. An overlapping mutable borrow or consumption is rejected with `AU3002`, with the retained selection identified as the borrow origin. Name roots and projected member places follow the same rule, and no backend inserts a hidden deep clone. Operations that require a point-in-time representation produce it immediately; each f-string interpolation renders to `str` before the next interpolation begins. Compound assignment uses the corresponding binary operator dispatch, including applicable user-defined operator traits for root and projected targets. A copy target is captured before the right operand. A non-copy root or projected target remains borrowed across that operand, so overlapping mutable borrow or consumption is `AU3002`. A non-copy `list` element or `dict` value cannot be a direct compound target because Aura 0.3 has no indexed-place identity and writeback model; Aura rejects the operation instead of cloning or destructively moving the stored value. ## Diagnostics `AU1101` reports malformed ownership, receiver, loop, match, or return syntax. `AU2002` covers type mismatch, while `AU2004` reports argument binding that cannot satisfy a required mutable place. `AU2999` covers unsupported move/control-flow/resource cases without a narrower category. `AU3001` reports use of a moved or partially moved place. `AU3002` reports overlapping or invalid borrows, moving through a borrow, invalid mutable-borrow defaults or task targets, stale borrowed-pattern bindings, and later mutable or consuming access that overlaps a retained non-copy binary operand, index base, method receiver, or indexed-assignment target. In a retained-expression conflict, the diagnostic points to both the later access and the retained-borrow origin. `AU3003` reports assignment or mutation through an immutable place, including shared `self`. `AU3004` reports invalid parameter, receiver, loop, or Queue-iteration ownership modes. `AU3005` rejects a direct indexed read of a non-copy list element or dict value; `AU3006` rejects the corresponding indexed compound read-modify-write. `AU3007` rejects direct or transitive duplication of non-cloneable state, including `random.Rng`, opaque FFI handles, capturing closure environments, and unsafe generic specializations. `AU3008` reports a non-Transfer task or Queue boundary. `AU3009` rejects clone, clone-producing collection read, or aggregate copy that would duplicate a single-consumer task-result right. Reuse after direct observation is the ordinary moved-value `AU3001`; shared-access consumption is `AU3002`. Ownership failures are static. A runtime operation reached through an owned or borrowed value keeps its own code: `AU4001` for a general trap, `AU4002` for arithmetic overflow or underflow, `AU4003` for a bounds or lookup violation, `AU4004` for a zero divisor, and `AU4005` for a resource or I/O failure. ## Backend Support The compiler performs one ownership/borrow analysis before backend selection. MIR execution and direct native generation receive the same resolved parameter ABI, moves, copies, capture modes, borrowed-match/iteration operations, and cleanup registrations. Analysis and LSP signatures expose those same modes. The parity matrix pins observable move, mutation, capture, writeback, cleanup, and primary-diagnostic behavior. ## Limits And Implementation-Defined Behavior Place analysis tracks local roots and field-prefix paths; it proves disjoint sibling fields but is not a general alias theorem. Mutable set iteration, explicit Queue ownership modifiers, mutable-borrow task targets, moving out of a managed resource, and arbitrary reference values are unavailable. Loop move analysis intentionally uses only the limited Boolean reasoning described above. Ownership mode and evaluation order are language-defined, not backend- or host-defined. ## Status Copy/move classification, declaration-stable parameter defaults, explicit owned/shared/mutable passing, all receiver modes, call-boundary exclusivity, partial moves and reinitialization, flow-sensitive checks, owned returns, borrowed matching and list/set iteration, task capture, cloning, and lexical resource ownership are implemented for the post-Phase 1.5 surface; the one-time list/set/Queue iteration-source rule is accepted under ADR-0017. Mutable set iteration, Queue ownership modifiers, and mutable task capture are unavailable. ## Source: docs/manual/packages.md # Packages An Aura package is a directory containing `Aura.toml` and a `src/` source root. The package graph determines module paths, dependency import prefixes, git revisions, and the owner of `Aura.lock`. Package resolution is performed before static checking. A malformed manifest, unresolved dependency, package cycle, invalid lockfile, or import that escapes its source root is a compile-time/tooling diagnostic. ## Package Manifest The supported package manifest shape is: ```toml [package] name = "app" version = "0.1.0" edition = "2026" [dependencies] util = { path = "../util" } ``` All three package fields are required: - `name` must match `[A-Za-z_][A-Za-z0-9_]*`; it is also the dependency import identifier - `version` must begin with an ASCII digit and otherwise contain only ASCII letters, digits, `.`, `-`, or `+` - `edition` must be exactly `"2026"` in Aura 0.3 `allow_ffi = true` is an optional `[package]` field whose default is `false`. It authorizes that package to contain FFI declarations. It grants no ambient permission to unrelated packages and does not validate the native code. When any direct or transitive dependency enables FFI, the root package must also opt in and provide an exact dependency report: ```toml [package] name = "app" version = "0.1.0" edition = "2026" allow_ffi = true [dependencies] native = { path = "../native" } [ffi] dependencies = ["native"] ``` The report lists every reachable FFI-enabled dependency by its package name, including transitive dependencies. It does not list the root package itself. Duplicate, unknown, unreachable, and non-FFI entries are rejected, as is an omitted FFI-enabled dependency. Every listed dependency must independently set its own `[package] allow_ffi = true`. See [FFI v0](/manual/ffi). An empty or unsupported value is rejected. Hyphenated package names are invalid even though `-` is allowed later in the version string. The conventional and required package source root is `src/`. A package entry selected by ordinary check/run/build commands must be under that root. Package-aware test entries may instead be under the root package's `tests/` directory; they receive logical module names beginning with `tests.`. ## Module Paths Inside A Package A file path below `src/` maps to its dot-separated path without `.au`: | File | Logical local module | | --- | --- | | `src/main.au` | `main` | | `src/math.au` | `math` | | `src/helpers/text.au` | `helpers.text` | Local imports are not prefixed with the current package name: ```aura import helpers.text from helpers.text import normalize ``` An import path maps directly to a `.au` file below the selected source root. Import traversal cannot escape that root, including through canonicalized filesystem paths. Cyclic source imports are rejected. Imported modules contribute declarations, not runtime initialization. Their top-level executable statements do not run as import side effects in Aura 0.3. Visibility and import binding behavior are specified in [Names And Scopes](/manual/names-and-scopes#imports). ## Dependency Sources Each dependency table entry must choose exactly one source: | Source | Example | Resolution contract | | --- | --- | --- | | Local path | `util = { path = "../util" }` | Resolves a package directory relative to the declaring manifest. | | Git revision | `util = { git = "...", rev = "abcdef0" }` | Uses that exact 7–64 digit hexadecimal revision. | | Git tag | `util = { git = "...", tag = "v1.0.0" }` | Resolves the tag and pins its exact revision. | | Git branch | `util = { git = "...", branch = "main" }` | Resolves the branch and pins its exact revision. | | Git default | `util = { git = "..." }` | Resolves branch `main` and pins its exact revision. | `path` and `git` cannot appear together. A git entry may choose at most one of `rev`, `tag`, or `branch`; selectors without `git` are invalid. String-valued version dependencies such as `util = "1.2.0"` and detailed `version =` dependencies are registry forms and are not implemented. Aura 0.3 has no registry resolution, publish, or install flow. The dependency table key is not a free alias: it must exactly match the resolved dependency's `[package].name`. This keeps the manifest name and import root identical. A dependency package named `util` is imported with that prefix: ```aura import util.math print(util.math.double(21)) ``` Inside a dependency, its own local imports remain relative to its `src/`; when exposed in the loaded graph, dependency module identities retain the dependency package prefix. ## Dependency Graph Rules Resolution recursively loads path and git dependencies and enforces: - no cyclic package dependency path - one canonical directory for each package name in the graph - the dependency key equals the resolved package name - at most 1,024 direct dependencies per package - at most 4,096 packages in one resolved graph - every package has a readable `src/` directory and a valid package manifest Two different paths cannot both claim the same package name. The graph limits are observable Aura 0.3 limits and may be raised only with corresponding reference and conformance changes. ## Workspaces A workspace-only root manifest lists exact member paths: ```toml [workspace] members = ["app", "util"] ``` Each member path is resolved relative to the workspace root and must identify a package with its own `[package]` manifest and `src/`. Membership is an exact normalized path match; glob patterns are not implemented. A package is governed by an ancestor workspace only when its manifest directory appears in that workspace's member list. Workspace membership does not automatically make one member importable by another. The consuming member still declares the other package under `[dependencies]`, commonly as a local path dependency. The workspace owns one `Aura.lock`. A standalone package owns `Aura.lock` beside its own manifest. Running `aura deps update` from a workspace-only root resolves all declared members and their dependency graphs; an empty workspace has nothing to update and is rejected. ## Lockfile Contract `Aura.lock` version 1 records every resolved package in deterministic package-name order. Path entries record a path relative to the lockfile root. Git entries record the normalized source, exact resolved revision, and the original tag or branch selector where applicable. Conceptual examples: ```toml version = 1 [[package]] name = "util" version = "0.1.0" source = "git" git = "https://example.com/util.git" rev = "0123456789abcdef0123456789abcdef01234567" branch = "main" ``` For tag, branch, and default-`main` dependencies, ordinary resolution reuses a matching locked revision instead of silently following a moved remote reference. An explicit `rev` is already immutable. A lockfile with an unsupported version, malformed source entry, missing path/git/revision, invalid selector, or unsupported source kind is rejected. File-backed `check`, `run`, `build`/MIR loading, and explicit source-buffer check paths resolve the package graph and may create or rewrite the owning lockfile after successful loading. Compiler analysis and completion of editor buffers deliberately use the no-lockfile path so diagnostics and completions do not dirty the workspace. `aura deps update` always writes the applicable package/workspace lockfile after successful resolution. Applications should commit `Aura.lock` when reproducible dependency resolution matters. The lockfile is generated state; edit the manifest and use the resolver/update command rather than hand-maintaining revision entries. ## Updating Git Dependencies From a package or workspace directory, refresh every eligible git dependency: ```bash aura deps update ``` During repository development the equivalent is: ```bash cargo run -p aura -- deps update ``` Refresh one named git dependency: ```bash aura deps update util ``` An all-dependency update refreshes tag, branch, and default-`main` selectors. Exact `rev` dependencies are not refreshed. A named update requires that the name be present in the current graph and refer to a git dependency; path packages are rejected as update targets. ## Git Resolution, Cache, And Safety A git source is either an explicit URL/SSH form or an existing local path relative to the declaring manifest. Empty sources, option-like sources beginning with `-`, invalid revision text, and unsafe tag/branch spellings are rejected before invoking git. Aura disables interactive git credential prompts so package commands fail without waiting for terminal input. Each git command has a 60-second default timeout. Set `AURA_GIT_TIMEOUT_MS` to a positive millisecond value to override that timeout. Resolved revisions are materialized in a content-addressed cache under `$XDG_CACHE_HOME/aura/git`, otherwise `$HOME/.cache/aura/git`, with a temporary-directory fallback when needed. Cached entries are validated against their recorded revision. Aura refuses symlinked cache paths, symlinked manifests, and symlinked content in a git checkout; clones also disable symlink materialization. Concurrent cache placement uses a compatible existing checkout only when it validates to the same revision. These checks are part of package loading behavior, not a guarantee that dependency source is trustworthy. Applications must still review and pin the code they execute. ## Package Root Discovery For a file-backed command, Aura walks upward from the selected path to find the nearest `Aura.toml` containing `[package]`. It then checks whether an ancestor workspace exactly lists that package as a member and chooses the corresponding lockfile root. A workspace-only manifest is not itself a package source root. Commands operating from a directory, such as `deps update`, may discover either an enclosing package or an enclosing workspace. Malformed manifests encountered during discovery are reported rather than silently skipped. For stdin-backed compiler commands, the supplied path still controls package discovery, import resolution, diagnostics, and module identity while source text comes from stdin. Whether that command writes a lockfile follows the command-specific rule above. See [CLI And Tooling](/manual/cli-and-tooling#stdin-buffers). ## Visibility Across Packages Only `public` top-level classes, enums, functions, and traits can be imported from another module. Public classes still enforce field and method visibility separately. Trait implementations loaded through package modules participate in dispatch with their defining module identities preserved. ```aura from util.math import double print(double(21)) ``` Package boundaries do not create implicit public exports, wildcard imports, relative import syntax, or prelude re-exports. The exact import grammar is in [Grammar](/manual/grammar#imports). ## Current Boundaries - registry dependencies are not implemented - workspace membership uses exact paths, not globs - there is no implicit dependency between workspace members - source roots are fixed at `src/` - ordinary package entry files must be below `src/`; package test programs may be below the root `tests/` - package graphs and direct dependency counts have the documented finite limits - FFI declarations require a package manifest, the declaring package's explicit opt-in, and an exact root dependency report when dependencies use FFI See [Current Limits](/manual/current-limits#runtime) for the broader maintained implementation limits and [Conformance](/manual/conformance) for package test coverage. ## Grammar Source imports have the maintained forms `import dotted.module [as local]` and `from dotted.module import name [as local]`, as specified in [Grammar](/manual/grammar#imports). A from-import may contain several direct or aliased names. Import paths are absolute within the resolved local or dependency namespace. The resolver uses the path before `as`; the alias is a local binding and is never interpreted as a package, directory, or dependency key. Relative imports, wildcard imports, and package-name prefixes for the current package are not grammar. `Aura.toml` and `Aura.lock` use TOML as external tooling formats, not Aura source grammar. Their accepted keys, table shapes, selector combinations, identifier rules, FFI opt-in/report fields, and lockfile version are exactly the contracts documented above; unrecognized source kinds or unsupported dependency forms are rejected rather than inferred. ## Typing Rules Package and module resolution completes before static checking. It also authorizes every loaded FFI declaration against the declaring package and root dependency report before execution. An import binds a module namespace or a visible declaration with its defining module identity and declared type. Local modules use their `src/`-relative dotted name; a dependency's package name is its import root. Only `public` top-level declarations cross a module boundary, with class member visibility checked separately. Imports do not erase types or ownership modes. Calls to imported functions and methods are checked against their original signatures, and trait implementations retain defining-module identities for coherence and dispatch. A package manifest does not create an Aura value, implicit export, prelude, or relationship between workspace members. Clone-safety obligations survive module imports as part of the callable contract. Namespace-qualified and directly imported calls enforce the same inferred requirements after specialization. User-defined nominal types retain their defining module identity during structural clone-safety analysis, so an unrelated same-leaf type in the importing module cannot replace them. ## Runtime Semantics Resolution discovers the nearest package, any exact containing workspace, the transitive path/git graph, and the applicable lockfile before loading source. Imported modules contribute declarations only: top-level executable statements in an imported file are not run as module initialization. The selected entry module alone supplies program execution. Ordinary locked resolution reuses a matching exact git revision for moving selectors. `deps update` deliberately refreshes eligible moving selectors and then deterministically rewrites the owning version-1 lockfile. Successful file-backed compiler paths may create or rewrite that lockfile; analysis and completion of editor buffers use the no-lockfile path. ## Ownership And Evaluation Order An import binds compile-time namespaces and declarations, not runtime resource values, so importing neither moves nor borrows a value and has no runtime evaluation position. Ownership begins when an imported declaration is called, constructed, or otherwise evaluated, using the declaration's normal parameter, receiver, field, and return contracts. Package traversal order cannot introduce initialization side effects. Lockfile and git-cache writes are tooling side effects that occur during successful graph resolution; they precede program execution and are not rolled back by a later runtime failure. ## Diagnostics `AU1101` means invalid syntax in a loaded Aura module or malformed TOML syntax in a manifest or lockfile. `AU2001` means module, import, package, or name resolution failed. `AU2002` means a cross-module type mismatch. `AU2004` means imported-call argument binding failed. `AU2999` means a manifest, lockfile, package-graph, source-root, cycle, limit, FFI authorization/dependency-report, or dependency-safety rejection without a narrower code. Through imported declarations, `AU3001` means use of a moved value, `AU3002` means a borrow violation, `AU3003` means a mutability violation, and `AU3004` means an invalid ownership mode. `AU3007` means an imported callable's clone-safety obligation was not satisfied or an imported nominal value would duplicate non-cloneable `random.Rng` state. File-backed `check`, `run`, and `build` render package-loading diagnostics through the normal compiler diagnostic path. `aura deps update` renders compiler-owned resolver failures in human form with the same stable `error[AU####]` code and exit status `1`; structured `--format` output is limited to `check`, `run`, and `build`. Malformed `deps` invocation is a command-usage error with status `2`, not a language diagnostic. ## Backend Support Package discovery, resolution, import loading, visibility, type checking, lockfile handling, and MIR lowering occur in the shared compiler front end. The MIR runtime and direct native backend therefore receive the same resolved declarations and module identities. Backend parity includes imported function behavior and cross-package trait dispatch. Built executables do not resolve source packages at runtime. Direct builds contain emitted program code; MIR-launcher builds contain serialized checked MIR and the runtime launcher. The package sources and git cache are compiler inputs, not runtime dependencies of the built program. ## Limits And Implementation-Defined Behavior Source roots are fixed at `src/`; package tests alone may enter through the root `tests/` directory. Each package may declare at most 1,024 direct dependencies, and one graph may contain at most 4,096 packages. Workspace membership is an exact normalized path list, not a glob, and membership does not imply a dependency. Registry/version dependencies, publish, install, wildcard imports, relative imports, and implicit workspace dependencies are unavailable. Git commands default to a 60-second timeout, disable interactive credential prompts, and use the cache and symlink checks documented above. Cache location follows `XDG_CACHE_HOME`, then `HOME`, then a temporary fallback. Network availability, git transport, filesystem canonicalization, and credentials are host-dependent. These controls protect resolver operation; they do not establish trust in dependency source code. ## Status Single packages, exact-path workspaces, path dependencies, pinned and moving git selectors, deterministic lockfile version 1, package visibility, import aliases, cross-package trait dispatch, editor no-lockfile analysis, package-local FFI authorization, and exact root FFI dependency reporting are implemented and maintained. No package semantics on this page are provisional. Registry resolution, publishing, installation, alternative source roots, workspace globs, wildcard or relative imports, implicit re-exports, and import-time initialization are outside the Aura 0.3 language contract. ## Source: docs/manual/performance.md # Performance Aura tracks performance with reproducible programs, named hardware, pinned source commits, raw observations, and content hashes. The current measurements show where the compiler and runtime are already competitive and where later releases need focused optimization. This page is the performance record for the Aura 0.3 technical preview. It is separate from the language's semantic guarantees. ## Current Measurements The tables below were collected from exact programs in a clean detached checkout at commit `18c45ac` on one post-reboot Mac14,9 with an Apple M2 Pro (10 cores) and 16 GiB of memory. The recorded boot was 30 July 2026 at 23:02:25. The comparison interpreter was Xcode CPython 3.9.6. ### Control-Plane Workloads For the four protocol workloads, the harness validates an exact `READY` record, starts the clock when it sends `GO`, and stops at the exact `DONE` record. Lower is faster. “Aura / CPython” is the ratio of medians. | exact protocol workload | Aura median | CPython median | Aura / CPython | | --- | ---: | ---: | ---: | | naive recursive `fib(30)` | 93.875250 ms | 158.491666 ms | 0.592304 | | create and join 10,000 tasks | 101.743042 ms | 51.950667 ms | 1.958455 | | 20-client delayed loopback TCP fan-out | 104.505375 ms | 108.605459 ms | 0.962248 | | 16-cycle retrying HTTP worker | 429.291292 ms | 520.447791 ms | 0.824850 | The TCP shape uses 20 pre-bound loopback listeners. Aura 0.3 rejects transfer of an accepted `TcpStream` into a handler task (`AU3008`), and a single listener would serialize the handlers. The task measurement includes creation and join of all 10,000 tasks after `GO`. The retry measurement executes the same status and delay schedule in both programs. ### Integer Loops The V6 integer loops are whole-process measurements. Startup-adjusted values subtract a same-repetition startup control and estimate the loop cost. | exact 10,000,000-iteration comparison | Aura whole process | CPython whole process | Aura startup-adjusted | CPython startup-adjusted | | --- | ---: | ---: | ---: | ---: | | Aura `int32` / CPython integer | 36.620333 ms | 321.096625 ms | 31.037083 ms | 295.458959 ms | | Aura `int64` / CPython integer | 13.724042 ms | 321.096625 ms | 7.7378125 ms (10/11 valid) | 296.966042 ms (10 aligned pairs) | Python has one arbitrary-precision integer lane, so the same CPython program is shown against Aura's two fixed-width lanes. ### Numeric Arrays Numeric Arrays were measured with NumPy 2.0.2 using one million `float64` elements and 11 paired single-thread observations on the same host. | exact Array workload | Aura median | NumPy median | Aura / NumPy | | --- | ---: | ---: | ---: | | fresh owned elementwise add | 1.142461 ms | 0.251602 ms | 4.540751 | | existing-array sum reduction | 1.150392 ms | 0.174065 ms | 6.608975 | The [Numeric Arrays](/manual/numeric-arrays) chapter records the complete Array methodology and current API boundaries. ## Current Performance Gaps The measurements identify two immediate gaps. Creating and joining 10,000 Aura tasks takes about 1.96 times the CPython comparison workload. The measured Aura Array addition and reduction kernels take about 4.54 and 6.61 times their NumPy counterparts. The MIR backend also carries interpreter and synchronization costs, so the direct native backend is the performance path. These gaps are engineering targets. They do not change Aura's ownership, failure, or concurrency semantics. ## Performance Direction Later Aura releases will focus on closing the measured gaps while preserving the language contract. The active direction includes: - reducing task creation, join, wake, and scheduler synchronization overhead; - expanding direct-backend optimization across call boundaries, loops, and temporary values; - reducing allocation and copying in numeric workloads; - adding specialized and vectorized Array kernels as the Array surface grows; - profiling model-serving, agent-runtime, networking, and queue workloads at realistic concurrency levels; and - keeping MIR and direct-backend behavior byte-compatible while the native path becomes faster. Performance work remains benchmark-driven. A change closes a gap when the repository harness reproduces the improvement on pinned workloads and the full correctness and backend-parity gates remain green. ## Evidence And Reproduction The release-performance raw evidence has SHA-256 `06cc1223630b1063c8a6806bf590449d6121a3be8d33e8dc1b0ffd17cee93ccb`. Its SHA-linked summary has SHA-256 `4490e0d169d9a031ae57f04ade772d22169189f71a949356234f529d40e56236`. The repository benchmark runner records commands, source and binary hashes, raw observations, medians, dispersion, host inventory, boot identity, and the environment policy. Run the maintained harness with: ```bash npm run bench:release-performance ``` The scalable-runtime and numeric-Array harnesses provide the deeper scheduler, memory, and kernel evidence referenced by their Manual chapters. ## Source: docs/manual/process.md # Process Module The `process` module runs child processes without a shell by default. Commands are explicit `list[str]` argv values. That means `["/bin/echo", "hello world"]` runs exactly one executable with one argument; Aura does not split strings or expand shell syntax. ```aura import process ``` Use `process.run(...)` for "start, wait, collect" workflows. Use `process.start(...)` when the parent needs pipes or a long-running child handle. Use `process.supervisor()` when the parent owns a named set of child processes and wants restart/event behavior. ## Stdio Configuration | API | Signature | Contract | | --- | --- | --- | | `process.inherit` | `inherit() -> process.Stdio` | Connects the child stream to the parent process stream. | | `process.null` | `null() -> process.Stdio` | Connects the child stream to the null device. | | `process.pipe` | `pipe() -> process.Stdio` | Creates a pipe that can be captured or accessed through `process.Pipe`. | `process.Stdio` variants: | Variant | Meaning | | --- | --- | | `process.Stdio.Inherit` | Use the parent stream. | | `process.Stdio.Null` | Discard output or provide EOF input. | | `process.Stdio.Pipe` | Create a pipe. | Prefer the functions (`process.pipe()`, `process.null()`, `process.inherit()`) in normal code. They are also capture-free function values; for example, `factory: def() -> process.Stdio = process.pipe` followed by `factory()` has the same result as the qualified direct call. ## process.run Signature: `process.run(command: list[str], cwd: Option[str] = None, env: dict[str, str] = {}, stdin: process.Stdio = process.null(), stdout: process.Stdio = process.pipe(), stderr: process.Stdio = process.pipe(), timeout: Duration = ..., group: bool = false) -> Result[process.Completed, process.Error]` `process.run(...)` starts a child, waits for it, and returns a `process.Completed` value. By default, stdin is null and stdout/stderr are captured. Omitting `timeout` uses an internal absence marker and supplies no caller deadline. No Duration value is that marker: an explicit negative timeout is invalid rather than unlimited. As with other builtin module functions, `process.run` is a capture-free first-class function value. A direct alias such as `runner = process.run` retains its parameter names and defaults, so `try runner(command)` still uses null stdin, captured stdout/stderr, and no caller deadline. Storing the value behind a structural function annotation, class field, or mutable collection erases those call-site extras and requires every positional argument. The `env` dictionary augments the inherited host environment and replaces inherited values with matching names. Aura never invokes a shell for `run` or `start`. Capture occurs only for streams configured with `process.pipe()` and each captured stream is capped at 64 MiB. ```aura def run_echo() -> Result[None, process.Error]: command = ["/bin/echo", "aura"] completed = try process.run(command, stdout=process.pipe(), stderr=process.pipe(), timeout=1s) try completed.check() print(completed.stdout().trim()) return Result.Ok(None) ``` Set `group=true` when the child may spawn descendants and the parent should clean up the whole process group on maintained Unix hosts. ## process.start Signature: `process.start(command: list[str], cwd: Option[str] = None, env: dict[str, str] = {}, stdin: process.Stdio = process.null(), stdout: process.Stdio = process.inherit(), stderr: process.Stdio = process.inherit(), group: bool = false) -> Result[process.Child, process.Error]` `process.start(...)` returns a live `process.Child`. The default is interactive-friendly: stdout and stderr inherit the parent's streams unless you ask for pipes. ```aura def start_cat() -> Result[process.Child, process.Error]: command = ["/bin/cat"] child = try process.start(command, stdin=process.pipe(), stdout=process.pipe(), stderr=process.pipe(), group=true) return Result.Ok(child) ``` The caller is responsible for waiting, killing, terminating, or closing the child. ## process.Child | API | Signature | Contract | | --- | --- | --- | | `stdin` | `stdin() -> Option[process.Pipe]` | Returns the child's piped stdin when `stdin=process.pipe()` was used. | | `stdout` | `stdout() -> Option[process.Pipe]` | Returns the child's piped stdout when `stdout=process.pipe()` was used. | | `stderr` | `stderr() -> Option[process.Pipe]` | Returns the child's piped stderr when `stderr=process.pipe()` was used. | | `wait` | `wait(timeout: Duration = ...) -> process.Wait` | Waits for exit and returns an exit, timeout, cancellation, or failure outcome. | | `wait_or_none` | `wait_or_none(timeout: Duration = ...) -> Result[Option[process.ExitStatus], process.Error]` | Returns `Ok(Some(status))` on exit, `Ok(None)` on timeout, and `Err(...)` for cancellation or wait failure. | | `wait_ok` | `wait_ok(timeout: Duration = ...) -> Result[process.ExitStatus, process.Error]` | Returns the exit status only for successful exits; non-zero status and wait failures become `process.Error`. | | `kill` | `kill() -> Result[None, process.Error]` | Kills the child immediately. With `group=true`, targets the process group on maintained Unix hosts. | | `terminate` | `terminate() -> Result[None, process.Error]` | Requests graceful termination. With `group=true`, targets the process group on maintained Unix hosts. | | `close` | `close() -> None` | Closes the child resource, terminating it if still running. | `process.Wait` variants: | Variant | Meaning | | --- | --- | | `Exited(status: own process.ExitStatus)` | The child exited or was signaled. | | `TimedOut` | The wait timeout expired. | | `Cancelled` | Cancellation interrupted the wait. | | `Failed(error: own process.Error)` | Waiting failed. | `process.ExitStatus` variants: | Variant | Meaning | | --- | --- | | `Exited(code: own int32)` | The process exited with a numeric code. | | `Signaled(signal: own int32)` | The process was terminated by a signal on platforms that expose signal status. | ## process.Pipe | API | Signature | Contract | | --- | --- | --- | | `read_all` | `read_all() -> Result[str, process.Error]` | Reads remaining strict UTF-8 text until EOF, capped at 64 MiB. Use byte APIs for arbitrary output. | | `read_line` | `read_line(timeout: Duration = ...) -> Result[Option[str], process.Error]` | Reads one strict UTF-8 line without its trailing LF/CRLF, `Ok(None)` only on EOF, or an error. | | `read_bytes` | `read_bytes(max_bytes: int32, timeout: Duration = ...) -> Result[Option[list[uint8]], process.Error]` | Reads up to `max_bytes` raw bytes and returns `Ok(None)` only at EOF. `max_bytes` must be in `1..=67108864`. | | `write_all` | `write_all(text: str, timeout: Duration = ...) -> Result[None, process.Error]` | Writes all text. | | `write_bytes` | `write_bytes(bytes: list[uint8], timeout: Duration = ...) -> Result[None, process.Error]` | Writes all bytes. | | `flush` | `flush() -> Result[None, process.Error]` | Flushes buffered pipe output. | | `close` | `close() -> None` | Closes the pipe handle. | A pipe deadline expires as `Err(process.Error.TimedOut)`; cancellation becomes `Err(process.Error.Cancelled)`. Neither outcome is reported as `Ok(None)`. `read_bytes(0, ...)` and requests above 64 MiB return `process.Error.Io(io.Error.InvalidInput)`. Close a child's stdin pipe when the child expects EOF: ```aura def close_stdin(child: process.Child) -> Result[None, process.Error]: match child.stdin(): case Option.Some(pipe): try pipe.write_all("hello\n") pipe.close() case Option.None: pass return Result.Ok(None) ``` ## process.Completed `process.Completed` is returned by `process.run(...)`. | API | Signature | Contract | | --- | --- | --- | | `status` | `status() -> process.ExitStatus` | Returns the captured exit status. | | `success` | `success() -> bool` | Returns `true` when the status is exit code `0`. | | `stdout` | `stdout() -> str` | Returns captured stdout decoded as strict UTF-8. Invalid UTF-8 raises a runtime diagnostic; use `stdout_bytes` for untrusted output. | | `stdout_bytes` | `stdout_bytes() -> list[uint8]` | Returns captured stdout as raw bytes. | | `stderr` | `stderr() -> str` | Returns captured stderr decoded as strict UTF-8. Invalid UTF-8 raises a runtime diagnostic; use `stderr_bytes` for untrusted output. | | `stderr_bytes` | `stderr_bytes() -> list[uint8]` | Returns captured stderr as raw bytes. | | `check` | `check() -> Result[None, process.Error]` | Returns `Ok(None)` for successful exit status, otherwise `Err(...)`. | Use `check` when a command failure should stop the current `Result`-returning function: ```aura def must_succeed() -> Result[None, process.Error]: completed = try process.run(["/bin/false"], timeout=1s) try completed.check() return Result.Ok(None) ``` Use byte methods for tools that may emit binary or non-UTF-8 output. ## process.supervisor ```aura process.supervisor() -> process.Supervisor ``` A supervisor is a resource that owns named child process specs and emits lifecycle events. Bind it with `with` whenever possible: ```aura def wait_for_worker() -> Result[process.SupervisorWait, process.Error]: with supervisor = process.supervisor(): try supervisor.start(name="worker", command=["/bin/sleep", "1"]) return Result.Ok(supervisor.wait(timeout=2s)) ``` ## process.Supervisor | API | Signature | Contract | | --- | --- | --- | | `start` | `start(name: own str, command: own list[str], cwd: own Option[str] = ..., env: own dict[str, str] = ..., stdin: own process.Stdio = ..., stdout: own process.Stdio = ..., stderr: own process.Stdio = ..., restart: own process.RestartPolicy = ..., backoff: own Duration = ..., max_restarts: own int32 = ..., group: own bool = ...) -> Result[None, process.Error]` | Starts a named child under supervision and retains the owned configuration needed for restarts. Names must be unique within the supervisor. | | `wait` | `wait(timeout: Duration = ...) -> process.SupervisorWait` | Waits for the next supervisor event, timeout, or cancellation. | | `wait_or_none` | `wait_or_none(timeout: Duration = ...) -> Result[Option[process.SupervisorEvent], process.Error]` | Returns `Ok(Some(event))`, `Ok(None)` on timeout, or `Err(...)` on cancellation or wait failure. | | `stop` | `stop() -> Result[None, process.Error]` | Stops every supervised child and clears the supervisor. | | `is_empty` | `is_empty() -> bool` | Returns `true` when no services are running or pending restart. | | `close` | `close() -> None` | Closes the supervisor, stopping all managed children. | Runtime defaults for `Supervisor.start(...)` are: | Parameter | Default | | --- | --- | | `cwd` | `None` | | `env` | empty dictionary | | `stdin` | `process.null()` | | `stdout` | `process.inherit()` | | `stderr` | `process.inherit()` | | `restart` | `process.RestartPolicy.OnFailure` | | `backoff` | `100ms` | | `max_restarts` | unlimited when omitted; `-1` is accepted as unlimited | | `group` | `true` | When restart is enabled, `backoff` must be at least `10ms`. `process.RestartPolicy` variants: | Variant | Meaning | | --- | --- | | `Never` | Do not restart. | | `OnFailure` | Restart only when the child exits unsuccessfully. | | `Always` | Restart after every exit while restart limits allow it. | `process.SupervisorEvent` variants: | Variant | Meaning | | --- | --- | | `Exited(name: own str, status: own process.ExitStatus, restart_count: own int32)` | A child exited and was not restarted. | | `Restarted(name: own str, status: own process.ExitStatus, restart_count: own int32)` | A child exited and a replacement was started. | | `Failed(name: own str, error: own process.Error, restart_count: own int32)` | A child failed to start or restart. | `process.SupervisorWait` variants: | Variant | Meaning | | --- | --- | | `Event(event: own process.SupervisorEvent)` | A supervisor event is available. | | `TimedOut` | No event arrived before timeout. | | `Cancelled` | Cancellation interrupted the wait. | An invalid `Supervisor.wait` timer cannot be returned directly as `process.Error` because `wait` returns `process.SupervisorWait`. It maps exactly to `process.SupervisorWait.Event(process.SupervisorEvent.Failed("", process.Error.Io(io.Error.InvalidInput), 0))`: the synthetic name is `` and the synthetic restart count is zero. The `wait_or_none` return type has an error carrier, so the same invalid timer returns `Result.Err(process.Error.Io(io.Error.InvalidInput))` instead. ## process.Error | Variant | Meaning | | --- | --- | | `NoCommand` | The command list was empty. | | `TimedOut` | A process operation timed out. | | `Cancelled` | Cancellation interrupted the operation. | | `Io(error: own io.Error)` | The operation failed with an I/O error. | | `Spawn(message: own str)` | The child could not be spawned. | | `Other(message: own str)` | A process-specific failure not covered by another variant. | ## Cleanup Rules Child, pipe, and supervisor values are resources. Prefer `with` for supervisors and call `close()` on children and pipes when ownership is not scoped. For child processes, `close()` terminates a still-running child. With `group=true`, cleanup targets the process group on maintained Unix hosts. When `process.run` times out or its Aura task is cancelled, the runtime terminates the child and waits for cleanup; with `group=true` it applies that policy to the process group on maintained Unix hosts. As with all host I/O, cancellation cannot retroactively undo side effects already performed by the child. ## Grammar The process module adds no source-language grammar. Commands are ordinary `list[str]` expressions passed to ordinary calls; Aura does not parse shell syntax, split one command string, expand variables, interpret redirections, or construct pipelines. Named arguments, `Duration` literals, `Result`, `Option`, `try`, `match`, and `with` use their general grammar. An omitted parameter displayed with `= ...` selects the documented builtin default. The ellipsis is reference notation, not a source expression. Process and standard-I/O variants use ordinary qualified enum construction and pattern syntax. For `process.run`, the omitted timeout is represented internally rather than by a sentinel Duration. Explicit zero is a real immediate deadline, and an explicit negative value is invalid input. This distinction is Accepted under ADR-0019. ## Typing Rules The function and method signatures above are normative. Commands are `list[str]`, environment overlays are `dict[str, str]`, working directories are `Option[str]`, and timeout parameters are `Duration`. Fallible start/run/pipe/control operations use `process.Error`; wait APIs deliberately distinguish enum, `Option`, and `Result` outcomes as shown in their tables. `process.Child`, `process.Pipe`, and `process.Supervisor` are non-copy resources. Kill, terminate, pipe write/flush/close, and supervisor start/stop/close operations require mutable receiver places. `Supervisor.start` consumes every configuration argument marked `own`, because the supervisor retains that configuration for possible restart. `Completed.stdout()` and `stderr()` are trapping text accessors; the byte accessors are total over captured bytes. ## Runtime Semantics `run` and `start` invoke exactly the executable and argument list supplied, inherit the host environment, then apply `env` entries as replacements or additions. `run` waits and captures only streams configured as pipes. `start` returns immediately with a live child and any configured pipe endpoints. Repeated child pipe accessors return handles to the same underlying endpoint, so cursor state and close state are shared. `Child.wait` reports exit, timeout, cancellation, or failure without automatically terminating a still-live child. By contrast, timeout or cancellation of `process.run` terminates the child and waits for cleanup. A negative, host-unrepresentable, or deadline-overflowing timeout/backoff is `process.Error.Io(io.Error.InvalidInput)` wherever the declared process outcome can carry that error; deadline overflow never becomes an unlimited wait. `Completed.check` converts a non-success status into `process.Error`; invalid captured UTF-8 in `stdout()` or `stderr()` is a runtime diagnostic, while the byte accessors return the original bytes. Supervisor restarts, counts, events, defaults, and minimum backoff follow the tables above. ## Ownership And Evaluation Order Arguments are evaluated left to right before process creation. `run` and `start` share their Aura arguments for the call and copy the required command, environment, and path data into host process state; they do not retain Aura borrows after returning. A supervisor takes ownership of retained configuration. Child, pipe, completed-output, status, error, and event values returned from an operation are owned by the caller. Moving a resource invalidates the source binding. `with` closes a supervisor on every scope exit; explicit child and pipe `close()` operations close shared handle state, and child close terminates a process still running. Cleanup is ordered after body evaluation but cannot undo child filesystem, network, or other external side effects already performed. ## Diagnostics Unknown process members use `AU2001`, type mismatches use `AU2002`, invalid argument binding uses `AU2004`, and remaining static rejections use `AU2999`. Use after moving a process resource uses `AU3001`, borrow conflicts use `AU3002`, and calling a mutating method through an immutable place uses `AU3003`. Empty commands, spawn failures, timeouts, cancellation, invalid byte counts, invalid timeout/backoff values or deadlines, closed pipes, non-zero status checked through `check`, and ordinary host I/O failures are typed `process.Error` values. Invalid timer inputs use `process.Error.Io(io.Error.InvalidInput)`. Decoding invalid captured bytes through `Completed.stdout()` or `stderr()` is deliberately a runtime trap with code `AU4005`; use `stdout_bytes()` or `stderr_bytes()` when output encoding is not guaranteed. ## Backend Support Process creation, capture, pipes, waiting, supervisor behavior, typed errors, and cleanup are implemented in the MIR runtime and direct native backend. Command-list handling, environment overlay, capture bytes, timeout outcomes, and ownership are backend-parity requirements. Process-group creation and signaling are maintained on Unix hosts. On unsupported hosts, requesting group behavior returns a typed process error rather than silently weakening cleanup. Executable lookup, signals, and exit-status details otherwise follow host process facilities. ## Limits And Implementation-Defined Behavior Each `process.run` captured stream and each whole-pipe read is capped at 64 MiB; bounded pipe byte reads accept `1..=67108864`. This stream ceiling is independent of the larger filesystem whole-read limit. Text access is strict UTF-8. Supervisor restart backoff must be at least 10 ms when restart is enabled; omitted or `-1` maximum restarts means unlimited. There is no shell, command-string parser, pipeline builder, pseudo-terminal API, daemon manager, sandbox, resource-limit API, or portable signal-number abstraction. Executable discovery, path syntax, inherited environment, signal availability, numeric exit behavior, graceful-termination meaning, scheduling, and side effects are host-dependent. Timeouts and cancellation bound Aura's wait but cannot retract child actions that already occurred. Group cleanup of descendants is a maintained Unix contract, not a portable guarantee for every host process tree. ## Status One-shot execution, live children, standard-I/O configuration, pipes, completed output, status checking, supervisor restart/event behavior, typed failures, and Unix process-group cleanup are implemented and maintained in Aura 0.3. The fixed stream-cap policy recorded by ADR-0018 is Accepted, as is the omitted-timeout and invalid host-timer policy recorded by ADR-0019. Shell evaluation, pipelines, pseudo-terminals, Windows process groups, portable signal control, sandboxing, and operating-system service management are unavailable. They are future, non-normative facilities rather than implicit behavior of the current API. ## Source: docs/manual/randomness.md # Randomness Module Aura separates reproducible pseudo-random streams from security-sensitive operating-system randomness. Import `random`, construct an explicitly seeded `random.Rng` when results must repeat, and use the module's `secure_*` functions only when results must be unpredictable. | API | Signature | Contract | | --- | --- | --- | | `random.Rng` | `Rng(seed: int64) -> random.Rng` | Creates one deterministic xoshiro256** stream from the exact signed seed bit pattern. | | `random.Rng.next_int` | `next_int(lo: int64, hi: int64) -> int64` | Advances the stream and returns a uniform value in `[lo, hi)`. | | `random.Rng.next_float` | `next_float() -> float64` | Advances the stream and returns a binary64 value in `[0.0, 1.0)`. | | `random.Rng.shuffle` | `shuffle[T](values: mut list[T]) -> None` | Advances the stream while shuffling `values` in place. | | `random.secure_int` | `secure_int(lo: int64, hi: int64) -> int64` | Returns an OS-secure uniform value in `[lo, hi)`. | | `random.secure_bytes` | `secure_bytes(n: int64) -> list[uint8]` | Returns exactly `n` bytes from the operating system's secure random source. | The deterministic generator is reproducible, not cryptographically secure. Never use `random.Rng` for keys, tokens, nonces, salts, session identifiers, or anything whose predictability could affect security. Secure calls do not use, seed, or advance any deterministic `Rng` stream. ## Deterministic Algorithm This section is normative and is sufficient to reconstruct Aura's seeded stream without consulting the compiler implementation. Let every value in this section be an unsigned 64-bit word, let `+`, `*`, `<<`, and exclusive-or wrap or truncate to 64 bits, and let `rotl(x, k)` rotate `x` left by `k` bits. The signed `int64` seed is first reinterpreted as its two's-complement unsigned 64-bit pattern. Starting with `split_state = seed_bits`, each of four SplitMix64 steps performs: 1. `split_state = split_state + 0x9E3779B97F4A7C15`. 2. `z = split_state`. 3. `z = (z xor (z >> 30)) * 0xBF58476D1CE4E5B9`. 4. `z = (z xor (z >> 27)) * 0x94D049BB133111EB`. 5. The step output is `z xor (z >> 31)`. The four consecutive outputs become `s0`, `s1`, `s2`, and `s3`. One xoshiro256** raw draw then returns and transitions in this exact order: 1. `result = rotl(s1 * 5, 7) * 9`. 2. `t = s1 << 17`. 3. `s2 = s2 xor s0`. 4. `s3 = s3 xor s1`. 5. `s1 = s1 xor s2`. 6. `s0 = s0 xor s3`. 7. `s2 = s2 xor t`. 8. `s3 = rotl(s3, 45)`. `next_int(lo, hi)` first requires `lo < hi`. Let `span` be the exact unsigned mathematical difference `hi - lo`, which is in `1..=2^64 - 1`. Let `threshold = 2^64 mod span`, equivalently the unsigned-wrapping expression `(-span) mod span`. Draw raw words until one is at least `threshold`, then return the exact signed value `lo + (raw mod span)`. This rejection makes every result equally likely. A one-value interval still consumes one raw draw. `next_float()` consumes one raw word and returns `float64(raw >> 11) * 2^-53`. The 53-bit integer is exactly representable as a binary64 value, so the result is always at least `0.0`, always less than `1.0`, and is selected from the `2^53` evenly spaced values in that interval. `shuffle(values)` uses descending Fisher-Yates. For `i` from `len - 1` down to `1`, inclusive, it obtains `j` through the same `next_int(0, i + 1)` rule and swaps positions `i` and `j`. A list of length zero or one is unchanged and consumes no raw draws. Longer shuffles consume one accepted index draw per iteration plus any raw draws rejected by the unbiased range mapping. ## Conformance Oracles For seed `42`, the initialized state words are: | Word | Hexadecimal value | | --- | --- | | `s0` | `bdd732262feb6e95` | | `s1` | `28efe333b266f103` | | `s2` | `47526757130f9f52` | | `s3` | `581ce1ff0e4ae394` | The first five raw xoshiro256** results are, in order: 1. `1546998764402558742` 2. `6990951692964543102` 3. `12544586762248559009` 4. `17057574109182124193` 5. `18295552978065317476` Fresh seed-42 generators produce these public results: - consecutive integer calls produce `next_int(0, 10) == 2`, `next_int(-5, 6) == 2`, `next_int(-9223372036854775808, 9223372036854775807) == 3321214725393783201`, and `next_int(7, 8) == 7` - consecutive floating calls produce `0.08386297105988216`, `0.3789802506626686`, and `0.6800434110281394` - shuffling `[0, 1, 2, 3, 4, 5]` produces `[3, 5, 4, 1, 2, 0]` These values, the mapping rules above, and the no-draw rule for zero/one-length shuffles are compatibility tests, not merely illustrative examples. ## Secure Randomness `random.secure_int(lo, hi)` samples the half-open interval `[lo, hi)` without modulo bias using fresh bytes from the operating system's cryptographically secure random source. It has no seed and no reproducibility guarantee. `random.secure_bytes(n)` requires `0 <= n <= 2147483647`. The upper bound is a fixed per-call resource and safety ceiling for allocation and operating-system entropy requests, independently of Aura's public `list` length domain. The function allocates a fresh `list[uint8]` and fills it from that same OS source. `secure_bytes(0)` returns an empty list without contacting the entropy source. A count above the secure-random request ceiling fails with `AU4005` before allocation or entropy is requested. For any accepted positive count, Aura either returns exactly that many initialized bytes or fails; it never returns a short list and never substitutes deterministic data. The exact secure outputs are intentionally unspecified. Their distribution, length, failure category, and no-fallback rule are specified. Host entropy and allocation availability remain external conditions. ## Example ```aura import random def main() -> int32: mut rng = random.Rng(42) print(rng.next_int(0, 10)) print(rng.next_int(-5, 6)) mut values: list[int64] = [0, 1, 2, 3, 4, 5] mut shuffle_rng = random.Rng(42) shuffle_rng.shuffle(values) print(values) return 0 ``` This prints `2`, `2`, and `[3, 5, 4, 1, 2, 0]` on separate lines. The maintained program is `examples/randomness/deterministic_rng.au`. ## Grammar The module adds no source-language grammar. `import random`, qualified names, constructor calls, mutable bindings, method calls, named arguments, generic `list[T]`, and ordinary module functions use the forms defined elsewhere in this Manual. There is no random literal and no implicit process-global generator. ## Typing Rules The signatures in the opening table are normative. Seeds, integer bounds, and secure byte counts are `int64`; `next_float` returns `float64`; secure bytes are the ordinary bytes representation `list[uint8]`. Bounds are half-open and must satisfy `lo < hi` at runtime. `random.Rng` is a non-copy, non-resource builtin module type. Its three methods have mutable receivers. `shuffle` is generic over every element type `T`; it requires no copy, clone, equality, ordering, or user-trait bound because it only exchanges owned list positions in place. The argument must be a mutable `list[T]` place, including a supported mutable field projection. The no-clone rule is transitive. A type that contains `random.Rng`, whether through a collection, user class, enum payload, or another ordinary value wrapper, cannot be used with an operation that would clone the contained generator. This rejects direct `random.Rng.clone()` calls and clone-producing collection or task observations such as `list.copy`, `list.get`, `dict.copy`, `dict.get`, `dict.keys`, `dict.values`, `dict.items`, `set.copy`, `Task.result`, `Task.result_or_none`, `Task.result_or`, `wait_any`, and `wait_all` when the produced value would contain an `Rng`. A polymorphic clone-producing operation over an unresolved type parameter instead infers a clone-safety obligation. The generic declaration remains valid, the obligation propagates through generic-to-generic calls and imports, and an unsafe concrete specialization is rejected with `AU3007`. Task and Queue handles are clone barriers: an allowed handle copy does not clone its stored payload. This is a clone-safety statement. Accepted ADR-0033 separately forbids `random.Rng` at task-result and Queue-payload boundaries with `AU3008`, and a Task carrying a non-repeatable result is not copyable. Operations that transfer one owned value within one owning task remain valid; examples include `list.pop`, `dict.remove`, ordinary moves, and shuffling a `list[random.Rng]` in place. Clone-safety obligations are part of callable and trait method contracts. An obligation inferred by a trait default body is substituted through `Self` and the trait's type arguments for concrete and bound-based dispatch. An explicit implementation cannot silently require clone safety that its trait method does not require; such strengthening is rejected with `AU3007`. Operator-trait and `From` dispatch enforce the same contract. The module exposes no `random.Error` enum. Secure operations return plain values and use runtime diagnostics for invalid requests or unavailable host facilities. ## Runtime Semantics Constructing an `Rng` applies the exact seed expansion above. Each successful state-consuming method advances that one stream in the specified order. `next_int` may consume additional raw words only when rejection sampling requires them; `next_float` consumes exactly one; shuffle consumes according to its loop and rejection rules. Secure functions do not observe or mutate an `Rng`. `Rng` does not define equality. `==`, `!=`, membership, list equality searches, set insertion, and dictionary-key use are rejected with `AU2008`; generator identity and the four state words are not observable. Human rendering through `print` or f-string interpolation is exactly `` and does not advance the stream. No public state export or import exists. There is also no public operation that clones the generator, including a collection or task-result alias that would clone it indirectly. Invalid ranges and counts are checked before producing a return value. Secure entropy or allocation failure terminates the operation with the diagnostic specified below; partial or deterministic fallback output is forbidden. ## Ownership And Evaluation Order Assigning or passing an `Rng` through an owned position moves it. A moved source cannot be reused. `next_int`, `next_float`, and `shuffle` require a mutable generator place; an ordinary immutable binding is insufficient. A function that should advance a caller's stream takes `rng: mut random.Rng`. Moving a generator into or out of a collection preserves its single owner. Cloning an enclosing value would not, so the transitive clone restrictions in the typing rules apply even when the generator is nested several type layers deep. An allowed Task- or Queue-handle copy is different: it copies handle identity, not the `Rng` value behind that handle, and therefore does not duplicate generator state. Queue and Task handle state is synchronized for cross-worker use; the `Rng` itself remains non-`Transfer` and stays on its owning task. `shuffle(values: mut list[T])` borrows the caller's list exclusively, mutates that same place, and returns `None`; it does not move, clone, or replace the list or its elements. Projected mutable lists receive the same writeback semantics as root bindings. Ordinary call order applies: the receiver is evaluated before supplied arguments, and supplied arguments are evaluated in call-site source order even when named. Generator state changes occur at the method-call position. The secure functions have no shared generator state; each call performs its own OS request except the specified zero-byte fast path. ## Diagnostics `AU2001` reports an unavailable `random` name or unknown member. `AU2002` reports seed, bound, byte-count, receiver, list, or return-type mismatches. `AU2004` reports invalid arity, argument names, or positional/named binding. `AU2006` rejects a trait method on `random.Rng` whose name collides with a builtin `Rng` method. `AU3001` reports reuse of a moved generator. `AU3002` reports conflicting borrows involving the mutable receiver or shuffled list, including passing an immutable list place to `shuffle` or trying to shuffle a list while its exclusive borrow is unavailable. `AU3003` reports a state-consuming method called through an immutable generator place. `AU3007` rejects direct or transitive use of a clone-producing operation when its produced value contains, or may contain, non-cloneable `random.Rng` state. It also reports an unsafe generic specialization, an unprovable concrete clone requirement, or a trait implementation that would strengthen its declared contract. `AU4003` reports `lo >= hi` for either integer function and a negative `secure_bytes` count. `AU4005` reports a byte count above the fixed secure-random request ceiling of `2147483647`, failure to obtain secure operating-system entropy, or failure to allocate/fill the requested secure byte list. The over-limit diagnostic is emitted before any allocation or entropy request. Because the public return types are plain values, these runtime conditions are diagnostics, not `Result` or `random.Error` values. ## Backend Support The MIR runtime and direct native backend implement the same deterministic algorithm, seed reinterpretation, rejection threshold, float mapping, shuffle order, rendering, ownership, and diagnostics. For one seed and call sequence, deterministic output MUST be bit-for-bit identical across backends and supported hosts. Secure output is not compared byte-for-byte between executions or backends. Both backends MUST use the host's secure random facility, preserve the exact length and half-open uniformity contracts, use the same diagnostic categories, and provide no fallback to the deterministic generator. ## Limits And Implementation-Defined Behavior The deterministic surface has only one stream algorithm and no process-global generator, reseeding method, state serialization, jump/substream operation, distribution library, random choice helper, or public clone route. Integer sampling is limited to `int64` half-open ranges, floating sampling to uniform `float64` values in `[0.0, 1.0)`, and shuffle to mutable `list[T]` values. Secure byte count is an `int64`, but each request is capped at `2147483647` as a fixed secure-random resource and safety ceiling. The ceiling does not define or narrow the public `list` length domain or the result of `list.len()`. Within that request ceiling, the allocation must also fit the host address space and allocator. Either failure reports `AU4005`. The operating system chooses the secure entropy implementation and actual returned values. No deterministic ordering relationship exists between secure calls, tasks, backends, processes, or hosts. The deterministic algorithm and seeded results are not implementation-defined: they are stable throughout the Aura 0.3.x series as fixed above. They remain unsuitable for cryptography regardless of seed secrecy. ## Status The constructor, deterministic methods, secure functions, move-only ownership, backend parity, and documented diagnostics are maintained Aura 0.3 surface. The exact algorithm, mapping, compatibility window, identity/rendering policy, and secure-failure boundary are accepted under ADR-0020. No other random distributions, secure floating function, global generator, derived sampling trait, or `random.Error` type is part of Aura 0.3. ## Source: docs/manual/statements.md # Statements Statements introduce and update bindings, control execution, or evaluate an expression for its effects. This chapter defines their legality and observable flow. Exact syntax is normative in [Grammar](/manual/grammar#suites-and-statements), compile-time legality in [Static Semantics](/manual/static-semantics), and runtime sequencing and cleanup in [Execution Model](/manual/execution-model). ## Statements, Items, And Suites Aura 0.3 statements are: - binding and assignment - expression statements - `return` - `assert` - `if` / `elif` / `else` - `while` and `for` - statement-form `match` - `with` - `break`, `continue`, and `pass` Class, enum, function, trait, and implementation declarations are items, not statements. Items are module-level; declaration members such as fields, enum variants, and methods appear only in their permitted item bodies. Nested functions, classes, enums, traits, and implementations are not supported. A compound statement header ends with `:` and `NEWLINE`, followed by an indented suite. Suites contain one or more statements: ```aura if ready: print("ready") record_success() ``` One-line suites such as `if ready: print("ready")` are not valid. Blank and comment-only lines do not make a suite nonempty; use `pass` when no operation is required. Statements are terminated by logical newlines. A physical newline suppressed inside an open delimiter is not a statement terminator. Aura has no semicolon and does not permit multiple statements on one physical line. ## Bindings And Assignment The first assignment to a simple name introduces a binding: ```aura name = "aura" count: int32 = 0 ``` The binding's type is its annotation when present, otherwise the initializer type. The initializer must have exactly that type after contextual literal inference. `mut` makes a newly introduced binding assignable and usable as a mutable place: ```aura def main(): mut count: int32 = 0 count = 1 count += 2 ``` Reassignment requires an existing mutable binding and preserves its type. `mut` does not mean dynamically typed, and it does not make values globally mutable through aliases. `from` is a contextual identifier and is legal as a binding and assignment target when the token sequence is not a from-import: ```aura def main(): mut from = "cache" from = "network" ``` ### Assignment Targets An assignment target begins with a name and may continue through fields or indices: ```aura point.x = 4.0 values[0] = 9 counts["ready"] = 2 user.profile.name = "Ada" ``` Calls cannot occur in a place-assignment target. A tuple unpack target contains only names and recursively parenthesized name targets: left, right = pair name, (x, y) = record The right side is evaluated once. Its exact tuple shape and corresponding element types must match the target. A top-level comma distinguishes unpacking from an expression; tuple value expressions themselves require parentheses. Tuple unpacking uses plain `=`, not a type annotation, leading `mut`, compound assignment, member leaf, or index leaf. A type annotation is allowed only on a simple-name target. `mut` also belongs only to a new simple-name binding. These forms are invalid: ```aura # Invalid. # point.x: float64 = 4.0 # mut point.x = 4.0 ``` Field assignment requires a mutable base place and a declared field. List index assignment uses the `int64` index domain. Simple dict index assignment requires exactly the dictionary's key type and either replaces an equal key or inserts a new entry; an absent key is not a simple-assignment error. It accepts any value type. The key and value are owned storage positions, so each is consumed when non-copy, matching `set(key: own K, value: own V)`. ### Compound Assignment Aura supports the complete arithmetic compound-assignment family `+=`, `-=`, `*=`, `/=`, `%=`, and `//=`: ```aura count += 1 total *= scale pages //= page_size ``` A compound assignment requires an existing mutable, initialized target. It selects that target place once and uses exactly the corresponding binary operator dispatch. This includes an applicable user-defined operator trait for a root or projected target. For a copy target, it captures the current copied value before evaluating the right operand and stores a same-typed result into the originally selected place. Right-operand side effects therefore cannot change the captured left operand or retarget the store. A non-copy root or projected target remains borrowed across right-operand evaluation; an overlapping mutable borrow or consumption is rejected with `AU3002`. Direct indexed compound assignment requires a copy `list` element or `dict` value. A non-copy indexed element is rejected because reading it for read-modify-write would require either a hidden clone or a destructive move before an operation that may fail. Use an explicit safe read or ownership transfer followed by a simple write; for a dict, use `get(key)` or `remove(key)` and explicit simple assignment. Runtime overflow/division behavior is the same as for the corresponding expression operator. Integer `/=` is rejected with the integer `/` teaching diagnostic; use integer `//=` for a floor quotient. Floating `/=` remains true division. `//=` uses the builtin numeric or Duration rule when applicable and otherwise may dispatch through `FloorDiv.floor_div`; as with every compound assignment, the result must have the target's existing type. ### Assignment Evaluation A simple-name or field assignment evaluates its right side before creating or updating the target. Indexed assignment evaluates the collection place and then the index or key before its right side. Its non-copy collection base remains borrowed through those later inputs; an overlapping mutable borrow or consumption is rejected with `AU3002`, and no hidden deep clone is inserted. A simple dict assignment captures and, when non-copy, consumes that key before evaluating and consuming its value, so later value-side effects cannot change the selected key. Reassigning an exact moved binding or field reinitializes that place when the new value has the required type. Failed checked mutation produces the documented runtime failure or typed result and does not create a different language-level partial assignment contract. See [Ownership And Borrowing](/manual/ownership-and-borrowing) for moves, partial field moves, and mutable-place rules. ## Expression Statements Any expression may be used as a statement when its produced value is not needed: ```aura print("ready") queue.close() counter.increment() ``` The expression is fully evaluated, including moves, mutations, I/O, and runtime failures; its resulting value is discarded. A discarded `Result` is not implicitly propagated. Use `try` or `match` when failure must affect control flow. ## `return` `return` is legal only inside a function or method: ```aura def answer() -> int32: return 42 ``` The expression is evaluated before control returns. Its type must equal the declared return type. Bare `return` produces `None` and is valid only where `None` is a valid return. ```aura def maybe_log(enabled: bool): if not enabled: return print("enabled") ``` A non-`None` function must return on every statically reachable path. Returning runs active `with` cleanups in reverse nesting order before control reaches the caller. ## Conditional Statements `if`, zero or more `elif` branches, and an optional `else` select at most one suite: ```aura if value < 0: print("negative") elif value == 0: print("zero") else: print("positive") ``` Conditions must have exactly type `bool`. Aura does not convert strings, numbers, collections, resources, or classes by truthiness. Conditions are evaluated in source order until one is `true`. Only the selected suite executes. Static checking analyzes branches independently and conservatively merges ownership, partial-move, and initialization state across paths that can continue. ## `while` A `while` statement evaluates its condition before each iteration: ```aura def main(): mut attempts = 0 while attempts < 3: attempts += 1 ``` The condition must have type `bool`. A false first condition executes the body zero times. Aura 0.3 has no loop `else` clause. Moving a non-copy outer value for the first time inside a repeatable loop is rejected when it could make a later iteration invalid. Reinitialize the place on every continuing path or restructure ownership explicitly. ## `for` Iteration A `for` statement binds one name or recursively unpacks one tuple target for each value from an iterable: ```aura for value in values: print(value) for name, count in records: print(name) print(count) ``` Use `for value in own values:` when the loop deliberately consumes a `list` or `set` and needs owned element bindings. The collection moves once into a loop-private source at entry. Reinitializing the consumed `values` binding in the body does not switch or truncate that active iteration. Every target leaf is local to the body, does not escape, and cannot shadow a name already visible in the same scope. A tuple target must match the yielded tuple shape exactly. Maintained iterable forms include: | Form | Behavior | | --- | --- | | `for i in range(n):` | Yields `int64` values from zero up to `n`, excluding `n`. | | `for i in range(start, end):` | Yields `int64` values from `start` up to `end`, excluding `end`. | | `for value in values:` | Retains the list and yields shared access for non-copy elements. | | `for value in own values:` | Consumes the list and yields owned elements. | | `for value in mut values:` | Retains a mutable list and yields mutable access; the iterable place must be mutable. | | `for value in set:` | Retains the set and yields shared-borrowed access. | | `for value in own set:` | Consumes the set and yields owned elements. | | `for value in queue:` | Receives queue items under the scheduler-aware queue iteration contract. | | `for index, value in enumerate(seq):` | Yields `(int64, element)` pairs, counting positions from zero. | | `for left, right in zip(first, second):` | Yields one pair per shared position and stops at the shorter sequence. | When an iterable yields tuples, bare/shared collection iteration gives non-copy tuple leaves shared provenance; `own` collection iteration gives owned leaves; and bare Queue iteration receives an owned item and gives owned leaves. `mut` iteration with a tuple target is rejected because the minimal tuple surface has no recursive element writeback. `for value in mut set:` is not supported in Aura 0.3. Queue iteration receives values rather than traversing places: each item arrives owned and the queue handle is a copy value. Consequently `own` and `mut` are rejected for Queue iteration; use the bare form. That form evaluates and copies the Queue handle once at loop entry without freezing the source binding. Rebinding the source in the body does not switch later receives. Queue iteration ends according to close, cancellation, producer-completion, and task-failure rules defined in [Concurrency](/manual/concurrency). `enumerate` and `zip` are compiler-known loop forms rather than callable values. They are legal only as the iterable of a `for` statement; naming either one anywhere else reports `AU2005` and names the loop spelling. A user declaration of either name shadows the loop form, so an existing `def zip(...)` keeps its ordinary call meaning. Both forms read their operands by position, so each operand must be a `list[T]` or a `set[T]`; a `Range` or `Queue[T]` operand reports `AU2002`. Both iterate over the bare-loop borrow default: an ownership modifier on the loop reports `AU3002`, every operand stays shared-borrowed and frozen for the whole loop, and a non-copy element binding is a shared borrow that cannot be moved out. `enumerate` takes exactly one operand and `zip` exactly two, positionally; any other arity or a named argument reports `AU2004`. `zip` stops as soon as any operand has no value at the current position, so it performs `min(len(first), len(second))` iterations and never observes the longer sequence's tail. ```aura hosts = ["alpha", "beta"] ports = [80, 443, 8080] for index, host in enumerate(hosts): print(index) for host, port in zip(hosts, ports): print(port) ``` Range iteration accepts only the bare form. Every yielded `int64` is an independent copy, so `mut` has no place through which to write back and `own` has nothing to transfer. Either modifier reports `AU3004`, explains that ownership modifiers do not apply to these copy values, and suggests `for item in range(...):`. ## `break` And `continue` `break` and `continue` are legal only inside `for` or `while`: ```aura for value in range(10): if value == 5: break if value % 2 == 0: continue print(value) ``` `break` exits the nearest loop. `continue` begins its next iteration. If either operation exits an active `with` scope, that scope is cleaned up before loop control transfers. ## Match Statements Statement-form `match` evaluates its scrutinee exactly once and considers arms in source order. The first matching arm executes: ```aura match result: case Result.Ok(value): print(value) case Result.Err(message): print(message) ``` Every statement arm contains an indented suite. Inline statement arms such as `case Result.Ok(value): print(value)` are not valid. Inline arms are available only for match expressions whose arm body is one expression; see [Expressions](/manual/expressions#match-expressions). Matches over enums and booleans must be exhaustive unless `_` covers the remainder. Integer, float, and string literal matches require `_` because their value spaces are open. Duplicate, unreachable, type-incompatible, or wrong-arity patterns are rejected. `match own value` consumes a non-copy scrutinee. This includes a non-copy tuple, which is consumed as one whole value and unpacked into owned pattern bindings. Bare `match value` retains ownership and exposes shared enum-payload or tuple leaf access. `match mut value` permits enum-payload mutation and writeback, but a tuple pattern is rejected because recursive mutable tuple writeback is not part of the minimal surface. See [Enums And Pattern Matching](/manual/enums-and-match) for pattern forms. ## `with` And Scoped Cleanup Aura accepts two equivalent binding forms: ```aura with file = try fs.open("data.txt"): text = try file.read_all() print(text) ``` ```aura with TaskGroup() as group: group.start_soon(worker) ``` The first form is `with name = expression:`. The second is `with expression as name:`. Each form evaluates and consumes the resource expression, creates a fresh mutable managed binding, and registers cleanup after resource creation succeeds. Supported builtin resources define their cleanup behavior. A user class can be used when it is non-generic and declares exactly `close(mut self) -> None`. The managed value cannot be moved out in a way that prevents cleanup. The registered `close` operation runs exactly once when control leaves the body by: - normal fallthrough - `return` - `break` or `continue` that exits the scope - `try` error propagation - a maintained Aura runtime failure Nested cleanups run in reverse registration order. If the body is already failing and cleanup also fails, the body diagnostic remains primary. This contract is shared by `aura run` through the maintained MIR runtime and by native builds through the maintained native execution paths. Backend parity tests enforce the common contract. See [Execution Model](/manual/execution-model#resource-lifetime-and-cleanup). ## `assert` An assertion checks an invariant and either continues or produces an unrecoverable runtime diagnostic: assert ready assert response_code == 200, "expected a successful response" The condition must have exactly type `bool`. The optional message must have exactly type `str`. The condition evaluates exactly once. A true condition falls through without evaluating the message. A false condition evaluates the message exactly once and traps with `AU4001`. Without a message, the exact failure text is `assertion failed`; otherwise the supplied str is preserved exactly, including an empty or whitespace-only value. The diagnostic points to the `assert` keyword. A trap produced while evaluating the condition or message occurs first and remains primary. Assertion failure runs active `with` cleanups, and the assertion remains primary if cleanup also fails. An assertion has ordinary fallthrough for static analysis. It does not refine the type or possible values of a later expression, and the compiler does not strip it in any build mode. Assertions are valid executable top-level statements in a script entry module; the ordinary rule against combining top-level execution with a local `main` still applies. See [Assertions](/manual/assertions) for the complete contract and executable example. ## `pass` `pass` performs no operation and produces no binding: ```aura def placeholder(): pass ``` It must appear on its own logical line. It is used for intentionally empty function, method, class, trait, implementation, or control-flow suites. An enum body still requires at least one variant and does not use `pass` as a variant. ## Module Constants, Imports, And Execution Imports are module elements rather than executable statements. Aura accepts: ```aura import util.math from util.math import double, triple import agents.telemetry as telemetry from agents.telemetry import record as record_event, Event ``` Import paths are dot-separated identifiers. A module import may bind the complete module under a local alias. Each name in a from-import may also have its own local name, and renamed and direct imports may appear together. A renamed import introduces only its local name into the importing module. Aliases are static local names for resolved modules and declarations. They do not change visibility, nominal identity, trait implementations, initialization storage, or the package path used for resolution. Wildcard imports, relative-dot imports, parenthesized import lists, and trailing import commas are not accepted. Import resolution and visibility are defined in [Packages](/manual/packages#imports). An immutable binding at module level is a module constant: ```aura message = "hello" public retry_limit: int64 = 3 def main(): print(message) ``` The constant initializer is required. `mut` module storage and later assignment are rejected. Constants may coexist with a local `main`, and reachable dependency constants initialize before entry execution. The full scope, order, visibility, and ownership rules are defined in [Names And Scopes](/manual/names-and-scopes#module-constants). An entry module may also contain executable top-level statements: print(message) Those statements execute in their stored source order after reachable module constants are ready. An entry module with executable top-level statements cannot define a local `main`. Imported module top-level statements do not execute as import side effects. A top-level `mut name = value` statement declares `name` in the entry script's local environment. Later `name = value` and compound assignments such as `name += value` reassign that same local: ```aura mut count = 0 count = count + 1 count += 1 print(count) ``` A bare top-level binding with a new name remains a module constant, regardless of its textual position among entry statements. It cannot read a top-level script local because constants initialize before entry execution. Declare the new binding with `mut` to keep the computation in the entry script, or move the work into `main`. The accepted `main` signatures and process exit behavior are defined in [Functions](/manual/functions#main) and [Execution Model](/manual/execution-model#entry-module-execution). ## Contextual Legality Summary Parsing a statement shape does not make it legal in every context: - `return` requires a function or method. - `break` and `continue` require an enclosing loop. - reassignment and compound assignment require a mutable existing place. - member and index assignment require a mutable base and cannot declare a type or use `mut`. - conditions require `bool` rather than truthiness. - assertion conditions require `bool`, and assertion messages require `str`. - match arms must satisfy compatibility, reachability, and exhaustiveness rules. - `with` requires a supported resource and preserves its cleanup capability. - items cannot appear inside suites. - module constants are immutable and cannot use `mut` or reassignment. - module constants cannot read top-level script locals, which initialize later. - an entry module cannot mix executable top-level statements with local `main`. The complete checker rules are normative in [Static Semantics](/manual/static-semantics), and ownership effects are normative in [Ownership And Borrowing](/manual/ownership-and-borrowing). ## Grammar The simple and compound statement productions, suite indentation, binding and assignment targets, loop modifiers, match arms, and `with` forms are normative in [Grammar](/manual/grammar). Statements end at a physical `NEWLINE`; Aura has no semicolon-separated or inline compound statements. ## Typing Rules Bindings infer or check one type, and reassignment preserves it. Conditions are exactly `bool`; return values match the enclosing signature; iterables determine their loop binding contract; match patterns are compatible, reachable, and exhaustive where required; and `with` accepts only the maintained cleanup contract. Assertion conditions are exactly `bool` and messages are exactly `str`; an assertion does not refine later control flow. Contextual legality is checked after parsing. ## Runtime Semantics Statements execute in source order within the selected suite. Simple-name and field assignment evaluate the right side before writing the target; indexed assignment evaluates its collection and index/key before the right side, with a simple dict assignment capturing its owned key before any value-side effects; compound assignment uses the corresponding binary dispatch and stores into its once-selected target; a copy target is captured before the right side, while a non-copy root or projected target remains borrowed across it; direct indexed compound assignment reads only a copy element and traps with `AU4003` when a Dictionary key is absent; conditionals select at most one branch; loops test or receive before each body; a match evaluates its scrutinee once; and `with` registers cleanup only after resource construction succeeds. An assertion evaluates its condition once, skips its message on success, and evaluates that message once before failing. Control transfer runs every exited cleanup in reverse registration order. ## Ownership And Evaluation Order Bindings own, copy, or borrow their initializer according to type and context. `own` list/set iteration consumes once into a loop-private source, bare collection iteration retains and freezes its selected place, and Queue iteration captures a copy handle once while receiving already-owned items. The one-time iterable selection is the accepted ADR-0017 rule; the ownership modes themselves remain those accepted in ADR-0006. Simple dict indexed assignment consumes non-copy keys and values into owned storage; direct list/dict indexed compound assignment is restricted to copy elements. Assignment to a place invalidates conflicting borrows and reinitializes the written place. Branch and loop analysis conservatively preserves any move that may reach a continuing path; no control-flow join restores ownership implicitly. ## Diagnostics `AU1101` means malformed statement or suite syntax. `AU2001` means an unresolved name or target. `AU2002` means an expected-type, condition, iteration, match, return, or assignment mismatch. `AU2003` means an unsupported compound-assignment operator. `AU2004` means call or target argument binding failed. `AU2005` means unsupported syntax or feature for a Python-shaped statement. `AU2999` means an exhaustiveness, contextual-legality, unsupported statement rejection without a narrower code. `AU3001` means use of a moved place; `AU3002` means a borrow conflict, including later access that mutably borrows or consumes an overlapping retained non-copy compound or indexed- assignment target; `AU3003` means an immutable target was used mutably; and `AU3004` means an invalid loop, parameter, or ownership mode. `AU3005` identifies a non-copy direct indexed read, and `AU3006` identifies a non-copy indexed compound assignment. During execution, `AU4001` means a general statement trap, `AU4002` means numeric range, overflow, or underflow failure, `AU4003` means a bounds or lookup violation, `AU4004` means a zero divisor, and `AU4005` means a trapping resource or I/O failure, including cleanup failure when no earlier body failure remains primary. A failed assertion is `AU4001`, uses `assertion failed` or the exact custom message, and points to its keyword. ## Backend Support Every implemented statement form shares the checker and MIR lowering used by MIR execution and direct native generation. Cleanup, loop, match, task, and runtime-trap behavior is forced through the backend-parity suite; unsupported direct lowering is contained rather than silently given different semantics. ## Limits And Implementation-Defined Behavior Suites require a real statement, loop `else` is unavailable, statement match arms cannot be inline, a statement may span physical lines only through an open `(`, `[`, or `{`, backslash continuation is unavailable, and items cannot nest in suites. Range iteration yields copy `int32` values and accepts only the bare form as recorded above. No statement evaluation order is implementation-defined. ## Status Bindings, assignments, expression, return, and assertion statements, conditionals, loops, match, scoped cleanup, `pass`, imports, and entry-module top-level execution are implemented as described. Tuple assignment/loop targets are implemented under Accepted ADR-0026. Class/collection destructuring, loop `else`, exception statements, `yield`, `raise`, `async`, and nested declarations are unavailable; `try` remains an expression over `Result`. ## Source: docs/manual/static-semantics.md # Static Semantics Static semantics are the rules applied after parsing and module loading and before MIR lowering or native code generation. A module is well typed only if every declaration, statement, expression, pattern, call, move, and borrow satisfies these rules. This chapter states the cross-cutting rules. The declaration-specific chapters provide additional contracts, and [Ownership And Borrowing](/manual/ownership-and-borrowing) defines place and lifetime restrictions. ## Types And Type Equality Aura 0.3 primarily uses nominal types with invariant generic arguments. Two nominal types match when their canonical names and recursively all type arguments are equal. Tuple types are structural: two tuple types match exactly when their arity and every corresponding element type match recursively. There is no general subtype relation and no implicit numeric widening. Examples: - `int` and `int64` are the same canonical type. - `int32` and `int64` are different types. - `list[int32]` and `list[int64]` are different types. - two user classes with identical fields are still different types. - an imported type retains its defining module identity even when imported under an unqualified binding. `T?` is syntactic sugar for `Option[T]`. `int` canonicalizes to `int64`, and `str` currently canonicalizes to `str`; neither alias introduces a distinct runtime type. Every generic type use must supply its declared number of type arguments. Non-generic types reject type arguments. `Self` is available only in supported trait and implementation type positions. ## Contextual Inference Aura uses local, contextual inference rather than global inference. Public function parameters, fields, method signatures, and explicit return values remain typed in source. The checker uses an expected type from an annotation, parameter, return position, collection, constructor field, or surrounding expression where the rule is unambiguous. ### Literals - An integer literal adopts an expected integer type and must fit it. In an expected `float32` or `float64` context it adopts that floating type only when its mathematical integer value is exactly representable there; an inexact case is a static error that directs the author to an explicit floating spelling or `.to_float()`; otherwise it defaults to `int64`. - A negative integer literal is parsed as unary `-` applied to a non-negative literal. It follows the same exact float-context rule, or must fit the selected signed integer type. - A floating literal adopts an expected `float32` or `float64`; otherwise it defaults to `float64`. - `true` and `false` have type `bool`. - A single-quoted, double-quoted, triple-quoted, raw, or formatted string has type `str`; delimiter and literal form do not create distinct types. Each f-string format specification is checked against the interpolation's static type. String-only, integer-only, numeric-only, sign, precision, and grouping restrictions are compile-time errors under `AU2002`. - A duration literal has type `Duration`. - Bare `None` has type `None`, except in an expected `Option[T]` position where it denotes `Option.None` of that type. Expected-option context flows through grouping, annotated bindings, return positions, and argument positions. For `==` and `!=`, when either operand has static type `Option[T]`, a bare `None` on the other side is contextually typed as that same option specialization; this rule is symmetric. Unit `None == None` is `true` and unit `None != None` is `false`. A qualified `Option.None` with no expected specialization remains an inference error. ### Collections A non-empty list, set, or dictionary infers its element/key/value type from the first value unless an expected collection type is available. All remaining values must have the same inferred type. Equal keys in one dictionary literal are permitted; the later value replaces the earlier value at runtime without changing the key's first insertion position. An empty list or dictionary literal requires an expected `list[T]` or `dict[K, V]` type. `{}` is a dictionary literal. An empty set uses `set[T]()`. ### Comprehensions A list comprehension has type `list[T]`, a set comprehension has type `set[T]`, and a dictionary comprehension has type `dict[K, V]`. An expected collection specialization flows into the element, key, and value expressions before inference. Otherwise those output expressions determine `T`, `K`, and `V` under the ordinary exact-type and contextual-literal rules. A filter must have exactly type `bool`. Clauses are checked in runtime order. A clause iterable is checked before its target enters scope. The target receives the same type and ownership provenance as an ordinary bare `for` target, then becomes visible to that clause's filters, later clauses, and the output. Targets cannot shadow visible names or earlier targets and do not escape the expression. Every clause reuses the statement bare-loop iterable classification. Lists and sets provide shared elements, Range provides copy `int64`, the compiler-known `enumerate` and `zip` forms retain their contracts, and Queue receives owned items through its existing carve-out. `mut` and `own` clause modifiers are not part of the syntax. Output storage owns its inserted value. A copy value is copied and an owned non-Copy value moves. A shared non-Copy target cannot be inserted without an explicit clone-safe `.clone()` route. Queue targets already own their received items. Move checking treats each clause as potentially repeated, retains every active source borrow through downstream clauses and output evaluation, and rejects loop-carried full or partial moves from outer places. ### Lambdas A lambda with parameters requires an expected structural function type. That type fixes its parameter count and each bare/`mut`/`own` capability and parameter type; an expected result constrains the body. The body is checked once under those parameter bindings. Aura does not infer parameter types from body operations. A zero-parameter lambda may infer `def() -> R` from its body when no expected callable type is present. Outer owned locals and `own` parameters referenced by the body are captured by value. Copy values are snapshotted and non-Copy values move when the lambda expression is evaluated. Bare and `mut` enclosing parameters are capabilities and cannot be captured. Captured values permit shared reads or consumption, but not mutation in Phase 6.3. See [Closures](/manual/closures). Capture-free lambdas may cross every ordinary structural function-value boundary. Capturing closures retain environment and call-kind metadata, so they cannot coerce through arbitrary written-`def` parameters or stored fields, collections, and annotated returns. Immutable local bindings, compiler-known repeatable callbacks, direct calls, and qualifying task starts preserve the metadata. ### Generic Calls Generic type parameters are inferred by unifying argument types with parameter type patterns and, where available, the expected result type. Explicit specialization such as `identity[int64](value)` seeds or fixes the substitutions. Every declared type parameter must resolve. The substituted type must satisfy all declared trait bounds. Inference does not guess from unrelated declarations or from runtime values. ## Declarations A declaration is valid only when: - its item name does not collide with another local/imported item or a reserved builtin - type parameter names are unique and their bounds name known traits with correct arity - field, variant, and method names are unique within the relevant declaration - all referenced types exist with the correct arity - default expressions have exactly the declared parameter or field type - a non-`None` function or method returns on every statically reachable fallthrough path - copy classes contain only copy-compatible fields - trait implementations satisfy the trait's type arguments, supertraits, method set, and method signatures Class, enum, function, and trait declarations may be `public` at module scope. `impl` cannot be public because it introduces no independently imported item. An extern declaration participates in the module namespace but has no Aura body. The ABI must be `"C"`, its package must be authorized, and its complete signature must belong to the fixed FFI v0 scalar/view/opaque-handle table. Extern functions are direct-call-only: referencing one without immediately calling it is rejected rather than producing a function value. An opaque declaration contributes a nominal type but no constructor, fields, methods, or Aura-visible layout. See [FFI v0](/manual/ffi). ## Bindings And Assignment The first simple-name assignment introduces a binding. Its type is the annotation when present, otherwise the initializer type. The initializer must match exactly. `mut` makes the new binding assignable and a mutable place. Reassignment requires an existing mutable place and preserves the original type. Reassignment reinitializes a fully moved binding or field when the assigned value has the correct type. Compound assignments `+=`, `-=`, `*=`, `**=`, `/=`, `%=`, `//=`, `&=`, `|=`, `^=`, `<<=`, and `>>=` read the current target, apply the corresponding binary operation, and write the result only after success. The target must already exist, be mutable, not be moved, and have the operation's result type. Integer `/=` is rejected by the same rule and teaching diagnostic as integer `/`; floating `/=` remains valid. Field assignment requires a mutable base place and a declared field. Index assignment supports `list[T]` with the `int64` index domain and `dict[K, V]` with a key of exactly `K`. Simple dict index assignment accepts any `V` and replaces an equal existing key or inserts a new entry. Its key and value are owned storage positions: each is consumed when non-copy. The key is fully evaluated and captured before the assigned value is evaluated, so value-side effects do not retarget the write. Compound dict indexed assignment is permitted only for copy `V`; non-copy `V` is rejected rather than implicitly cloned or destructively removed before the operator completes. An annotation and `mut` are not permitted on member or index assignment. ## Expression Typing ### Unary Operators - `not value` accepts `bool` and returns `bool`, or resolves a matching `Not.not` trait operation. - `-value` accepts an integer or float and returns the same type, or resolves a matching `Neg.neg` operation. - `~value` accepts an integer and returns the same exact integer type. - `try value` requires `value: Result[T, E1]` and an enclosing return type `Result[U, E2]`; it has type `T` when `E1 == E2` or an applicable `impl From[E1] for E2` exists. ### Binary Operators Built-in operator typing is: | Operators | Operand rule | Result | | --- | --- | --- | | `and`, `or` | both `bool` | `bool` | | `+` | equal integer types, equal float types, two `str` values, or two Duration values | operand type | | `-` | equal integer types, equal float types, or two Duration values | operand type | | `*` | equal integer types, equal float types, `Duration` and `int64` in either order | numeric operand type, or `Duration` | | `**` | equal integer types or equal float types | operand type | | `//` | equal integer types, equal float types, or `Duration // int64` | numeric operand type, or `Duration` | | `%` | equal integer or equal float types | operand type | | `/` | equal float types | operand type | | `&`, `|`, `^` | equal concrete integer types | operand type | | `<<`, `>>` | equal concrete integer types | left operand type | | `==`, `!=` | equal operand types | `bool` | | `<`, `<=`, `>`, `>=` | equal integer types, equal float types, or two Duration values | `bool` | When both operands have the same integer type, `/` is rejected with this exact maintained diagnostic: ```text integer `/` is not supported; use `//` for floor division, or call `.to_float()` on both operands for true division ``` Arithmetic and ordering operators may otherwise resolve through the corresponding `Add`, `Sub`, `Mul`, `Div`, `FloorDiv`, `Mod`, or `Ord` trait method. Builtin numeric and Duration rules take precedence over operator-trait dispatch. Builtin equality does not dispatch through an operator trait in Aura 0.3. Tuple `==` and `!=` require operands with the same static tuple type. They apply builtin equality recursively to corresponding element types and produce `bool`; nested tuple elements apply the same rule. Both operands are read, not consumed. Runtime metadata attached to a tuple value is not a further type-compatibility or equality input. Tuple `<`, `<=`, `>`, and `>=` are rejected: structural tuple types have no lexicographic ordering and cannot acquire one through `Ord`. When one equality operand is a tuple literal and the other has a known tuple type, the known type contextually types the literal recursively. The rule is symmetric. Each equality link in a comparison chain applies the same contextual typing before enforcing exact operand-type equality. Operator operands are not implicitly widened. An integer literal may be contextually typed to match an integer operand, or a `float32`/`float64` operand when the literal is exactly representable in that floating type. A floating literal may adopt the other operand's floating type. Non-literal values require an explicit numeric cast or integer `.to_float()` conversion. Integer power requires a non-negative exponent. A negative exponent visible in source is `AU2003`; a negative value discovered only during execution is a runtime failure. Bitwise operations, shifts, and power are builtin numeric operations and do not dispatch through operator traits. ### Conditions `if` and `while` conditions, including the condition in `value if condition else alternative`, must have exactly type `bool`. `and`, `or`, and `not` also require boolean results under the rules above. Aura does not apply general truthiness conversion to strings, collections, resources, or user types. ### Assertions An `assert` condition must have exactly type `bool`. Its optional message must have exactly type `str`. Both mismatches use `AU2002` at the retained `assert` keyword span. The checker evaluates the condition's ownership effects first. It checks an optional message from the resulting state, but because that expression is runtime-lazy, message-only moves and mutations are not applied to the fallthrough state. The statement itself has ordinary fallthrough and performs no lasting type or value refinement. ### Indexing, Slicing, And Members Direct indexing supports `list[T]` with the `int64` index domain, `dict[K, V]` with exactly `K`, and `Array[T]` with one `int64` coordinate per runtime axis. For a list, a negative index `i` is normalized once as `len + i` before the existing bounds check; this applies equally to direct reads and writes and to `get`, `set`, `pop`, and both `swap` indexes. Fixed-width `int8`, `int16`, `int32`, `uint8`, `uint16`, and `uint32` values widen losslessly only at these positions. Direct access, `set`, `pop`, and `swap` fail at runtime when the normalized position is invalid, while `get` returns `None`. `insert` clamps its position to `0..=len`. A direct read produces `T` or `V` only when that element/value type is copyable. For a non-copy list element, use `get(index)` for an explicit cloned optional read only when the element type is clone-safe. For a non-copy dictionary value, use `get(key)` only when the value type is clone-safe, or `remove(key)` to transfer ownership. These non-copy direct-read rejections use `AU3005`; a non-copy indexed compound assignment uses `AU3006` because its initial read has the same ownership problem. A missing dictionary key in a direct read is runtime diagnostic `AU4003`. Integer indexing is not defined for `str`. A slice suffix is defined on `list[T]`, `str`, and `Array[T]`. It returns a fresh owned value of the same source type. Each written endpoint uses the `int64` position domain, including lossless widening from the supported narrow fixed-width types. An omitted endpoint contributes no expression. After one `len + i` normalization for each negative written endpoint, both effective endpoints must lie in `0..=len` and start must not exceed end. Invalid or reversed bounds are runtime `AU4003`. List slicing establishes a clone-producing obligation for `T`: Copy elements are copied and non-Copy elements must be clone-safe. A concrete or transitive `random.Rng` element is rejected with `AU3007`; a non-repeatable Task observation right is rejected with `AU3009`; unresolved generic `T` carries the inferred obligation to specialization. String slicing counts Unicode scalar values and returns `str`. A slice is not a place and cannot be the target of assignment or mutable access. Step syntax and slice assignment are reported as unsupported forms with `AU2005`. `Array[T]` is specialized only by `int32`, `int64`, `float32`, or `float64`. Its constructors require exact `list[int64]` shape metadata. Array slicing uses the same one-colon grammar and endpoint rules but copies only a first-axis range, retaining the remaining runtime dimensions. Array/Array arithmetic requires identical `T`; runtime shape equality is checked with `AU4007`. Scalar arithmetic requires exactly `T`, with no mixed promotion or broadcasting. Integer Array `/` is `AU2003`. Array `map[U]` requires exact repeatable `def(T) -> U` and restricts `U` to the four Array dtypes. `sum`, `min`, and `max` return `T`; `mean` returns `float64` for all dtypes. Mutable `set`, `fill`, and indexed assignment require a mutable Array place. Array `get` converts an invalid coordinate or runtime-rank mismatch to `None`; `set` traps for either failure and returns the replaced scalar in `Some` only after a valid update. Member access must resolve to a visible field, method, enum variant, module item, or maintained builtin member. Calling a receiver method also validates whether the receiver is consumed, shared-borrowed, or mutable-borrowed. A non-copy place selected as a binary left operand, index base, method receiver, or indexed-assignment target remains borrowed through the operation's later inputs. Another shared borrow is valid, but an overlapping mutable borrow or consumption is rejected with `AU3002`, with the retained selection reported as the borrow origin. The same rule applies to name roots and projected member places. The checker never legalizes the operation by assuming a deep clone. Equality and inequality retain this borrow through the right operand and consume neither operand; tuple equality does not introduce a recursive move. ## Call Binding Arguments are written as positional arguments followed by named arguments. Binding proceeds against the declaration or builtin metadata: 1. positional arguments fill parameters in declaration order 2. named arguments fill the parameter with the same name 3. a parameter cannot be filled twice 4. unknown names and extra arguments are rejected 5. omitted parameters require defaults 6. each argument type must equal the substituted parameter type Default expressions are evaluated for each call where the parameter is omitted. Every supplied argument expression is evaluated first in call-site source order before the next supplied expression begins. A copy or move result is captured in its argument slot; a borrow-mode selection is established without cloning and is checked under the retained-borrow rule above. Later side effects therefore cannot re-read or change an earlier captured argument. Defaults for omitted parameters are then evaluated in declaration order. Binding a named argument to its declaration slot does not reorder evaluation, and no default is evaluated for a supplied parameter. Defaults may refer only to names valid under the declaration's default-expression rules; they do not capture a caller's locals. A bare shared default's temporary lives through the call. An `own` default is consumed. A `mut` default is rejected because mutations to its caller-invisible temporary would be silently lost. A bare parameter grants logical shared access for every type. The ABI may pass copy bits directly, but specialization never changes the declared capability. An `own` parameter consumes a non-copy argument, and a `mut` parameter requires a mutable place. All arguments at one call boundary are checked together for overlapping move/shared/mutable access. An indirect call through a function value follows the same rules. When the value has one statically known declaration, binding uses that declaration's parameter names and defaults. A control-flow join retains those extras only when all candidate contracts agree on names and default availability; an omitted argument evaluates the runtime-selected target's own default expression. Conflicting reassignment, return through a structural function annotation, class-field storage, and mutable-collection storage erase the extras, so the call supplies every parameter positionally. The structural type does retain each parameter's capability: bare is shared, `mut` requires a mutable place and caller-visible writeback, and `own` transfers a non-copy argument. Function-type assignment and substitution require those modes, parameter types, and the return type to match. Extern calls use ordinary positional/named argument binding and left-to-right evaluation, then apply the declared FFI capabilities. Scalars require bare parameters; `str` is a bare const UTF-8 view; `list[uint8]` is a bare const byte view or `mut` fixed-length byte view; opaque handles permit bare sharing or `own` consumption. A `mut` view requires a mutable place. Extern defaults, generics, callbacks, variadics, returned views, and raw pointers are rejected. Callable-powered list methods use exact structural callback types. `map` requires `f: def(T) -> U`; `filter` requires `f: def(T) -> bool`; and keyed `sort` requires `key: def(T) -> K`. Bare callback parameters are shared capabilities. A callback with `mut T` or `own T` is not substitutable. `filter` is clone-producing and adds the ordinary clone-safety obligation for `T`. `sort` requires `T` to support the existing natural `<` ordering, while keyed `sort` requires that ordering for `K`. Both require a mutable list place. `map` and `filter` retain a shared receiver. `control.retry` requires `worker: def() -> Result[T, E]`, `max_attempts: int32`, and `initial_backoff: Duration`. The worker function type, including its empty parameter list and `Result` return identity, must match exactly. `T` and `E` are inferred from that return specialization or may be supplied through ordinary explicit generic specialization. The callback is not widened from a function with parameters or a different return type. ## Class Construction Calling a class name constructs a value. Constructor fields may be supplied positionally in declaration order, then by name. Positional arguments cannot follow a named argument. Every field without a declaration default must be supplied exactly once; provided and default values must match the substituted field types. Supplied field expressions follow the same source-order capture rule as call arguments, so a later field expression cannot change an earlier captured field value. A class receiver is declared as shared `self` (or its explicit synonym `self`), consuming `own self`, or mutable `mut self`. A first method parameter written `self: Type` is rejected with guidance naming those forms. A method without a receiver is associated and is called through the class/type rather than an instance. ## Enum Construction And Matching An enum variant constructor must name an existing variant and provide exactly its payload shape. A variant declares either all positional payloads or all named payloads; constructors bind accordingly. Supplied named payload expressions evaluate in their written source order, each result is captured, and those results then bind by payload name to the variant's declaration-order slots. Declaration-slot binding does not reorder evaluation. Generic enum constructors require sufficient context to determine all type arguments. This may come from explicit specialization, an expected annotation/parameter/return type, or payload inference. Bare builtin variants such as `Some`, `Ok`, `Err`, or `None` are accepted only where the expected enum identity is unambiguous. A match pattern must be compatible with the scrutinee type. Variant payload subpatterns must have exactly the variant's arity. Literal patterns must match the scrutinee's supported scalar type. Duplicate or unreachable arms are rejected where the checker can establish overlap. Matches over enums and booleans must be exhaustive unless `_` covers the remainder. Literal matches over open numeric/string domains require `_`. Every arm of a match expression must produce the same result type, using the surrounding expected type where available. A conditional expression checks both value arms against one result type. Surrounding expected context applies to both arms. Without such context, a context-dependent literal may adopt the type established by the other arm; no rule widens or converts an already-bound value. The condition is checked first, then each arm starts from the resulting ownership state. ## Generics, Traits, And Implementations Traits are nominal interfaces. A bound `T: A + B` requires an applicable implementation of each trait after substitution. Supertraits are inherited requirements. An `impl` identifies one trait specialization and one target type pattern. Its methods must correspond to trait methods; missing required methods are rejected unless the trait provides a default body. Extra methods are not part of that trait implementation. For a concrete receiver, the checker chooses the unique applicable implementation with greatest specificity. If multiple equally specific implementations apply, the call or operator is ambiguous and rejected. Source order is not a tie breaker. For a type parameter, available methods and operators come from its declared bounds. If multiple bounds expose an indistinguishable method, the access is ambiguous unless the language can resolve one unique contract. Trait and implementation methods cannot declare default ordinary parameters in Aura 0.3. Trait default method bodies are permitted; a signature-only trait method has no body after its terminating newline. A clone-producing operation over unresolved generic types infers clone-safety obligations on the contributing declared parameters. Calls propagate those obligations to a fixed point and discharge them after substitution. The contract applies equally to ordinary, imported, inherent, associated, bounded trait, operator, task-target, and `From` calls. A concrete type that contains non-cloneable `random.Rng` state, or whose safety cannot be proved, is rejected with `AU3007`. An obligation inferred from a trait default method is part of that method's contract and is structurally substituted through `Self`, trait arguments, and method arguments. An explicit implementation may satisfy that contract but MUST NOT add a clone-safety requirement absent from it. Recursive nominal type inspection terminates conservatively rather than assuming an expanding cycle is safe. ## Control Flow `return` is valid only in a function or method. Its value must equal the declared return type; an omitted value has type `None`. `break` and `continue` are valid only inside `for` or `while`. A loop-local binding does not escape. Moving a non-copy outer value for the first time inside a repeatable loop is rejected unless the checker can prove the path does not create an invalid next iteration. A comprehension is expression control flow, not a statement loop: `break`, `continue`, and `return` cannot appear in its clauses or output. Each filter checks one conditional path. Later clauses and output effects apply only on the path where every preceding filter is true, and resulting ownership state is merged conservatively. `try` remains an expression and may propagate from a reached source, filter, key, value, or element after cleaning up the partial result. An `if`, statement match, match expression, or conditional expression checks branches independently and merges move/partial-move state conservatively across reachable paths. A non-`None` function is rejected when any reachable path can fall through without returning. ## `with` Resources `with` consumes its resource expression and creates a mutable managed binding for the body. Supported builtin resources have runtime-defined cleanup. A non-generic user class may be used only when it declares exactly a `close(mut self) -> None` instance method. The managed binding cannot be moved out in a way that would prevent required cleanup. Leaving the scope normally, by return, by loop control, or through a runtime failure runs the cleanup behavior described in [Execution Model](/manual/execution-model). ## Tasks And Static Safety `TaskGroup.start`, `start_soon`, `start_with_stack`, and `start_soon_with_stack` accept capture-free function values and closure values as well as the existing direct named-function and associated-method-without-`self` targets. The explicit-stack methods first require an exact `int64` capacity. Target arguments are copied or moved into task-owned capture storage independently of the target ABI. Bare shared target parameters borrow that storage for the child call; `own` parameters consume it. Generic targets also enforce their inferred clone-safety obligations after specialization. `mut` target parameters are rejected. Under the Accepted ADR-0033 Phase 5.6 contract, each value captured by these four start methods and the specialized target return type must be `Transfer`. This is a compiler-derived structural obligation, not a builtin user trait. A same-named ordinary user trait cannot confer the property. It follows collection, tuple, class, and enum storage to the first non-transferable leaf. All copy types and `str` qualify; aggregates qualify when every stored component does; `Queue[T]` and `Task[T]` handles qualify without traversing `T`. Capability views, `random.Rng`, `TaskGroup`, and live host resources do not qualify unless a later compiler-owned whitelist names a specific type. A task-start expression that reads a Copy value through shared or mutable access captures an owned Copy snapshot, not the access capability, and is allowed when that value type is Transfer. Non-copy access cannot be captured by value without ownership, and the capability itself never crosses. Queue construction, `put`, and `try_put` require the payload `T` to be `Transfer`; handle-only receive/fallback/close operations do not recheck it. A fully concrete generic specialization is checked structurally, but an unresolved type parameter at a task or Queue boundary is rejected conservatively; Phase 5.6 does not infer a deferred Transfer contract. A rejection uses `AU3008`, identifies the task or Queue boundary, and gives the nested component path that caused it, such as a field that contains `fs.File`; it does not suggest implementing a `Transfer` trait. A closure target is Transfer exactly when every stored capture is Transfer. The complete closure value is moved or copied into task-owned storage before the child calls it. Capture-free lambdas are Copy and Transfer. By-value closure capture cannot launder shared or mutable capabilities because those captures are rejected when the closure is created. Task-target resolution accepts a concrete function value. Explicit `function[Types]` specialization may produce such a value before the call; the direct associated-method `Type.associated_method[Types]` spelling remains limited to the callable-target slot. A bare target whose declared/default context already resolves its complete types is also concrete. ADR-0008 also distinguishes repeatable and single-consumer task results. `Task[T]` is copyable only when `T` is copyable, `T` is `Queue[...]`, or `T` is a recursively repeatable `Task[...]`. For any other transferable `T`, `result`, `result_or_none`, and `result_or` consume the unique observation right on every outcome. `wait_any` and `wait_all` consume the complete task list; `wait_any` abandons the unchosen rights. `select(...)` consumes every non-repeatable Task source at call entry and abandons each losing right. This prevents handle aliases, including nested `Task[Task[str]]`, from producing a second value. Attempts to clone, read through a clone-producing collection method, or copy an aggregate containing such a right use `AU3009`. Reusing the task binding after a consuming observation uses ordinary moved-value `AU3001`; consuming through shared access is `AU3002`. Task, queue, and cancellation runtime semantics are defined by [Concurrency](/manual/concurrency). ## Entrypoint Rules The selected entry module may use one of two shapes: - executable top-level statements and no local `main` - a local `main` and no executable top-level statements The local `main` takes no parameters and returns `None` or `int32`. Imported functions named `main` are ordinary imported functions and do not become the entrypoint. ## Source: docs/manual/status-and-compatibility.md # Status And Compatibility Aura 0.3 is an advanced technical preview. It is suitable for compiler and runtime evaluation, examples, and controlled experiments. It is not a production systems-language release or a security boundary for untrusted programs. ## Canonical Contract The maintained language contract consists of: 1. the normative Language Specification and Manual 2. compiler fixtures and CLI/LSP regression tests as executable conformance evidence 3. the compiler, runtime, CLI, and language server as conforming implementations 4. categorized examples and Learn chapters as teaching material The Manual and executable suite are expected to agree. A divergence is a project defect, not an alternate language rule. Aura provides the ordinary builtin call `select(source, ...)` under Accepted ADR-0034 and no statement form. The builtin function name `select` and builtin enum name `SelectOutcome` are reserved. User declarations with either name are rejected. `len` and `str` are also reserved builtin function names, and redefining either is rejected the same way as redefining `print` or `abs`. ADR-0030 is Accepted with the B3.0-d length-unification amendment: `str.len()`, `str.byte_len()`, `list.len()`, `dict.len()`, and `set.len()` now return `int64`. For `str`, `list`, `dict`, and `set`, `len(value)` and `value.len()` have the same static type and value; `str.byte_len()` is the separate UTF-8 byte count. Range bounds and yields, list indexes, slice endpoints, enumeration positions, and Array coordinates use the same `int64` position domain. Conditional expressions, membership operators, comparison chains, and the `enumerate`/`zip` loop forms are accepted language surface under ADR-0027, ADR-0028, and ADR-0029. A later `for` loop may reuse the same target names at different element types; both maintained backends must preserve each loop's distinct typed binding identities. Tuples are accepted language surface under ADR-0026. Tuple `==` and `!=` compare same-typed values structurally and recursively. Reading an existing non-copy tuple for comparison does not consume it; tuple ordering remains rejected. See [Tuples](/manual/tuples) and [Statements](/manual/statements#for-iteration) for the loop-form contract. Phase 6.1 capture-free function values make named functions Copy and Transfer values with structural `def(...) -> ...` types. Phase 6.2 uses that surface for the maintained eager natural/keyed `list.sort`, `map`, and `filter` algorithms and for `control.retry`. These are current technical-preview APIs. `control` resolves as a builtin module namespace, and the four List member names are part of the builtin no-shadowing surface. Callback capabilities are exact: code must pass bare/shared element callbacks rather than relying on adaptation from `mut` or `own`. Contextually typed `lambda parameters: expression` closures follow Accepted ADR-0037. Captures are by value: Copy values are copied, owned non-Copy values move at creation, read-only capture use is repeatable, and consuming capture use makes the closure single-use. A closure is Transfer only when every capture is Transfer. Shared or mutable capability capture and mutable captured state are unavailable. Zero-parameter lambdas may infer their result without a contextual callable type. Capturing closures retain compiler metadata and therefore do not cross arbitrary written-`def` parameter, field, collection, or annotated return boundaries. Phase 6.4 adds explicitly authorized FFI v0 packages. Bodyless `extern "C"` functions call process-global symbols synchronously through fixed-width scalars, pointer-length str/byte views, or non-null opaque handles. FFI-enabled dependencies must be visible in the root manifest's exact `[ffi] dependencies` report. Externs are direct-call-only; callbacks, raw pointers, variadics, returned views, nullable handles, and explicit library loading remain unavailable. This is an unsafe native boundary, not a memory safety promise for a false declaration or misbehaving C implementation. Phase 7.1 adds eager owned list, set, and dictionary comprehensions under Accepted ADR-0039. Clauses inherit statement bare-loop iteration, including shared List/set traversal, Range copy values, compiler-known `enumerate`/`zip`, and Queue's receive-owned item carve-out. Nested clauses are outer-major, filters run left to right, dictionary keys run before values, and target names never leak. Result insertion follows ordinary Copy, move, explicit-clone, and ADR-0037 capture rules. Generator expressions are unavailable; use an eager comprehension or explicit loop. Phase 7.2 adds owned list and str slicing under Accepted ADR-0040. The four one-colon forms accept omitted endpoints and select a half-open range. Written endpoints use the `int64` position domain; negatives normalize once, and an invalid or reversed range traps with `AU4003`; endpoints are not clamped. String positions count Unicode scalar values and require an O(n) scan. Every result owns independent storage: list elements copy or clone under clone-safety and task-repeatability rules, while str produces a fresh valid UTF-8 value. String integer indexing, steps, slice assignment, and views remain unavailable; this feature does not implement ADR-0038. Phase 7.3 adds global contiguous `Array[T]` values under Accepted ADR-0041. The four dtypes are `int32`, `int64`, `float32`, and `float64`; every value owns a rank-at-least-one row-major CPU buffer. The accepted surface includes three constructors, multidimensional scalar indexing, first-axis owned slices, mutation, mapping, reductions, exact-shape/scalar kernels, and explicit wrapping/saturating integer arithmetic. It adds no array-shape broadcasting, mixed promotion, views, shape transformations, equality, autograd, or accelerator placement. `mean()` returns `float64` for every dtype; integer Array `/` remains rejected under ADR-0002. Batch S1 adds the Accepted ADR-0047 and ADR-0048 numeric surface. Integer literals support decimal separators and hexadecimal, binary, and octal bases. Every integer width supports fixed-width bitwise operators, checked shifts, and explicit wrapping and saturating shift modes. `**` provides checked same-type integer power and same-type floating power. `round` implements ties-to-even floating conversion to `int64` and exact integer identity; `divmod` returns the paired floor quotient and divisor-signed remainder. See [Language Specification](/manual/language-specification) and [Conformance](/manual/conformance). ## Stability Policy Accepted ADRs define the current reference baseline. Outside explicitly recorded decisions, syntax expansion is frozen for each technical-preview checkpoint. Work prioritizes correctness, native-runtime safety, editor responsiveness, and a coherent control-plane surface. APIs may change while Aura remains a technical preview. The post-Phase-1.5 Manual is reference-frozen. Every later semantic change, including any extension, requires an ADR and must update the normative reference, compiler fixtures, maintained examples, and tutorials in the same commit. A change that cannot keep those surfaces synchronized does not enter the maintained language. Compiler coverage is held at the current non-regression floor rather than being pushed to 100%. New behavior still requires focused tests; the freeze only ends marginal coverage work that does not reduce product risk. Seeded randomness has an additional observable-data promise: the algorithm, seed mapping, integer and floating mappings, and shuffle order documented in [Randomness Module](/manual/randomness) remain stable throughout Aura 0.3.x. A later release may change them only with an explicit decision and new conformance vectors. OS-secure outputs are intentionally not stable. ## Maintained Concurrency Surface Aura 0.3 uses structured concurrency: - `TaskGroup()` owns child tasks inside `with` - `TaskGroup.start(...)` returns a `Task[T]` - `TaskGroup.start_soon(...)` starts a child whose result is not retained - Accepted ADR-0032 adds guarded 512 KiB default task stacks plus `TaskGroup.start_with_stack(...)` and `start_soon_with_stack(...)` overrides from the measured-shallow-task 256 KiB minimum through 64 MiB - `Queue[T]` provides bounded or unbounded task-aware communication - `yield_now()` provides an explicit cooperative scheduling point - `select(...)` provides a typed heterogeneous Queue/Task/deadline wait under Accepted ADR-0034 - `wait_any(...)` and `wait_all(...)` coordinate task completion There is no `Channel`, statement-form `select`, bare `spawn`, or detached task. `select(...)` is an ordinary builtin call; it does not add branch syntax. Task bodies execute on pinned cooperative scheduler workers on both maintained backends. The default worker count is the available parallelism reported by the host; the provisional `AURA_WORKERS=` override selects an explicit count. A child receives a stable worker assignment when it is spawned. Its coroutine stack never migrates, work is not stolen, and `yield_now()` yields only to runnable work on that worker. Compiler-inserted checks on every loop backedge prevent a tight loop from starving ready timers, Queue operations, and sockets assigned to the same worker indefinitely. Ordinary loop tails and `continue` participate; `break` and `return` bypass the backedge. The checks do not inspect cancellation, and one long loop body can still delay same-worker siblings. Ordinary tasks request a guarded 512 KiB coroutine stack; explicit requests may range through 64 MiB. Waits use persistent descriptor registrations, heap-managed deadlines, and direct Queue, task-completion, and blocking-pool notifications; an idle worker blocks until work, an event, or a deadline without a periodic tick. Under Accepted ADR-0035, the separate process-wide blocking-I/O pool is lazily initialized. The first runtime preflight reads its settings once and keeps that configuration immutable for the process lifetime without starting worker threads. First blocking submission creates the complete worker set; production reuses it until process exit and exposes no Aura shutdown/join surface. `AURA_BLOCKING_WORKERS` selects an exact positive worker count; without it, the runtime derives and clamps a `2..=8` default from host parallelism with fallback `4`. `AURA_BLOCKING_QUEUE_CAPACITY` optionally bounds accepted pending jobs only; when it is omitted, the pending queue is unbounded. Full-queue admission is FIFO and scheduler-aware. MIR, direct, and standalone execution reject invalid values with `AU4006` before user code. Cancellation or timeout before queue insertion prevents submission; accepted work runs once and has any abandoned result discarded. The bound cannot interrupt host calls or guarantee unrelated blocking-I/O progress while all workers remain occupied. Accepted ADR-0033 implements compiler-derived structural Transfer checks for task captures, task results, and Queue payloads, plus conditional task-handle Copy and statically single-consumer non-repeatable results. Queue and Task handles are the maintained cross-worker channels; all other boundary values remain owned and share-nothing through `Transfer`. Cancellation and diagnostic context stay per task. Scheduling, completion, and program-output order are unspecified. Task execution is multicore; preemption, work stealing, worker introspection, and detached tasks are unavailable, while parallel speedup depends on the program. See [Execution Model](/manual/execution-model) and [Current Limits](/manual/current-limits). Accepted ADR-0036 defines complete typed runtime frames on both maintained backends. Diagnostics carry innermost-first Aura call frames and youngest-first task ancestry. Each public schema-version-1 frame span has its own required source `path`; the analysis/LSP editor shape permits an optional `file_path` for source-only analysis. The public diagnostic schema remains version `1` because the always-present arrays are an additive extension; compiler-service/editor transport uses semantic schema version `5`. This version includes structural function values, import aliases, and the expanded numeric expression surface, and forwards the same diagnostic records. ## Platform And Distribution Support Release archives target glibc Linux x86-64 and macOS x86-64/Apple silicon. Each archive includes the native runtime and linker manifest used by `aura build`; Cargo and the Aura source checkout are not runtime dependencies of an installed archive. A host C compiler is still required. See the repository `SUPPORTED_PLATFORMS.md` for the exact matrix and pinned toolchain. ## Source: docs/manual/tuples.md # Tuples Tuples are fixed-size, heterogeneous product values. Aura's minimal tuple surface is intended for returning, passing, unpacking, and pattern-matching a known number of values. Tuples are not variable-size collections. ## Grammar The normative productions are in [Complete Grammar](/manual/grammar): ```ebnf tuple-expression = "(", expression, ",", ")" | "(", expression, ",", expression, { ",", expression }, ")" ; tuple-type = "(", type, ",", ")" | "(", type, ",", type, { ",", type }, ")" ; unpack-target = binding-target, ",", binding-target, { ",", binding-target } | "(", binding-target-list, ")" ; binding-target-list = binding-target, "," | binding-target, ",", binding-target, { ",", binding-target } ; binding-target = identifier | "(", binding-target-list, ")" ; tuple-pattern = "(", pattern, ",", ")" | "(", pattern, ",", pattern, { ",", pattern }, ")" ; ``` Tuple value expressions are always parenthesized. `(value)` remains grouping, while `(value,)` is a singleton tuple. `()` is not a tuple value. A multi-element tuple has no trailing comma: ```aura def main(): pair = ("north", 7) singleton = (true,) nested = (pair, (2, 3)) ``` Top-level assignment and `for` binding lists use `left, right`; parentheses represent a nested target or a singleton target. Tuple types and tuple patterns are parenthesized. ## Typing Rules A tuple type records one exact element type at each position: ```aura def location() -> (str, int64): return ("north", 7) point: (int64, int64) = (3, 4) ``` The tuple expression's arity and element types must exactly match an expected tuple type when one is present. Otherwise each element is inferred in its own position. Tuple types are structural: two tuple types are equal exactly when they have the same arity and equal corresponding element types. Tuple value `==` and `!=` require both operands to have the same static tuple type. Equality then compares corresponding element values recursively. When one operand is a tuple literal and the other has a known tuple type, that exact type contextually types the literal recursively; this rule is symmetric. `<`, `<=`, `>`, and `>=` are not defined for tuples; Aura does not infer a lexicographic ordering. The ordinary optional-type suffix applies to a complete tuple type: `(str, int64)?` is `Option[(str, int64)]`. `indirect` tuple types are rejected; `indirect` remains the recursive named-field facility. Consequently, a class field cannot place its recursive link inside a tuple. Put that link in a separately named `indirect` field instead; the compiler diagnoses the tuple case with that exit. An unpacking target or tuple pattern must have the scrutinee's exact recursive tuple shape. Each binding leaf receives its corresponding element type. Duplicate names and a leaf that shadows a visible name are rejected by the ordinary binding rules. A tuple binding leaf is a name, not a member or index place. Tuple indexing accepts only a non-negative integer literal known at compile time. The literal must select an existing position, and that element's type must be copyable. The expression's type is the selected element type. A computed index, a negative literal, an out-of-bounds literal, or selection of a non-copy element is a static error. ## Runtime Semantics A tuple value stores its elements in source order. Construction evaluates and captures each element from left to right. An unpacking operation evaluates its right side or iteration item exactly once, then binds leaves left to right according to the recursive tuple shape. A tuple-pattern match evaluates the scrutinee once and tests arms in source order. The first matching arm executes. Tuple patterns are irrefutable when all nested patterns are binding patterns or `_`; literal and enum subpatterns retain their existing matching and exhaustiveness rules. Constant tuple indexing selects the statically named position and returns a copy. It has no runtime index expression to evaluate. Tuple `==` compares corresponding element values from left to right using each element type's ordinary equality semantics. Nested tuples apply the same rule recursively. The result is `true` only when every corresponding comparison is true; comparison stops at the first unequal element. Tuple `!=` is the logical negation of tuple `==`. Both complete operand expressions are evaluated once, left to right. The comparison reads the two resulting tuple values and consumes neither, even when an operand contains non-copy elements. Runtime element-type, transport, or backend metadata carried with a tuple value is not an additional equality component; the checker has already required one common static tuple type. Evaluating an operand expression still has its ordinary ownership effects; the equality operation itself adds no move. Tuple equality links use the ordinary comparison-chain contract. For example, `first == middle != last` evaluates `first`, then `middle`, compares the first link, and evaluates `last` only when that link is true. Each evaluated operand, including `middle`, is evaluated once. Tuple ordering remains a static error. Tuple rendering uses parentheses, `, ` between elements, and one final comma for a singleton: `(1, 2)` and `(1,)`. Each element uses its ordinary Aura rendering, so a contained `str` is not quoted. `print`, f-string interpolation, and backend diagnostics use this same format. Rendering is not part of tuple equality, and it does not define tuple ordering. ```aura def make_record() -> (str, int64): return ("Aura", 7) def main(): record = make_record() assert record == ("Aura", 7) assert record != ("Aura", 8) name, version = record print(name) print(version) copy_pair = (10, 20) print(copy_pair[1]) for label, count in [("ready", 2), ("done", 3)]: print(f"{label}:{count}") nested = ((1, 2), true) assert nested == ((1, 2), true) assert nested != ((1, 3), true) assert (1, 2) == (1, 2) != (2, 1) match nested: case ((left, right), flag): print(left + right) print(flag) ``` ```text Aura 7 20 ready:2 done:3 3 true ``` ## Ownership And Evaluation Order A tuple is copyable if and only if every element type is copyable. Assignment, owned argument passing, returns, and pattern flow then follow the ordinary copy or move rule for the tuple as a whole. Unpacking a copy tuple copies its elements and leaves the source usable. Unpacking a non-copy tuple consumes the whole source exactly once and gives owned leaf bindings. Aura does not turn positional fields into independently reusable partial-move places; any later source use is diagnosed as use after move. Tuple `==` and `!=` are shared-read operations rather than unpacking or ownership transfer. They leave both operands usable, including a non-copy tuple such as `(str, int64)`. For collection iteration, tuple leaves inherit the ownership provenance of the yielded element: - bare shared iteration retains the collection and gives shared leaf provenance for non-copy tuple elements - `own` iteration consumes the collection and gives owned leaves - bare Queue iteration receives an owned tuple item and gives owned leaves Mutable-borrow iteration with a tuple target is rejected. Aura does not reconstruct and write a recursively unpacked tuple back into a collection element. `match own` consumes a non-copy tuple scrutinee and gives owned leaf bindings. Bare `match` retains the tuple and gives shared leaf provenance. `match mut` with a tuple pattern is rejected; mutable tuple-pattern writeback is outside this surface. ## Diagnostics Malformed tuple expressions, types, targets, patterns, or comma placement are `AU1101`. An annotated tuple element-type mismatch is `AU2002`; tuple shape/arity mismatches use the checker's general `AU2999` code. Unsupported tuple operations, including non-constant or invalid indexing and mutable tuple writeback forms, are rejected at check time with a diagnostic that identifies the restriction and the supported alternative. Using a non-copy tuple after whole-source unpacking or `match own` is `AU3001` and points to the move. Attempting to move an element through shared unpacking or bare `match` is `AU3002`. ## Backend Support Tuple construction, fixed structural types, function returns, recursive assignment/loop unpacking, tuple patterns, whole-source ownership, and copy-only constant indexing are implemented alongside recursive structural equality for MIR execution and direct native generation. Maintained parity fixtures require both backends to produce the same output and primary diagnostics. ## Limits And Implementation-Defined Behavior Aura 0.3 has no empty tuple, multi-element trailing tuple comma, tuple iteration, tuple methods, tuple ordering, named tuple elements, rest/star unpacking, mutable tuple-target writeback, tuple slicing, or dynamic tuple indexing. A tuple is not implicitly converted to or from `list`. Tuple element order, left-to-right construction, recursive shape matching, copy classification, whole-source non-copy moves, constant-index results, and recursive equality are language-defined rather than implementation-defined. Runtime tuple metadata cannot change the equality result. ## Status The minimal tuple kernel and its Batch 3 B3.0-c equality amendment are Accepted under ADR-0026. The maintained implementation includes parenthesized tuple values and types, function returns, recursive assignment and `for` unpacking, recursive tuple patterns, structural copy classification, whole-source moves, shared borrowed destructuring, copy-only constant indexing, and same-static-type recursive `==` and `!=`. The limits above remain intentional parts of the accepted boundary. ## Source: docs/manual/types.md # Types Aura is statically typed. Every expression has a type, and type annotations are part of the public shape of functions, fields, methods, and many empty literals. The type system is designed to keep three facts visible: - what kind of value a program has - whether the value is copied or moved - whether failure is represented in the return type ## Scalar Types | Type | Description | | --- | --- | | `bool` | Boolean value: `true` or `false`. | | `int` | Alias for `int64`; it is not a distinct type. | | `int8`, `int16`, `int32`, `int64`, `int128`, `intsize` | Signed integers. | | `uint8`, `uint16`, `uint32`, `uint64`, `uint128`, `uintsize` | Unsigned integers. | | `float32`, `float64` | Floating-point values. | | `str` | Owned UTF-8 string; `len()` counts Unicode scalar values and `byte_len()` counts encoded bytes. | | `None` | Unit type and unit value. | | `Duration` | Signed 128-bit nanosecond duration used by arithmetic, sleeps, timeouts, and scheduling APIs. | | `Range` | Integer range returned by `range(...)`. | Integer bounds are exact: | Type | Inclusive range | | --- | --- | | `int8` | -128 through 127 | | `int16` | -32,768 through 32,767 | | `int32` | -2,147,483,648 through 2,147,483,647 | | `int64` | -9,223,372,036,854,775,808 through 9,223,372,036,854,775,807 | | `int128` | -2^127 through 2^127 - 1 | | `uint8` | 0 through 255 | | `uint16` | 0 through 65,535 | | `uint32` | 0 through 4,294,967,295 | | `uint64` | 0 through 18,446,744,073,709,551,615 | | `uint128` | 0 through 2^128 - 1 | | `intsize` | host-pointer-width signed range | | `uintsize` | host-pointer-width unsigned range | `float32` and `float64` use IEEE-754 binary32 and binary64 representations. Literal lexing first requires a finite binary64 value; contextual `float32` conversion may round or overflow as recorded in [Current Limits](/manual/current-limits). Runtime operations may produce NaN, but Aura 0.3 makes `/`, `//`, or `%` by a floating zero explicit runtime failures rather than producing infinity or NaN through those operators. `int` is an alias for `int64`, so the two spellings have identical bounds, type identity, layout, and runtime behavior. Integer literals may be decimal, hexadecimal (`0x`), binary (`0b`), or octal (`0o`), with underscores between digits. Every spelling follows the same contextual typing and bounds rules. An unsuffixed integer literal uses an expected integer type when one is available. It may also use an expected `float32` or `float64` when its value is exactly representable in that target; this is literal typing, not a conversion available to integer variables. Otherwise it defaults to `int64`. The default does not widen explicitly typed APIs. Existing fixed `int32` contracts remain `int32`, including `main()` exit statuses, queue capacities, and bounded process/network I/O byte-count parameters. Position APIs form one deliberate exception: range bounds and yields, collection indices, slice endpoints, enumeration positions, and Array coordinates use `int64`. Values of type `int8`, `int16`, `int32`, `uint8`, `uint16`, or `uint32` widen losslessly at those positions. This conversion is unavailable in ordinary assignments, arguments, operators, and returns. Length results are also `int64`: the builtin `len`, `str.len`, `str.byte_len`, `list.len`, `dict.len`, and `set.len` all return `int64`, so they compose directly with ranges and indices. `random.secure_bytes(n)` is a separate byte-count API: `n` is `int64`, with a fixed per-request resource and safety ceiling of `2147483647`. `Duration` stores a signed 128-bit count of nanoseconds. Literal units are normalized exactly to nanoseconds; literals are non-negative, while associated constructors and arithmetic can produce negative values. Representability as a language value is separate from validity as a host wait or deadline. `Range` contains `int64` start/end values and iterates from the start inclusive to the end exclusive. The associated constructors `Duration.ms(int64)`, `Duration.seconds(int64)`, and `Duration.minutes(int64)` accept signed counts. Duration values support checked addition and subtraction with another Duration, multiplication by `int64` in either operand order, floor division by `int64`, and full value-based comparison. `to_ms()` and `to_seconds()` convert the exact rational unit value to the nearest representable IEEE-754 binary64 value, ties-to-even; they may round. Their rounding, Duration rendering, and invalid host-timer policy are accepted under ADR-0019; the signed nanosecond representation and operators are accepted under ADR-0007. Numeric literals are checked against the target type. Integer literals must fit an annotated integer target, and a float-context integer literal must be exactly representable in its `float32` or `float64` target. An inexact literal must make rounding explicit with a floating spelling or `.to_float()`. Integer-to-float casts also reject silent precision loss. Separately, every integer type provides `.to_float() -> float64`, which intentionally permits IEEE-754 round-to-nearest, ties-to-even conversion when an application wants to enter the floating domain. A bare `value: str` parameter grants shared access. Bare parameters do the same for copy and move types; an implementation may pass copy bits directly without changing that source contract. `str` owns its UTF-8 storage. Aura has no separate slice layout or lifetime-bearing text-view type. `str.len() -> int64` scans the text and counts Unicode scalar values in O(n). `str.byte_len() -> int64` reads the UTF-8 byte count in O(1). `str.to_bytes() -> list[uint8]` and `str.from_bytes(list[uint8]) -> Result[str, bytes.Error]` provide the explicit strict UTF-8 boundary; `list[uint8]` is Aura's bytes representation. Aura has no distinct character type, integer str indexing, `chars()`, `ord()`, or `chr()`. String slicing accepts `int64` scalar endpoints, runs in O(n) over the source, and returns a fresh owned str. It is not a view or a byte-indexing operation. ## Copy And Move Categories Copy values may be reused after assignment or calls through value/`own` positions: - numbers - `bool` - `Duration` - `Queue[T]` - under Accepted ADR-0033, `Task[T]` only when `T` is repeatable as defined in [Provisional Transfer Classification](#provisional-transfer-classification) - tuple values when every element type is copyable - `copy class` values whose fields are all copyable - user enum values when every declared payload type is statically copyable - `Option[T]`, `Result[T, E]`, `SendError[T]`, and `QueueReceive[T]` when all payload types are copyable Move values transfer ownership: - tuple values with at least one move element - `str` - `list[T]` - `dict[K, V]` - `set[T]` - `random.Rng` - ordinary user classes - user enum values with any move payload - `json.Value` and `json.Error` - `Option`, `Result`, and related outcome values with move payloads - `TaskGroup` - file, process, supervisor, and network resources - opaque FFI handles declared by `extern "C" opaque class` Move values can still be shared through a bare parameter, accessed mutably through a `mut` parameter, or duplicated explicitly through methods such as `.clone()` when the type supports cloning. Slicing `str` produces a fresh owned str. Slicing `list[T]` produces a fresh owned list and is clone-producing for `T`: Copy elements are copied, non-Copy elements must be clone-safe, `random.Rng` state is rejected with `AU3007`, and non-repeatable Task observation rights are rejected with `AU3009`. The result is another move value independent of the source. `Queue[T]` is a copy handle to shared runtime state. Under Accepted ADR-0033, a `Task[T]` handle is conditionally copyable so aliases cannot duplicate a single-consumer result right. Copying an allowed handle never copies queued values or task results; it gives another reference to the same queue or task. `TaskResult[T]`, `SelectOutcome[Q, T]`, `WaitAny[T]`, and `WaitAll[T]` are treated as move outcome values even when every payload type is copyable. `Range` is also not a general copy type in Aura 0.3; use ranges directly in iteration rather than relying on duplication. A generic user-enum payload whose declared type is an unconstrained type parameter is not assumed copyable, even when one later instantiation supplies a copy type. ## Tuple Types `(T1, T2)` is a fixed two-element structural tuple type and `(T,)` is a fixed singleton tuple type. Tuple arity and corresponding element types are part of type identity. Tuple types may appear anywhere another complete type reference is accepted, including parameter, field, payload, local annotation, and return positions. A tuple is copyable if and only if every element is copyable. Copy classification is recursive through nested tuples. Otherwise the complete tuple is a move value; unpacking it consumes the source as one whole value rather than exposing independently reusable positional partial moves. Two tuple values may be compared with `==` or `!=` only when they have the same static tuple type. The comparison is recursive over corresponding element values and reads rather than consumes both operands, regardless of copy classification. Runtime metadata carried with a tuple value is not part of value equality. Tuple ordering is not defined. Aura has no empty tuple type and does not convert tuples to or from collections. See [Tuples](/manual/tuples) for construction, unpacking, patterns, indexing, and the exact current boundary. Copy/move classification and clone safety are distinct. `random.Rng` is not merely a move type: it exposes no public duplication route. A clone-producing operation is valid only when its produced type cannot contain an `Rng` through an ordinary value-storing class, enum, or collection path. `Task[T]` and `Queue[T]` stop that traversal because copying either handle does not observe or copy its stored `T`; moving, removing, or receiving a value also transfers one owner instead of cloning it. ## Provisional Transfer Classification Accepted ADR-0033 defines the static property used at a task boundary. `Transfer` means that ownership of a value may cross from one Aura task worker to another; it is separate from both Copy and clone safety. `Transfer` is derived by the compiler and is not a builtin trait that source code can implement or assert. An ordinary user trait also named `Transfer` does not affect this structural classification. All copy types and `str` are `Transfer`. `list[T]`, `set[T]`, `dict[K, V]`, tuples, classes, and enums are `Transfer` exactly when all of their stored component types are. The same recursive rule covers data wrappers such as `Option`, `Result`, task/queue outcomes, errors, and `json.Value`. `Queue[T]` and `Task[T]` handles are `Transfer` independently of `T`: moving the handle does not inspect or move the stored payload. Queue construction, `put`, and `try_put` separately require its payload `T` to be `Transfer`; handle copies, receives, fallback receives, and `close` do not recheck `T`. Shared and mutable capability views are not `Transfer`. Neither are `random.Rng`, `TaskGroup`, or live filesystem, process, pipe, supervisor, listener, socket, stream, HTTP-exchange, WebSocket, or TLS resources. A later decision may whitelist an individual host type only after its thread-safety is proved. Owned data returned from a host operation, such as completed output or a structural error value, is classified from the data it stores rather than from where it originated. `process.Completed`, `net.HttpResponse`, and `net.UdpDatagram` are explicitly Transfer owned snapshots; their live `process.Child`, `net.HttpExchange`, and `net.UdpSocket` sources are not. Reading a Copy value through shared or mutable access materializes an independent owned snapshot rather than transporting the capability. That snapshot may cross when its type is `Transfer`. Non-copy access cannot use this exception because value capture would require ownership. An unconstrained generic parameter does not prove `Transfer`. Phase 5.6 does not infer a deferred Transfer contract: a task or Queue boundary with an unresolved parameter is rejected with `AU3008`. A generic task target is usable when call inference has already produced complete concrete capture and result types. A task target may spell explicit specialization narrowly as `function[Types]` or `Type.associated_method[Types]`; brackets retain ordinary indexing meaning outside a TaskGroup start target. A bare target is valid when its declared/default context already makes every relevant type concrete. `Task[T]` is always `Transfer`, but ADR-0033 makes its Copy classification conditional. It is copyable only when `T` is copyable, when `T` is `Queue[...]`, or when `T` is `Task[U]` and `U` is recursively repeatable. This prevents a nested handle such as `Task[Task[str]]` from being copied to duplicate a single-consumer result right. This classification is the Phase 5.6 boundary used by the pinned-worker runtime. Queue and Task handle state is synchronized for cross-worker use; all other task captures and results remain owned, structural `Transfer` values. The boundary therefore stays share-nothing even when sibling task bodies run on different pinned workers. ## Builtin Generic Types | Type | Meaning | | --- | --- | | `Option[T]` | `Some(T)` or `None`; use for ordinary absence. | | `Result[T, E]` | `Ok(T)` or `Err(E)`; use for recoverable failure. | | `list[T]` | Owned ordered collection. | | `dict[K, V]` | Owned key/value dictionary. | | `set[T]` | Owned set of unique values. | | `Array[T]` | Owned contiguous row-major numeric array; `T` is exactly `int32`, `int64`, `float32`, or `float64`. | | `Queue[T]` | Scheduler-aware typed queue handle. | | `Task[T]` | Transferable task-result handle; conditionally Copy under Accepted ADR-0033. | | `SendError[T]` | Queue send failure that carries the unsent value. | | `QueueReceive[T]` | Queue receive outcome. | | `TaskResult[T]` | Task result outcome. | | `SelectOutcome[Q, T]` | Typed `select(...)` outcome for Queue payload `Q` and Task result `T`; an absent source category uses `None`. | | `WaitAny[T]` | `wait_any(...)` outcome. | | `WaitAll[T]` | `wait_all(...)` outcome. | `Array[T]` has runtime rank and `list[int64]` shape metadata rather than shape-level static type arguments. Every Array has rank at least one, may contain zero-length dimensions, and owns its contiguous CPU buffer. It is non-Copy, explicitly cloneable, and structurally `Transfer`; a Task result containing an Array retains the ordinary single-consumer observation right. See [Numeric Arrays](/manual/numeric-arrays). ## Resource And Module Types These types are provided by builtin modules and are reserved names. | Module | Types | | --- | --- | | `io` | `io.Error` | | `fs` | `fs.File` | | `json` | `json.Value`, `json.Error` | | `random` | `random.Rng` | | `net` | `net.TcpListener`, `net.TcpStream`, `net.UdpSocket`, `net.UdpDatagram`, `net.HttpListener`, `net.HttpExchange`, `net.HttpResponse`, `net.WebSocketListener`, `net.WebSocket`, `net.UnixListener`, `net.UnixStream`, `net.TlsListener`, `net.TlsStream` | | `process` | `process.Child`, `process.Pipe`, `process.Completed`, `process.Supervisor`, `process.ExitStatus`, `process.Wait`, `process.Stdio`, `process.Error`, `process.RestartPolicy`, `process.SupervisorEvent`, `process.SupervisorWait` | Resource types should usually be scoped with `with` or closed explicitly. `random.Rng` is an opaque move type rather than a resource: it has mutable state but no `close()` operation or `with` contract. Its complete type and sequence rules are in [Randomness Module](/manual/randomness). `json.Value` is a move type whose recursive variants represent Null, Boolean, `int64`, finite `float64`, str, list, and dict object data. `json.Error` is a move type because its Syntax variant owns a str. Their exact variants and number rules are in [JSON Module](/manual/json). ## Type Annotations Simple annotations: ```aura count: int32 = 0 name: str = "aura" ``` Collection annotations: ```aura names: list[str] = [] lookup: dict[str, int32] = {} seen = set[int32]() ``` Empty collection literals need an expected type. Constructors are also available: ```aura names = list[str]() lookup = dict[str, int32]() seen = set[int32]() ``` `T?` is shorthand for `Option[T]`: ```aura name: str? = None ``` Type arguments are invariant, nonempty when brackets are present, and must exactly match the declared arity. Aura does not implicitly convert `list[int32]` to `list[int64]` or treat structurally identical user classes as the same type. ## Option And Result Types Construct `Option` and `Result` with their enum names: ```aura maybe: Option[str] = Option.Some("name") missing: Option[str] = Option.None result: Result[int32, str] = Result.Ok(42) failure: Result[int32, str] = Result.Err("bad number") ``` Bare `None` contextually denotes `Option.None` whenever an expected `Option[T]` is available. This context flows through grouping, annotated bindings, returns, and arguments. Equality and inequality provide the context symmetrically: if either operand is `Option[T]`, a bare `None` on the other side has that same option type. Unit `None == None` is `true` and unit `None != None` is `false`. A qualified `Option.None` without an expected or otherwise inferred specialization is rejected because `T` is unconstrained. Aura has no identity-test spelling: use `value == None`, `value != None`, or `match`, not Python's `is` or `is not`. Pattern matching may use qualified or short-form variants when the type is known: ```aura match result: case Result.Ok(value): print(value) case Result.Err(message): print(message) ``` ## User Types Classes create product types: ```aura class Point: x: float64 y: float64 ``` Enums create sum types: ```aura enum Load[T]: Ready(value: T) Empty Failed(message: str) ``` Traits define shared behavior: ```aura trait Named: def name(self) -> str ``` ## Recursive Fields Direct recursive fields are not implemented. Use `indirect` for recursive class fields: ```aura class Node: value: int32 next: indirect Option[Node] = Option.None ``` `indirect` gives the recursive field a level of indirection so the value has a finite size. ## Casts Numeric casts use `value as NumericType`. Non-numeric casts are not implemented. - integer-to-integer casts require the value to fit the target bounds - integer-to-float casts require exact representability and reject silent precision loss; use integer `.to_float()` when a possibly rounded `float64` result is intended - float-to-integer casts require a finite in-range value and truncate toward zero - `float64` to `float32` rounds through the host `float32` representation - `float32` to `float64` preserves the represented value Casts are checked at runtime when the source value is not a compile-time literal. A failed cast is a runtime diagnostic, not `Result.Err`. Use parsing functions for text-to-number conversion: ```aura def parse_answer() -> Result[int32, str]: value = try parse_int32("42") return Result.Ok(value) ``` ## Grammar Type syntax consists of an identifier or module-qualified type path, optional bracketed type arguments, the optional marker `?`, and `indirect` in class-field position, as collected in [Grammar](/manual/grammar). `mut` and `own` parameter modifiers are not type constructors. Bare, `mut`, and `own` parameter capabilities govern access at a call boundary, while every `-> T` annotation describes an owned result. ## Typing Rules Every expression has one static type. An annotation, parameter, return, field, collection element, or expected enum context may type a compatible literal; otherwise integers default to `int64` and floating literals to `float64`. Unqualified `int` is exactly `int64`. Non-literal values never widen implicitly. Generic arity, substitutions, bounds, field recursion, optional desugaring, cast legality, and exact assignment equality are checked before execution. ## Runtime Semantics Copy scalars and declared copy aggregates are represented by value. Other values use the maintained owned runtime representations documented by their feature pages. Arithmetic and casts are checked and may trap with a runtime diagnostic; typed library failure remains an `Option` or `Result` value. `indirect` inserts the maintained runtime indirection needed to construct a recursive field. ## Ownership And Evaluation Order The static type determines whether reading an owned place copies it or moves it. A copy declaration is valid only when every stored field or payload is copy. Borrowing and parameter passing do not change the underlying type, and Aura inserts neither hidden cloning nor runtime coercion. Type annotations are erased after checking and add no evaluation step. Generic clone-producing uses infer clone-safety obligations that are checked after specialization; this does not change the underlying copy/move category. ## Diagnostics `AU1101` reports malformed type, type-argument, or annotation syntax. `AU2001` reports an unknown or unavailable type name. `AU2002` reports type mismatches, unresolved contextual literal typing, generic arity, payload, field, and annotation mismatches. `AU2003` reports unsupported numeric operators or casts, and `AU2004` reports invalid constructor argument binding. `AU2999` covers invalid recursive layouts and other type rejections without a narrower category. `AU3001` reports use of a moved non-copy value; `AU3002` reports a borrow conflict; `AU3003` reports mutation through an immutable place; and `AU3004` reports an invalid ownership or receiver type mode. `AU3005` reports a non-copy indexed read, and `AU3006` reports a non-copy indexed compound assignment. `AU3007` reports an operation or specialization that would duplicate non-cloneable state such as `random.Rng`, an opaque FFI handle, or a capturing closure environment. `AU3008` reports a non-Transfer task or Queue boundary. `AU3009` rejects cloning, collection reads, or aggregate copies that would duplicate a single-consumer task-result right; using an already-consumed task binding is `AU3001`. Runtime `AU4001` means a general checked trap, `AU4002` means numeric overflow, underflow, range, or exactness failure, `AU4003` means a bounds or lookup violation, `AU4004` means a zero divisor, and `AU4005` means a trapping resource or I/O failure. ## Backend Support The checker produces one canonical type model for MIR lowering, compiler-backed analysis, and direct native code generation. All types documented as implemented are supported by both maintained execution paths; the parity gate contains a backend surface that cannot preserve the same behavior. ## Limits And Implementation-Defined Behavior `int` is an alias for `int64`; method-value types, user-defined numeric casts, and non-numeric casts are unavailable, and recursive value fields require `indirect`. Capture-free named function values use `def(T1, mut T2, own T3) -> R`; bare parameters are shared and the written `mut`/`own` modes are part of the type. Contextually typed lambdas use that same source-level callable signature; a capturing closure additionally owns its hidden environment. Arbitrary stored and parameter `def` types describe capture-free code pointers; compiler-known callback and task-start sites preserve the additional closure metadata. `intsize` and `uintsize` follow the target pointer width, and host process exit transport may narrow an `int32` after Aura returns it. Other numeric widths and overflow behavior are language-defined rather than implementation-defined. FFI v0 opaque handles are nominal non-Copy, non-cloneable, non-Transfer wrappers for one non-null foreign pointer. Extern functions are direct-call-only declarations rather than `def(...) -> ...` values. ## Status The scalar, collection, enum, class, trait-bound, resource, optional, result, and indirect types described by this Manual are implemented for the post-Phase 1.5 surface. Return values are owned, and current syntax reserves no future loan or view contract. Capture-free function types and by-value expression closures are implemented. FFI v0 fixed-width declarations, byte/string views, and opaque handle types are implemented; extern functions do not become first-class function values. Method-value types are unavailable. Structural tuple types and their Batch 3 B3.0-c equality amendment are Accepted under ADR-0026. `str` is the owned UTF-8 text type. A distinct borrowed text-view type is unavailable. None of the unavailable types may be inferred from current syntax. ## Source: docs/learn/index.md # Learn Aura This book teaches Aura the way a programmer tends to actually learn a language: by writing short programs that do something real, then extending them until the pieces fit together. Each chapter introduces one part of the language through a program that would make sense to run. By the end of the track you will have built command-style tools, domain models, text parsers, concurrent worker pools, subprocess runners, and small network services, and you will have met the language rules that keep those programs honest. ## The Three Questions Aura Wants You To Ask Programs in Aura tend to be easier to read when three questions are answered near the code. **Who owns this value?** Small things copy — numbers, booleans, durations, queue handles. Binding one to a new name is cheap and both names keep working. Everything else moves: strings, collections, class instances, files, processes, task groups, network resources. Assignment hands ownership over. (A task handle sits in between: it copies when its result can be read more than once.) At a call boundary the signature tells you which it is — a bare parameter shares, `own` transfers. **Can this call fail?** Failure that a caller might sensibly handle lives in the return type. `Result[T, E]`, `Option[T]`, `QueueReceive[T]`, `TaskResult[T]`, and the I/O and process error enums let a program handle each failure at the line where it matters. **What closes this resource?** Files, network sockets, subprocess pipes, supervisors, and task groups should normally live inside a `with` block. The block is what runs cleanup — on normal exit and on runtime errors that unwind through it. `with` is how you turn "please remember to close this" into "this closes itself." ## The Shape Of An Aura Program A complete script: ```aura class Point: x: float64 y: float64 def distance(point: Point) -> float64: return sqrt((point.x * point.x) + (point.y * point.y)) point = Point(x=3.0, y=4.0) print(distance(point)) ``` Several ideas are already visible. `Point` is a class with named fields. `distance` only reads its argument, so the bare parameter grants shared access and the caller keeps the point. `print` renders a value and adds a newline. The script runs top to bottom; no `main` is required. Run it with: ```bash aura run examples/classes/point_distance.au ``` ## What This Track Covers The chapters are ordered so that each idea has a practical reason to exist before the formal rules arrive. 1. [Getting Aura Running](/learn/install-and-run) — install the CLI, run your first program, build your first binary. 2. [Aura For Python Developers](/learn/from-python) — the fast track: what transfers from Python, and what will surprise you. 3. [The First Program](/learn/small-programs) — bindings, functions, control flow, and small decisions made with `match`. 4. [Shaping Data](/learn/data-modeling) — classes, enums, methods, and the patterns that keep domain data honest. 5. [Working With Collections](/learn/collections) — `list[T]`, `dict[K, V]`, `set[T]`, eager owned comprehensions, owned list/str slices, and fixed shape numeric `Array[T]` values. 6. [Converting Between Types](/learn/casting) — `as`, `.to_float()`, parsing text, and why nothing converts implicitly. 7. [Values, Moves, And Borrows](/learn/ownership-and-borrowing) — the ownership model, explained through the programs that benefit from it. 8. [Results, Options, And `try`](/learn/results-and-options) — how Aura represents recoverable failure without hiding control flow. 9. [Testing](/learn/testing) — writing tests, reading a failed assertion, parameterized cases, and CI output. 10. [Organizing Code](/learn/modules-and-packages) — splitting a program into files, packages, and workspaces. 11. [Structured Concurrency](/learn/concurrency) — `TaskGroup`, `Task[T]`, `Queue[T]`, cancellation, and worker pools. 12. [Talking To The World](/learn/io-process-networking) — files, processes, sockets, HTTP, and supervisors. 13. [Running And Shipping](/learn/native-builds) — when to use `run`, when to use `build`, and what the native binary gives you. 14. [Calling A Small C API](/learn/ffi) — package-authorized FFI v0 for fixed-width values, temporary byte views, and opaque handles. Three case studies put the pieces together: - [Log Analyzer](/learn/case-studies/log-analyzer) — a text-processing tool with parsing, aggregation, and a report. - [Queue Worker Pool](/learn/case-studies/queue-worker-pool) — a structured-concurrency pattern that shuts down cleanly. - [Supervised Process Runner](/learn/case-studies/process-supervisor) — a small service supervisor with a restart policy and an event stream. ## Reading The Manual Alongside Learn Each Learn chapter ends by pointing at the matching Manual section. When a rule is surprising or a contract needs checking, go straight to the reference: - [Types](/manual/types) - [Ownership And Borrowing](/manual/ownership-and-borrowing) - [Collections](/manual/collections) - [Numeric Arrays](/manual/numeric-arrays) - [Concurrency](/manual/concurrency) - [Process Module](/manual/process) - [Foreign Function Interface (FFI) v0](/manual/ffi) - [API Index](/manual/api-index) The Manual is deliberately less chatty than Learn. It says what a thing is, not why you might want it. ## Source: docs/learn/casting.md # Converting Between Types Aura never converts a number behind your back. An `int32` does not quietly become an `int64`, and an integer does not drift into a float because it was convenient. Every conversion is written down, and there are three ways to write one. ## `as` For Numbers, Checked At Runtime `expr as Type` converts between numeric types: ```aura small: int32 = 7 wide = small as int64 # 7 big: int64 = 300 narrow = big as int32 # 300 exact = 3 as float64 # 3.0 truncated = 3.9 as int64 # 3, toward zero ``` `as` is exact or it fails. If the value does not fit the target, the program stops with a diagnostic instead of wrapping around: ```aura big: int64 = 5000000000 narrow = big as int32 ``` ```text error[AU4002]: integer value `5000000000` does not fit in `int32` ``` The same rule applies to floats. An integer too large to be represented precisely as a `float64` is a trap, not a silent rounding: ```aura n: int64 = 9007199254740993 f = n as float64 ``` ```text error[AU4002]: integer value `9007199254740993` cannot be represented exactly as `float64` ``` That is the design: `as` means "this fits, and I am telling you it fits." ## `.to_float()` When Rounding Is The Point Sometimes you *want* the nearest representable float — computing a ratio, say. `.to_float()` rounds instead of trapping: ```aura n: int64 = 9007199254740993 print(n.to_float() == 9007199254740992.0) # true, rounded to nearest ``` This is also how you divide integers, since `/` on two integers is rejected: ```aura ratio = 7.to_float() / 2.to_float() # 3.5 ``` Use `//` when you want the floor instead: ```aura whole = 7 // 2 # 3 ``` Pick by intent: `as float64` asserts exactness, `.to_float()` accepts rounding. ## Parsing And Rendering Text Text is not a numeric type, so `as` does not apply: ```aura s = "12" n = s as int64 ``` ```text error[AU2002]: casts are only supported between numeric types, found `str` and `int64` ``` Text can always fail to parse, so parsing returns a `Result` you must handle: ```aura match parse_int64("123"): case Result.Ok(value): print(value + 1) case Result.Err(message): print(message) ``` `parse_int32`, `parse_int64`, and the float parsers all follow this shape. Going the other way never fails, so it needs no `Result` — use `str(value)` or put the value straight into an f-string: ```aura n: int64 = 42 print(str(n)) print(f"as text: {n}") ``` ## Why No Implicit Conversion The rule that catches Python developers is that passing an `int32` to a function expecting `int64` is an error rather than a widening: ```aura def f(x: int64) -> int64: return x y: int32 = 5 print(f(y)) # error: expected `int64`, found `int32` ``` Write `f(y as int64)`. The reason is that implicit numeric conversion is where overflow and precision bugs hide — a language that widens silently in one direction eventually narrows silently in another. Aura's default integer type is `int64` and its default float is `float64`, so most code never mixes widths in the first place. The one exception is deliberate and narrow: an index position accepts smaller integer types, because widening an index can never lose information. ## Quick Reference | Goal | Write | | --- | --- | | Widen or narrow a number, exactly | `value as int64` | | Integer to float, rounding allowed | `value.to_float()` | | Integer division | `a // b` | | True division | `a.to_float() / b.to_float()` | | Float to integer, toward zero | `value as int64` | | Text to number | `parse_int64(text)`, handle the `Result` | | Number to text | `str(value)` or `f"{value}"` | The [Types](/manual/types) chapter gives the full conversion table and the exact trap conditions. ## Source: docs/learn/collections.md # Working With Collections Aura uses `list[T]`, `dict[K, V]`, and `set[T]`. Each collection has one exact static element shape, deterministic ownership, and explicit absence. ## Lists A list preserves order and allows duplicates: ```aura mut names: list[str] = ["Ada", "Grace"] names.append("Katherine") print(names) ``` An empty list needs an annotation or constructor: ```aura mut names: list[str] = [] mut scores = list[int32]() ``` Positions use `int64`. Negative positions count from the end: ```aura mut values = [10, 20, 30] print(values[-1]) match values.get(-2): case Option.Some(value): print(value) case Option.None: print("missing") ``` Use `get` when an invalid position is ordinary input. It returns `Option[T]` and requires clone-safe `T`. Direct indexing, `pop`, `set`, and `swap` trap on invalid positions. The core mutations have Python-shaped names and typed ownership: ```aura mut values = [10, 20, 30] values.insert(-1, 25) values.append(40) old = values.set(0, 5) last = values.pop() values.remove(20) ``` `insert` clamps its position to the range from zero through the current length. `pop()` removes the final element and returns it. `remove(value)` removes the first equal value. An absent value traps with `AU4008`; test membership first when absence is expected. `index(value)` returns the first equal position, and `count(value)` counts all equal elements: ```aura values = [3, 1, 3, 2] print(values.index(3)) print(values.count(3)) ``` List and string slices return fresh owned values: ```aura values = [10, 20, 30, 40] middle = values[1:3] suffix = values[-2:] copy = values[:] text = "A🎉Z" celebration = text[1:2] ``` Slice positions count elements for lists and Unicode scalar values for `str`. Bounds are half-open. Invalid or reversed bounds trap with `AU4003`. ### Eager Algorithms `map` and `filter` return fresh owned lists. Sorting is stable and mutates the receiver: ```aura def doubled(value: int32) -> int32: return value * 2 def is_even(value: int32) -> bool: return value % 2 == 0 def descending(value: int32) -> int32: return -value def main(): values = [3, 1, 2, 4] mapped = values.map(doubled) filtered = values.filter(is_even) mut ascending = values.copy() ascending.sort() mut reverse_order = values.copy() reverse_order.sort(key=descending) mut descending_natural = values.copy() descending_natural.sort(reverse=true) ``` A key function runs once per element before the list changes. Equal keys keep their input order. `copy()` requires clone-safe elements and returns storage independent from the source. Use capacity control for workloads that know their size: ```aura mut values = list[int32].with_capacity(1_000) values.reserve(500) ``` Capacity calls do not change list contents. Negative requests trap with `AU4003`; allocation failures trap with `AU4005`. ## Dictionaries A dictionary preserves key insertion order: ```aura mut counts: dict[str, int32] = {"ready": 2} counts["done"] = 1 counts["ready"] = 3 ``` Use `in` for membership and `get` for typed optional lookup: ```aura if "ready" in counts: print(counts["ready"]) match counts.get("missing"): case Option.Some(value): print(value) case Option.None: print("not found") ``` `get` has no default argument. It returns a cloned value and therefore requires clone-safe `V`. `remove(key)` transfers the value when present and returns `None` when absent. `keys()`, `values()`, and `items()` return eager owned lists in insertion order. Items are tuples: ```aura for key, value in counts.items(): print(key + ": " + value.to_string()) ``` `copy()` duplicates the dictionary into independent owned storage. `update(other)` transfers entries from another dictionary. An existing key keeps its insertion position; a new key is added at the end. ## Sets A set stores one value per equality class. A non-empty set literal needs a set context, and an empty set uses its constructor: ```aura mut seen: set[int32] = {1, 2, 2, 3} mut names = set[str]() ``` Membership uses `in` and `not in`. Mutation uses `add`, `remove`, and `discard`: ```aura seen.add(5) if 2 in seen: seen.remove(2) seen.discard(99) ``` `remove` traps with `AU4008` when the value is absent. `discard` is silent. Both return `None`. `copy`, `clear`, `reserve`, and `with_capacity` follow the same ownership and capacity rules as the other collection types. Sets render with braces when non-empty and as `set()` when empty. Program logic must not depend on set iteration order. ## Comprehensions Comprehensions eagerly create fresh owned collections: ```aura values = [1, 2, 3, 4] squares = [value * value for value in values] even = {value for value in values if value % 2 == 0} labels = {value: str(value) for value in values} ``` Nested clauses run in outer-major order and filters run from left to right. Collection sources are shared and frozen during a comprehension. A non-Copy value reached through shared iteration needs an explicit `.clone()` before it can enter the new collection. ## Choosing A Collection Use `list[T]` when order or duplicates matter. Use `dict[K, V]` for keyed lookup and updates. Use `set[T]` for uniqueness and membership. The normative method signatures, failure codes, evaluation order, and backend contract are in [Collections](/manual/collections). ## Source: docs/learn/concurrency.md # Structured Concurrency Concurrent programs get hard to reason about when child work has no parent. A task started deep inside a function might run forever, fail silently, or leak a resource. The fix Aura builds into the language is called **structured concurrency**: every task is created within a scope, and leaving that scope waits for, cancels, or otherwise accounts for the children. This chapter introduces that scope — the `TaskGroup` — and the two other primitives that make it useful: `Task[T]`, a handle to a task's result, and `Queue[T]`, a typed channel for moving values between tasks. ## Start One Task Begin with a plain worker: ```aura def double(value: int32) -> int32: return value * 2 ``` Run it inside a task group: ```aura with group = TaskGroup(): task = group.start(double, 21) match task.result(timeout=1s): case TaskResult.Ready(value): print(value) case TaskResult.Error(message): print(message) case TaskResult.TimedOut: print("timeout") case TaskResult.Cancelled: print("cancelled") ``` The `with` block defines the task's lifetime. Leaving the block waits for children the program started and accounts for any failures. Nothing is hidden, and nothing keeps running in the background after the block ends. `TaskResult[T]` has four cases — `Ready`, `Error`, `TimedOut`, and `Cancelled` — because those are the four things that can happen to a child task, and a reasonable program might want different behaviour for each. `Task[T]` is always safe to transfer between tasks, but it is copyable only when `T` is repeatable: a copy value, a `Queue[...]` handle, or a recursively repeatable `Task[...]` handle. A non-copy owned result gives the task handle one observation right. `result`, `result_or_none`, and `result_or` consume that right on the first attempt, even if the attempt times out, is cancelled, fails, returns `None`, or selects a fallback. A result that is not structurally `Transfer`, such as `random.Rng` or a live host resource, is rejected before the task is scheduled with `AU3008`. `AU3009` instead reports an operation that would duplicate a valid single-consumer result right. The timeout is a signed nanosecond `Duration`. Literals cover integral `ms`, `s`, and `m` values; `Duration.ms(n)`, `Duration.seconds(n)`, checked arithmetic, and comparisons handle runtime-computed backoff. A negative or host-unrepresentable wait is invalid and never means “wait forever.” ## Fire-And-Forget Inside A Scope When the program does not need a handle to a child's result, use `start_soon`: ```aura def say(label: str): print(label) with group = TaskGroup(): group.start_soon(say, "parse") group.start_soon(say, "check") ``` "Fire and forget" still has a parent here. The children are only forgotten by the local code; the runtime is still responsible for them. ## Choosing A Custom Task Stack Ordinary tasks use a guarded 512 KiB stack. That is the safe default for application code and keeps large task populations economical. If measurement shows that one child has a different task-local stack requirement, use a collision-free stack override: ```aura def deep_worker(depth: int32) -> int32: return visit_tree(depth) with group = TaskGroup(): task = group.start_with_stack(1024 * 1024, deep_worker, 128) ``` Use `start_soon_with_stack(bytes, function, ...)` when the child does not return a retained handle. The byte count is exact `int64` and must be from 256 KiB through 64 MiB inclusive. Aura rejects smaller and larger values. Accepted capacities are rounded upward to the host page size and guard-protected. Treat 256 KiB as an opt-in minimum only for a measured shallow task. It is not the ordinary default: Aura's complete compiled HTTP example faulted when 256 KiB was the global task default during integration and succeeds with the 512 KiB default. The lower-level runtime round trip that succeeds with 256 KiB protocol callers intentionally omits compiled Aura execution frames; it proves that deep protocol frames run on service workers, not that every complete Aura task is safe at 256 KiB. The method name is deliberately separate from `start`: a forwarded target may have its own parameter named `stack_size`, so Aura does not steal a named argument from the child. Prefer the ordinary methods until profiling demonstrates a need; a larger reservation is not a performance hint. ## Ownership When Starting Tasks Starting a task creates **owned captures**. Each argument moves or copies into task-owned storage before the child can outlive the caller. The target may then borrow that capture or consume it; it never borrows the caller's stack value. When both the parent and the child want the same clone-safe move value, clone before starting: ```aura def worker(label: str): print(label) with group = TaskGroup(): label = "build" group.start_soon(worker, label.clone()) print(label) ``` Copy types (numbers, `bool`, `Duration`, queue handles, and task handles with repeatable results) pass through unchanged. Bare shared parameters borrow the task-owned capture, `own` parameters consume it, and `mut` targets are rejected because detached capture has no caller-visible writeback. Every captured argument and the target result must be structurally `Transfer` after generic specialization. Copy data, `str`, structurally transferable collections and user data, and Queue/Task handle identities can cross. Capability views, `random.Rng`, `TaskGroup`, and live file, process, or network resources cannot. The compiler derives `Transfer`; user code cannot implement it as a trait. Queue and Task handle state is synchronized for cross-worker use; all other captures and results remain owned, share-nothing `Transfer` values. ## `Queue[T]`: Typed Channels A queue moves values between tasks. Handles to the same queue are copy values, so passing one to a producer does not take it away from the parent. Queue construction, `put`, and `try_put` require a structurally `Transfer` payload; receiving moves one admitted value to the consumer. ```aura def producer(jobs: Queue[int32]): for value in range(5): jobs.put(value) jobs.close() jobs = Queue[int32]() with group = TaskGroup(): group.start_soon(producer, jobs) for job in jobs: print(job) ``` Two things are happening in that `for` loop. The consumer receives each value already owned until one of three things is true: the queue is closed, cancellation interrupts the loop, or every producer in the surrounding task group has completed. Queue is not a place traversal, so explicit `own` and `mut` loop modifiers are rejected. The last case means the program can often rely on normal exit to drain the queue; explicitly calling `close()` is still the clearest signal. ## Bounded Queues And Backpressure `Queue[T]()` creates an unbounded queue. An unbounded queue is convenient but risky: a fast producer and a slow consumer will let memory grow without limit. A **bounded** queue says how many values are allowed in flight: ```aura jobs = Queue[str](capacity=2) ``` When a bounded queue is full, `put` waits until space is available, a timeout expires, the queue closes, or the task is cancelled. The failure shape is `SendError[T]`, which carries the unsent value back to the caller: ```aura match jobs.put("compile", timeout=50ms): case Result.Ok(_): print("queued") case Result.Err(SendError.Full(job)): print("full") case Result.Err(SendError.TimedOut(job)): print("timeout") case Result.Err(SendError.Closed(job)): print("closed") case Result.Err(SendError.Cancelled(job)): print("cancelled") ``` `try_put` is the non-waiting variant. Use it when waiting would be wrong — for example, when a polling loop has other work to do if the queue is full. ## A Worker Pool A common shape has one producer and several workers. Each worker reads the same queue until the producer closes it: ```aura def worker(name: str, jobs: Queue[int32]): for job in jobs: print(f"{name}: {job}") def produce(jobs: Queue[int32]): for job in range(8): jobs.put(job) jobs.close() jobs = Queue[int32](capacity=3) with group = TaskGroup(): group.start_soon(produce, jobs) group.start_soon(worker, "a", jobs) group.start_soon(worker, "b", jobs) ``` The parent owns the shape of the system: it decides how many workers to spawn and what capacity the queue has. The producer owns the decision to close the queue. Each worker owns only the job it is currently processing. Leaving the `with` block waits for the producer to finish and for each worker to drain the queue. ## Waiting On Queues, Tasks, And Deadlines Use `select(...)` when one operation may become ready through different source kinds: ```aura outcome = select(messages, task, 50ms) match own outcome: case SelectOutcome.Queue(index, received): print(index) print(received) case SelectOutcome.Task(index, result): print(index) print(result) case SelectOutcome.Deadline(index): print(index) case SelectOutcome.Cancelled: print("cancelled") ``` The Queue sources in one call share a payload type, and the Task sources share a result type. Missing categories use `None` in `SelectOutcome[Q, T]`. Cancellation wins; otherwise the lowest original argument index wins a tie. The runtime registers one composite wait and removes every loser when a source wins. A losing Queue remains unchanged. A non-repeatable Task right is consumed at entry and abandoned if another source wins. Selection uses the ordinary builtin call shown above. ## Waiting For Several Tasks Sometimes a program needs to wait on a batch of tasks at once. `wait_any` returns when the first one finishes: ```aura tasks: list[Task[int32]] = [] with group = TaskGroup(): tasks.append(group.start(double, 10)) tasks.append(group.start(double, 20)) match wait_any(tasks, timeout=1s): case WaitAny.Ready(index, value): print(f"task {index}: {value}") case WaitAny.Error(index, message): print(message) case WaitAny.TimedOut: print("timeout") case WaitAny.Cancelled: print("cancelled") ``` `wait_all` returns when every task has either produced a value or one has failed: ```aura match wait_all(tasks, timeout=1s): case WaitAll.Ready(values): for value in values: print(value) case WaitAll.Error(index, message): print(message) case WaitAll.TimedOut: print("timeout") case WaitAll.Cancelled: print("cancelled") ``` The `Error(index, message)` variant reports **which** task failed. That is usually more useful than a bare error. With repeatable `T`, the handles and observations remain reusable. With a non-repeatable but transferable `T`, either helper consumes the complete task list on its first attempt, including timeout, cancellation, and failure. `wait_any` deliberately abandons the observation rights of unchosen tasks. A Queue receive transfers one owned item; it never observes task-result storage. ## Cancellation Is Cooperative Calling `group.cancel()` signals child tasks. Tasks observe cancellation at **scheduler-aware waits**: `sleep`, queue sends and receives, task-result waits, socket waits, HTTP calls, and process waits. Compiler-inserted loop safepoints schedule sibling work but deliberately do not inspect cancellation, so a CPU-bound loop that must stop on request should check `cancelled()` itself: ```aura def ticker(): while not cancelled(): print("tick") sleep(100ms) with group = TaskGroup(): group.start_soon(ticker) sleep(350ms) group.cancel() ``` Cancellation is not an exception that lands at arbitrary points in the code. It is a request that tasks observe at well-defined boundaries. That makes cancelled code easy to reason about — and easy to test. Aura 0.3 runs task bodies on cooperative pinned workers on both maintained backends. The runtime uses the available parallelism reported by the host by default; provisional `AURA_WORKERS=` selects an explicit count. A task receives a stable assignment when it is spawned. Its coroutine stack never migrates, work is not stolen, and `yield_now()` yields only to runnable work on that worker. Every loop backedge includes an automatic scheduling check. Normal loop tails and `continue` take the check; `break` and `return` leave without it. This keeps a tight loop from freezing timers, queues, and sockets assigned to the same worker indefinitely, but one long loop body or long straight-line computation can still delay same-worker siblings. Ordinary tasks request a guarded 512 KiB coroutine stack, with an explicit per-child override available through the two `_with_stack` methods. Scheduler waits are event-driven: descriptors stay registered, deadlines are kept in a timer heap, and Queue, task-completion, and blocking-pool events notify the responsible worker directly. An idle worker sleeps until local work, an event, or a deadline becomes ready. The scheduler uses no periodic tick. Queue and Task handles are the cross-worker channels. Other captures and results remain owned `Transfer` values, so the model stays share-nothing. Cancellation and diagnostics remain per task. Scheduling, independent task completion, and printed-output order are unspecified; Aura exposes no worker identity or affinity API. Pinned workers enable multicore task execution; preemption and work stealing are unavailable, and speedup depends on the workload. Deep HTTP, TLS, and maintained Unix WebSocket library frames run on a bounded protocol-step service with deep native worker stacks. Each step is bounded and nonblocking; the child gets ownership of its protocol state back before observing cancellation or returning to reactor readiness waiting. Ordinary application tasks use the guarded 512 KiB default stack; protocol workers carry the deepest maintained third-party library frames. The protocol-step pool starts lazily and lives until the Aura process exits; there is no 0.2 shutdown or join call. File reads, resolver work, and listener binding continue through the generic blocking-I/O pool. For TLS assets, that generic pool reads the bytes and the protocol workers perform PEM parsing and rustls construction. The generic pool is a separate operational control. `AURA_BLOCKING_WORKERS=` requests an exact worker count; without it Aura derives a `2..=8` default from host parallelism with fallback `4`. `AURA_BLOCKING_QUEUE_CAPACITY=` optionally limits accepted jobs still waiting in the FIFO queue. It does not count running jobs or callers waiting for admission, and omitting it preserves an unbounded queue. A full bounded queue parks the Aura task without blocking its pinned worker. Cancellation or timeout before queue insertion prevents the host job from running. After insertion, Aura can stop waiting but cannot retract the host operation; its late result is discarded. The bound controls accepted pending backlog, not admission waiters or a stuck OS call, so unrelated blocking-I/O host work still cannot run until some worker returns when every worker is occupied. The runtime accepts larger task counts; 10,000 sleepers is the maintained memory-capacity bound. In the clean Mac14,9 Phase 5.10 measurement at `181204b`, three 100,000-sleeper plus 1,000-timer runs peaked at 1,170,735,104, 1,921,531,904, and 2,001,305,600 bytes of whole-process RSS. Two runs exceeded the proposed 1.5 GiB bound. On this host, one 16 KiB resident page for each of the 101,000 stackful children alone requires 1,654,784,000 bytes before scheduler metadata or the root runtime. The lower Phase 5.9 result depended on macOS memory compression. The 10,000-sleeper, standalone-timer, idle-CPU, starvation, and mandatory multicore gates all pass. MIR execution checks every loop backedge and yields every 8 backedges. Native concurrent programs use a function-local 4,096-iteration fuel budget, and sequential native programs remove checks that cannot have a sibling to schedule. Interleaving and ready-task order remain unspecified. `yield_now()` adds an explicit cooperative scheduling point between application-chosen chunks: ```aura def crunch(): mut chunk: int32 = 0 while chunk < 100: process_chunk(chunk) chunk += 1 yield_now() ``` It gives runnable siblings an opportunity to proceed, but does not sleep, promise that another task runs, or check cancellation. Use `cancelled()` when the task must also respond to a cancellation request. ## The Shape Worth Copying Good Aura concurrency tends to look the same across programs: - one `with TaskGroup()` per concurrent operation - queues owned by the parent, closed by the producers - task results inspected through `TaskResult`, `wait_any`, or `wait_all` - long CPU loops that check `cancelled()` when cancellation matters and use explicit yields when a particular chunk boundary should schedule siblings - no detached background work; Aura 0.3 exposes no detached task form If you can say, for each child task, which scope created it and which scope waits for it, the program is usually on the right track. Reference: [Concurrency](/manual/concurrency). ## Source: docs/learn/data-modeling.md # Shaping Data Most programs get easier to read once the data has names. A loose bag of strings and integers becomes a `Job` with an `id`, a `queue`, and an `attempts` counter. A value that is "sometimes a number and sometimes an error" becomes a `Result` with two variants. Shared behaviour lives on the type. This chapter introduces Aura's two data shapes — **classes** and **enums** — together with **methods**, **copy classes**, and **generics**. It is deliberately not a feature checklist. The through-line is how to decide which shape fits your domain. ## When To Use What A useful first cut: - Use a **class** when every field is present at the same time. - Use an **enum** when exactly one variant is present at a time. - Use a **method** when behaviour belongs to the type. - Use a **free function** when behaviour coordinates several types. The rest of the chapter fills those decisions in. ## Start With A Class Imagine a small job runner. A job has an identifier, a queue name, and an attempt count: ```aura class Job: id: int32 queue: str attempts: int32 = 0 ``` Construct an instance with named fields: ```aura job = Job(id=42, queue="image") ``` Fields can have defaults. The caller above did not supply `attempts`, so it starts at `0`. By default, classes are **move types**. A bare class parameter borrows; write `own` to transfer ownership: ```aura def consume(job: own Job): print(job.id) job = Job(id=42, queue="image") consume(job) # job has been moved into consume; using it again is a compile error. ``` When a helper only needs to look at a job, borrow it: ```aura def describe(job: Job) -> str: return job.queue + "#" + job.id.to_string() ``` The caller keeps the value and can use it again. The call site writes `describe(job)`; Aura reads the borrow form from the parameter type. ## Add Methods Methods are functions declared inside a class. The **receiver** — how `self` is named in the signature — says what the method is allowed to do. ```aura class Job: id: int32 queue: str attempts: int32 = 0 def bump(mut self): self.attempts += 1 def label(self) -> str: return self.queue + "#" + self.id.to_string() ``` Use it: ```aura mut job = Job(id=42, queue="image") job.bump() print(job.label()) ``` Receiver forms: | Receiver | What it can do | | --- | --- | | `self` | Read fields without taking ownership; this is the default spelling. | | `self` | Explicit synonym for shared `self`. | | `mut self` | Mutate fields on a mutable receiver. | | `own self` | Consume the instance. | | no receiver | Associated method called on the type, not an instance. | A borrowed method cannot move an owned field out of `self`. When the field type supports cloning, clone when you need to return an owned copy: ```aura class User: name: str def name_copy(self) -> str: return self.name.clone() ``` Returning `self.name` directly would move the `str` through a shared borrow, which the compiler rejects. The clone makes the intention explicit and the reader does not have to guess. An associated method is called on the type itself — useful for constructors and factories: ```aura class Counter: value: int32 = 0 def zero() -> Counter: return Counter() ``` ```aura counter = Counter.zero() ``` ## Copy Classes Some records are so small that treating them as move values is more ceremony than it is worth. When every field is itself a copy type, declare the class `copy class`: ```aura copy class Offset: x: int32 y: int32 ``` Copy classes duplicate on assignment: ```aura a = Offset(x=1, y=2) b = a print(a.x) print(b.x) ``` This is not a way to opt out of ownership when it feels inconvenient. Reach for `copy class` when duplication is part of the type's nature — coordinates, simple numeric measurements, identifiers made entirely of copyable fields. ## Model Alternatives With Enums An enum describes a value that is exactly one of several shapes. A job in flight, for instance, is always in one of four states: queued, running, done, or failed. ```aura enum JobState: Queued Running(worker: str) Done(duration: Duration) Failed(message: str) ``` Construct a variant by naming it: ```aura state = JobState.Running(worker="worker-a") ``` `match` then inspects the variant exhaustively: ```aura def render_state(state: JobState) -> str: return match state: case JobState.Queued: "queued" case JobState.Running(worker): "running on " + worker case JobState.Done(_duration): "done" case JobState.Failed(message): "failed: " + message ``` Two details are worth noticing. `match state` inspects the enum without taking ownership, which is important because `state` is itself a `JobState`. And the `_duration` name uses the leading underscore convention for a pattern binding that the body does not read. When each state carries different data, an enum almost always reads better than a class with many optional fields. ## Combine Classes And Enums A class can own an enum, and often should. This shape — a stable record with a changing state — is one of the cleanest patterns in Aura. ```aura class TrackedJob: job: Job state: JobState = JobState.Queued def mark_running(mut self, worker: own str): self.state = JobState.Running(worker=worker) def mark_failed(mut self, message: own str): self.state = JobState.Failed(message=message) ``` The fields that never change live on the class. The field that does change is an enum, so the compiler can help make sure every transition is handled. ## Generic Data Classes and enums can be parameterised by type. `Box[T]` holds some `T`; `Load[T]` represents a value that has either arrived, is still absent, or has failed: ```aura class Box[T]: value: T enum Load[T]: Ready(value: T) Empty Failed(message: str) ``` Generic types let you write utility data structures without giving up the type of the stored value. [Generics And Traits](/manual/generics-and-traits) in the Manual covers the details. ## Design Notes Three habits keep Aura data types clean: - **Prefer small classes with meaningful fields.** A class with ten unrelated fields is often two classes waiting for names. - **Prefer enums for domain states.** `JobState.Failed(message=...)` is harder to misuse than a `"failed"` string plus a maybe-empty error field. - **Prefer methods for type-local behaviour.** A function that reads one class's fields usually belongs to that class. A function that coordinates several types is usually a free function. The next chapter takes the same ideas into Aura's standard collections — where the classes and enums we just built start to form programs. Reference: [Classes](/manual/classes), [Enums And Pattern Matching](/manual/enums-and-match). ## Source: docs/learn/ffi.md # Calling A Small C API Aura's FFI v0 is for small, reviewed bindings to trusted C symbols that are already visible in the running process. It deliberately does not expose raw pointers or arbitrary library loading. Start with a package because standalone files cannot opt in to FFI: ```toml [package] name = "ffi_getpid" version = "0.1.0" edition = "2026" allow_ffi = true ``` Then declare a bodyless C function and call it directly: ```aura public extern "C" def getpid() -> int32 def main() -> int32: print(getpid() > 0) return 0 ``` On a Unix-family host, run the maintained example: ```bash aura run --backend mir examples/packages/ffi_getpid/src/main.au aura run --backend direct examples/packages/ffi_getpid/src/main.au ``` Both commands print `true`. The manifest opt-in is a review boundary: it says that the package contains native declarations whose correctness Aura cannot prove. ## The Safe Surface Is Small Use fixed-width scalars (`int32`, `uint64`, `float32`, and their supported peers) for ordinary C values. `int` is accepted as the exact `int64` alias, but an explicit width makes an ABI declaration easier to review. A bare `str` parameter passes temporary UTF-8 bytes and a byte length. A bare `list[uint8]` passes read-only bytes and a length. `mut list[uint8]` uses a same-length scratch buffer for fixed-length copy-in/out. Empty views use a null pointer with length zero. The C function must not retain those pointers, and the string view is not promised to end in a NUL byte. Use an opaque handle when C owns an object whose layout Aura should not see: ```aura public extern "C" opaque class Handle public extern "C" def acquire() -> Handle public extern "C" def inspect(handle: Handle) -> int32 public extern "C" def close(handle: own Handle) -> None ``` The bare parameter shares the pointer for one synchronous call. `own Handle` consumes it. Opaque handles cannot be cloned or sent to another Aura task, and a binding must call the appropriate native close/free function. ## What Aura Does Not Promise The compiler checks the Aura declaration, not the native implementation. A wrong C signature, retained temporary pointer, or out-of-bounds native write can corrupt or terminate the process. Native aborts, signals, and unwinds are not translated into Aura failures. Calls are synchronous and occupy their current Aura worker. The complete ABI table, manifest dependency-report rule, diagnostics, and backend contract are in [Foreign Function Interface (FFI) v0](/manual/ffi). ## Source: docs/learn/from-python.md # Aura For Python Developers Most of what you know transfers. Indentation, `def`, `class`, f-strings, comprehensions, keyword arguments, `for x in items` — all of it works the way you expect. This chapter is about the parts that do not, so the compiler stops surprising you by the end of the page. ## There Is No `if __name__ == "__main__"` A file with statements at the top level *is* the script. It runs top to bottom: ```aura langs = ["python", "aura"] for lang in langs: print(f"hello, {lang}") ``` When you want a real entry point — an exit code, a program you will compile — write `main`: ```aura def main() -> int32: print("hello") return 0 ``` The one rule to remember: a file picks a side. It either has top-level statements or an explicit `main`, never both. Declarations like `class` and `def` are fine alongside either. ## Bindings Are Immutable Unless You Say Otherwise This is the first error most Python developers hit: ```aura def main(): total = 0 total = total + 1 # error: cannot assign to immutable binding `total` ``` Add `mut` and it works: ```aura def main(): mut total = 0 total = total + 1 ``` `mut` is not a type — it is permission to rebind or mutate. You will see it in three places: local bindings, parameters that a function may change, and methods that modify their object. Top-level entry scripts use the same rule. A `mut` binding and its later plain or compound assignments belong to the script's shared local environment: ```aura mut count = 0 count = count + 1 count += 1 print(count) # 2 ``` A new bare top-level binding such as `limit = 3` declares an immutable module constant. Module constants initialize before top-level entry statements, even when the two categories are interleaved in the file. Use `mut limit = ...` when the value must be computed from an earlier top-level script local. ## Values Have Owners Python passes references around and a garbage collector eventually cleans up. Aura tracks a single owner for every value, and the *signature* tells you what a function does to its argument. ```aura def shout(name: str) -> str: # shared: reads it, you keep it return name.to_upper() def add_tag(tags: mut list[str], tag: own str): # mut: changes yours tags.append(tag) # own: takes it def consume(name: own str) -> int64: # own: it is theirs now return name.len() ``` Three capabilities, and that is the whole model: | Spelling | The callee can | You afterwards | | --- | --- | --- | | `name: str` | read it | still own it | | `name: mut str` | change it in place | still own it, changed | | `name: own str` | do anything, including keep it | no longer have it | Calls look like Python — no sigils, no `&`: ```aura label = "aura" print(shout(label)) print(shout(label)) # fine, shout only reads ``` Give a value away and the compiler holds you to it: ```aura n = consume(label) print(label) ``` ```text error[AU3001]: use of moved value `label` = related owner.au:6:17: value moved here = help: pass shared access when ownership is not needed, or call `.clone()` at the move site when an independent value is required ``` Read that as the compiler asking a question: did you mean to hand it over, or did you mean to share it? Copy small things freely — numbers, booleans, durations are copied, not moved. Everything else (strings, collections, class instances, files) moves. ## Classes Have No `__init__` An Aura class is fields and methods. There is no initializer, and no `self` assignment ceremony — you construct with keyword arguments, and fields may declare defaults: ```aura class Account: owner: str balance: float64 currency: str = "USD" ``` ```aura account = Account(owner="ada", balance=0.0) ``` When you want named construction — the thing `__init__` and `@classmethod` give you — write a function on the class that takes no `self` and returns one: ```aura class Account: owner: str balance: float64 currency: str = "USD" def new(owner: own str) -> Account: return Account(owner=owner, balance=0.0) def opening(owner: own str, deposit: float64) -> Account: return Account(owner=owner, balance=deposit) ``` ```aura fresh = Account.new("ada") mut acct = Account.opening("grace", 100.0) ``` These are "associated functions": called through the class name, free to validate, compute, or pick defaults. You can have as many as you need, which is more than Python gives you without `@classmethod` gymnastics. ### Methods Say What They Do To `self` The receiver follows the same three capabilities as parameters: ```aura def label(self) -> str: # reads return f"{self.owner}: {self.balance} {self.currency}" def deposit(mut self, amount: float64): # modifies self.balance += amount def into_balance(own self) -> float64: # consumes return self.balance ``` ```aura acct.deposit(25.0) print(acct.label()) final = acct.into_balance() # acct is gone after this ``` A bare `self` cannot mutate — the compiler will tell you to write `mut self`. And a method named `close` is special: it makes the class a managed resource for `with` blocks, so pick another name unless that is what you want. ### There Is No Inheritance `class Dog(Animal):` does not parse. Aura uses traits for shared behavior and composition for shared data — if you reach for a base class, define a trait with the methods and implement it for each type. ## Failure Is A Return Value There are no exceptions and no `try`/`except`. A function that can fail says so in its type: ```aura def parse_port(text: str) -> Result[int64, str]: match parse_int64(text): case Result.Ok(port): if port > 65535: return Result.Err("port out of range") return Result.Ok(port) case Result.Err(_): return Result.Err(f"not a number: {text}") ``` Callers must handle both sides — there is no invisible propagation: ```aura match parse_port("8080"): case Result.Ok(port): print(f"listening on {port}") case Result.Err(message): print(f"bad config: {message}") ``` `Option[T]` plays the role of `None`-or-a-value, and `try` propagates an error to the caller when your own function returns a `Result`. See [Results, Options, And `try`](/learn/results-and-options). ## Types Are Static, But Locals Infer Annotations are required where a contract crosses a boundary — parameters and return types — and inferred everywhere else: ```aura def total(prices: list[float64]) -> float64: mut sum = 0.0 # inferred float64 for price in prices: sum += price return sum ``` Three differences worth knowing up front: - **A missing parameter type is a parse error**, not a dynamic parameter. `def f(x):` does not compile. - **Numeric types never convert implicitly.** Passing an `int32` where `int64` is expected is an error; cast with `as int64` or `.to_float()`. Unsuffixed integer literals are `int64`, floats are `float64`. - **Generics are explicit**: `list[str]`, `dict[str, int64]`, `Option[int64]`, and type parameters are declared, as in `def first[T](values: list[T]) -> Option[T]`. ### If It Returns A Value, Declare The Type A function with no `->` returns nothing. That is fine when the body really returns nothing, and a bare `return` for an early exit is fine too: ```aura def greet(name: str): print(f"hi {name}") def early(flag: bool): if flag: return print("no") ``` The moment the body returns a *value*, the signature has to say so. Aura does not infer it from the body: ```aura def double(n: int64): return n * 2 ``` ```text error[AU2002]: return type mismatch: expected `None`, found `int64` --> ret_bad.au:2:5 | 2 | return n * 2 | ^ ``` Read `-> None` as the default that was there all along. The fix is to write the type you meant: ```aura def double(n: int64) -> int64: return n * 2 ``` Coming from Python this feels like extra typing for about a day, and then it starts reading as documentation: every signature tells you what goes in and what comes back without opening the body. ## Things That Will Surprise You **Integer `/` is rejected.** Python 3 made `/` true division; Aura makes you choose, because silently truncating is the older bug: ```text error[AU2003]: integer `/` is not supported; use `//` for floor division, or call `.to_float()` on both operands for true division ``` **There is no truthiness.** `if values:` fails — conditions are `bool` and nothing else. Write `if values.len() > 0:` or `if value == None:`. **Strings are not indexable.** `s[0]` does not work; a `str` is a sequence of Unicode scalar values, `len()` counts those, and slicing (`s[1:4]`) gives you an owned copy. Use `s.split("")` style operations or slices instead of character indexing. **`is` does not exist.** Use `== None` for optionals; there is no identity comparison. **Reading a non-copy element out of a list by index is rejected**, because it would move a value out of a collection you still own. Use `values.get(index)`, which hands you an `Option` containing a clone. **Top-level bindings live in module storage** and cannot be moved out of it. If you want to consume a value with an `own` method, do it inside a function. **Module state is immutable.** Constants at module level are fine; `mut` at module level is not. Mutable state belongs to some owner — usually `main`. ## Where To Go Next - [Values, Moves, And Borrows](/learn/ownership-and-borrowing) — the ownership model in depth, with the errors you will meet and how to fix each one. - [Shaping Data](/learn/data-modeling) — classes, enums, traits, and methods. - [Testing](/learn/testing) — `aura test`, assertions that show their values. - [The Manual](/manual/) — the normative rules when you need the exact contract. ## Source: docs/learn/install-and-run.md # Getting Aura Running Aura release archives ship a command-line tool called `aura` plus its private native runtime under `lib/aura`. The tool parses, type-checks, runs, and builds Aura source files, and it also serves as the entry point for editor tooling. Aura 0.3 is a technical preview. This chapter covers both a release archive and a source checkout. ## Install A Release Archive The fastest installation path supports Linux x64, macOS x64, and macOS arm64: ```bash curl -fsSL https://johnolafenwa.github.io/Aura/install.sh | sh ``` The script verifies the release checksum and installs the compiler plus its native runtime under `~/.local`. If `~/.local/bin` is absent from `PATH`, the installer prints the exact export command. Set `AURA_INSTALL_PREFIX` before running the command to choose another prefix. After Aura is installed, update the compiler and its bundled native runtime with: ```bash aura upgrade ``` For a manual installation, download the archive for a supported host, extract it, and keep its directory layout intact: ```text aura-v0.3.2-preview-/ ├── bin/aura ├── lib/aura/ ├── libaura_compiler.a └── native-link-args.json └── examples/ ├── basic_addition.au └── agents/retrying_network_worker.au ``` Add the extracted `bin` directory to `PATH`. Running and checking programs need no Rust installation. Building a native executable needs a host C compiler because `aura` performs the final host link itself. Aura does not publish a native Windows archive. Windows 11 users can run the Linux x86-64 release inside Ubuntu on WSL 2. See the detailed [platform installation guides](/install/) and the repository's supported-platform matrix before relying on an unlisted host. ## Choose Your Platform Guide Use the guide for the system where the `aura` command will run: - [Install on macOS](/install/macos) covers Apple silicon and Intel Macs, persistent `PATH` setup, Xcode command-line tools, and verification. - [Install on Linux](/install/linux) covers Ubuntu 24.04 and compatible x86-64 glibc systems, required packages, and the native build toolchain. - [Install on Windows with WSL 2](/install/windows-wsl) covers Ubuntu setup, Linux filesystem placement, Aura installation inside WSL, and remote VS Code. - [Install the VS Code extension](/install/vscode) covers Marketplace, Open VSX, manual VSIX, WSL, compiler paths, and editor verification. ## Build From Source Contributors building Aura itself need the pinned Rust toolchain and a host C compiler. - **Rust**: install through [rustup](https://rustup.rs). `rust-toolchain.toml` selects Rust 1.95.0. - **C compiler**: macOS provides one through the Xcode command-line tools (`xcode-select --install`). On Linux and Ubuntu under WSL 2, `build-essential` supplies the supported host toolchain. Native Windows source builds remain outside the distribution matrix. ## Build The Compiler Clone the repository and build a release binary: ```bash git clone https://github.com/johnolafenwa/Aura.git cd Aura cargo build --release -p aura ``` The release build lives at `./target/release/aura`. In a source checkout, `aura build` can use the sibling Cargo-built runtime. A distributed archive instead uses the runtime installed beside the executable. Put `aura` on your path so the rest of the commands in this book read naturally: ```bash export PATH="$PWD/target/release:$PATH" aura --version ``` Preview builds identify both their channel and source commit, for example `aura 0.3.2-preview (0123456789ab)`. Source-checkout builds identify their channel as `aura 0.3.2-dev (0123456789ab)`. On Unix shells, consider adding that export to your shell profile. ## Install The VS Code Extension Install the CLI first and confirm that VS Code will be able to find it: ```bash command -v aura aura --version ``` Install **Aura Programming Language** from the Visual Studio Marketplace, or run this command from a terminal where `code` is available: ```bash code --install-extension JohnOlafenwa.vscode-aura-lang ``` Open an `.au` file and confirm that the language mode reads **Aura**. Syntax highlighting is bundled with the extension. Diagnostics, completion, hover, definitions, and symbols come from the compiler server that the extension launches through `aura lsp`. On Windows with WSL 2, open the project from the Ubuntu terminal with `code .`. In the resulting **WSL: Ubuntu** window, select **Install in WSL: Ubuntu** for the Aura extension. The extension and `aura` CLI must both run inside WSL. The [complete VS Code installation guide](/install/vscode) also covers Open VSX, manual VSIX installation, custom compiler paths, and troubleshooting. ## Your First Program Save the following as `hello.au`: ```aura print("hello from aura") ``` Run it: ```bash aura run hello.au ``` You should see: ``` hello from aura ``` The program is a **top-level script**. Aura runs the file line by line and exits when it reaches the end. ## Using `main` For programs that want an explicit entry point, define a function named `main`: ```aura def main() -> int32: print("ready") return 0 ``` `main` takes no parameters. It returns either `int32` or `None`. A returned `int32` becomes the process exit code when the program is built as a native binary. A file may use script-style top-level statements **or** define `main`, but not both. ## The CLI At A Glance The commands you will use day to day are: | Command | What it does | | --- | --- | | `aura run file.au` | Parse, type-check, and execute the program. | | `aura check file.au` | Parse and type-check without running. | | `aura check --format json file.au` | Emit schema-versioned structured diagnostics for tooling. | | `aura build -o path file.au` | Compile a standalone native binary to `path`. | | `aura ast file.au` | Print the parsed syntax tree. | | `aura mir file.au` | Print the lowered intermediate representation. | | `aura analyze file.au` | Emit compiler-backed analysis used by editor tooling. | | `aura complete --line N --character M file.au` | Emit completion items at a source position. | | `aura deps update [name]` | Refresh git dependencies and rewrite `Aura.lock`. | Use `aura help` for the full list and `aura --version` to confirm the preview channel and exact source revision you are running. `aura run` defaults to the MIR runtime for a fast edit-run loop. Use `--backend direct` to require native execution, or `--backend auto` to prefer native execution while visibly falling back to MIR when direct execution is unavailable. ## Building A Native Binary ```bash aura build -o ./hello hello.au ./hello ``` `aura build` defaults to `auto`, which first tries direct native emission and may fall back to a standalone launcher containing embedded MIR plus the MIR runtime. The resulting binary does not need the original `.au` source at runtime; it does still need the host C compiler to produce the artifact. Use `--backend direct` when fallback is unacceptable. The [Running And Shipping](/learn/native-builds) chapter covers when to pick `run` versus `build` and what each path gives you. ## When Something Goes Wrong Aura's error messages usually point at the exact place in the source where the compiler or runtime found the problem: ``` error[AU4002]: integer value `2147483648` does not fit in `int32` --> overflow.au:3:14 | 3 | c: int32 = a + b | ^ ``` The bracketed `AU####` identifier is stable. The `-->` line names the file, line, and column, and the caret points at the offending expression. Related spans, guidance, and safe source edits follow when available. A program with a checker error will not run; a program with a runtime error prints the diagnostic and exits with a non-zero status. Use `--format json` with `check`, `run`, or `build` when a tool needs the same fields without parsing this human layout. Runtime diagnostics also carry typed `call_frames` (innermost first) and `task_ancestry` (youngest child first); both arrays are present in every schema-version-1 diagnostic, including as `[]` when no runtime frames apply. ## Next The next chapter builds a small program that counts and classifies values, and in doing so introduces bindings, functions, control flow, and `match`. ## Source: docs/learn/io-process-networking.md # Talking To The World Programs eventually need to speak to something outside themselves — a file, a subprocess, a socket, a supervised service. Aura exposes that surface through four built-in modules: `io` for standard streams, `fs` for files and directories, `process` for subprocesses and supervisors, and `net` for sockets, HTTP, and WebSockets. The APIs in these modules share a shape. Operations that can fail return `Result`. Resources cleaned up by the runtime are meant to live inside a `with` block. Waits that might block indefinitely accept a `timeout` argument and tell the caller explicitly when that timeout fires. Everything works together with `match`, `try`, `with`, and `TaskGroup`. ## Files: Read, Parse, Report The simplest filesystem API is one-shot: ```aura import fs path = "tmp.txt" try fs.write_string(path, "limit=42\n") match fs.read_to_string(path): case Result.Ok(text): print(text.trim()) case Result.Err(error): print(error) ``` One-shot `fs.read_to_string` and `fs.read_bytes` are capped at 256 MiB. The same cap applies to the remaining contents read by `fs.File.read_all()` and `fs.File.read_bytes()`. An accidental whole-file read against a very large log fails at the cap. Larger files need a host helper or pre-splitting because Aura 0.2 has no incremental file-read member. ```aura import fs import io def copy_text(source: str, dest: str) -> Result[None, io.Error]: with input = try fs.open(source): text = try input.read_all() with output = try fs.create(dest): try output.write_all(text) try output.flush() return Result.Ok(None) ``` `fs.File` is a resource. Put it in a `with` block and cleanup is the compiler's problem, not yours. The `with` ends automatically on both normal and error paths. ## Standard Streams `print(value)` renders a value and adds a newline. When a program needs more control — writing without a newline, flushing for a prompt, reading a line from standard input — the `io` module has it: ```aura import io try io.write("name> ") try io.flush() match io.read_line(): case Result.Ok(Option.Some(line)): print("hello " + line.trim()) case Result.Ok(Option.None): print("end of input") case Result.Err(error): print(error) ``` `io.read_line()` returns `Result[Option[str], io.Error]`. The `Option` is `None` at end of input; the `Result` captures I/O failures. Both are in the type, and a caller that wants to treat them differently can. ## Processes: No Shell By Default `process.run` executes a subprocess from an argument list. There is no shell interpretation, so the arguments are not re-split and there are no quoting hazards. The return value is a `process.Completed` record. ```aura import process completed = try process.run(command=["/bin/echo", "aura process"], stdout=process.pipe(), stderr=process.pipe(), timeout=1s, group=true) try completed.check() print(completed.stdout().trim()) ``` Two things in that call site are worth explaining. `stdout=process.pipe()` captures the subprocess's output so the parent can read it; `stderr=process.pipe()` does the same for standard error. `group=true` places the child in its own process group on Unix hosts, so termination reaches the leader and all descendants. Omitting `timeout` supplies no caller deadline through an internal absence marker. An explicit negative Duration is not that marker: invalid timeout or deadline values return `process.Error.Io(io.Error.InvalidInput)`. When a child writes bytes that are not valid UTF-8, use `stdout_bytes()` and `stderr_bytes()`: ```aura bytes = completed.stdout_bytes() print(bytes.len()) ``` ## Interacting With A Child `process.start` returns a `process.Child` you can talk to while the child is running: ```aura import process child = try process.start(command=["/bin/cat"], stdin=process.pipe(), stdout=process.pipe(), stderr=process.pipe(), group=true) match child.stdin(): case Option.Some(pipe): try pipe.write_all("hello\n") pipe.close() case Option.None: print("stdin was not piped") match child.stdout(): case Option.Some(pipe): text = try pipe.read_all() print(text.trim()) case Option.None: print("stdout was not piped") match child.wait(timeout=1s): case process.Wait.Exited(status): print(status) case process.Wait.TimedOut: child.kill() case process.Wait.Cancelled: child.terminate() case process.Wait.Failed(error): print(error) child.close() ``` `child.stdin()`, `child.stdout()`, and `child.stderr()` return `Option[process.Pipe]` so the program can tell the difference between "the stream was not piped" and "the stream is available." ## Supervisors When a program needs to manage several named subprocesses — start them, observe their lifetimes, restart them according to a policy — use a `process.supervisor`: ```aura import process with supervisor = process.supervisor(): try supervisor.start(name="worker", command=["/bin/sleep", "1"], restart=process.RestartPolicy.Never, group=true) match supervisor.wait(timeout=2s): case process.SupervisorWait.Event(event): print(event) case process.SupervisorWait.TimedOut: print("no event") case process.SupervisorWait.Cancelled: print("cancelled") ``` Supervisor names are unique within a supervisor. Starting a second child with the same name returns an error and preserves the existing child. Leaving the `with` block stops every child the supervisor still manages. ## Networking: TCP Network APIs return `Result[..., io.Error]`. Waits accept `timeout=...`. Listeners, streams, and other resources belong in `with` blocks. ```aura import net with listener = try net.listen("127.0.0.1:0"): address = try listener.local_addr() with stream = try net.connect_timeout(address, timeout=1s): try stream.write_all("ping\n", timeout=1s) try stream.shutdown_write() ``` Hostname lookup and blocking connect syscalls are sent to the generic blocking-I/O pool, so they do not freeze sibling Aura tasks. The `1s` timeout above is one shared budget for queue admission, DNS, and every candidate address. Task-group cancellation stops waiting promptly. Before pool acceptance it prevents submission; after acceptance, the host resolver cannot generally be interrupted and its eventual result is discarded. Operators may set `AURA_BLOCKING_WORKERS` to an exact positive worker count and `AURA_BLOCKING_QUEUE_CAPACITY` to a positive bound on accepted pending jobs. The absent worker setting derives `2..=8` workers from host parallelism, with fallback `4`; the absent queue setting is unbounded. Full-queue admission is FIFO and scheduler-aware. A queue bound limits accepted pending backlog, not admission waiters, and cannot guarantee unrelated blocking-I/O progress while every worker remains stuck. A live listener or stream is not `Transfer`, so it cannot be captured by a new task. The task that creates a listener keeps it and its accepted streams; it may use an ordinary helper on that same task to process a connection: ```aura import io import net def handle(stream: own net.TcpStream) -> Result[None, io.Error]: with conn = stream: line = try conn.read_line(timeout=5s) match line: case Option.Some(text): try conn.write_all(text, timeout=5s) case Option.None: pass return Result.Ok(None) ``` When a server itself should run as a child task, let that child create the listener. A copy `Queue[str]` handle can cross the boundary so the child can publish its bound address to the parent; the live listener never leaves its owning task. The `read_line` returns `Result[Option[str], io.Error]` for the same reason `io.read_line` does: the client might close cleanly, and the program might have to decide what that means. ## HTTP And WebSockets HTTP client helpers return `net.HttpResponse`: ```aura import net headers: dict[str, str] = {} response = try net.http_request_text_timeout(method="GET", url="http://127.0.0.1:8080/", body="", headers=headers, timeout=2s) print(response.status()) ``` HTTP servers use `net.http_listen` to create an `HttpListener`; accepting a connection returns an `HttpExchange` carrying request data and the methods to send a response. WebSocket APIs follow the same resource style: create or accept a socket, send and receive text or bytes, then close. See [Network Module](/manual/network) for the full surface. ## The Common Shape Most system-facing Aura code has the same outline: ```aura import fs import io def load(path: str) -> Result[str, io.Error]: with file = try fs.open(path): text = try file.read_all() return Result.Ok(text) ``` - `import` the module. - Call an API that returns `Result`. - Use `try` when the caller should receive the failure. - Use `match` when the current function makes a decision. - Put resources in `with`. - Pass a `timeout` to any wait that should not block forever. Reference: [Filesystem Module](/manual/filesystem), [Process Module](/manual/process), [Network Module](/manual/network), [I/O Module](/manual/io). ## Source: docs/learn/modules-and-packages.md # Organizing Code A single-file program is a fine way to start. At some point, though, helper types want a home, public APIs want to be marked as such, and dependencies want to be named somewhere the compiler can read them. That is what Aura's module and package system is for. This chapter walks from a single file to a package with dependencies. ## Local Modules Say the program has some math helpers. Move them into their own file: ``` helpers/math.au ``` ```aura public def double(value: int32) -> int32: return value * 2 def internal(value: int32) -> int32: return value + 1 ``` From another file, import the module and call its public names: ```aura import helpers.math print(helpers.math.double(21)) ``` Only declarations marked `public` are visible outside the file. `internal(...)` may be called from within `helpers/math.au`, but importers cannot reach it. This is not a convention; the compiler enforces it. ## Two Styles Of Import `import helpers.math` brings the whole module namespace in, so calls read `helpers.math.double(21)`. When a single name is the local concept, use `from ... import ...` to pull the name directly: ```aura from helpers.math import double print(double(21)) ``` Both styles are useful. A quick rule: when a file imports many names from a module, keep the module prefix; when the imported name is the central concept of the file, drop it. ## Choosing Local Import Names Use `as` when the complete module path is too long for repeated use or when two modules export the same concise name: ```aura import helpers.math as integer_math from helpers.counter import Counter as ReadableCounter print(integer_math.double(21)) counter = ReadableCounter(value=2) ``` The alias is the only local name introduced by that import entry. It changes how the importer spells the name, while the declaration keeps its original module identity, type, visibility, and behavior. A from-import may mix direct and aliased entries: ```aura from helpers.math import double as twice, empty ``` Both styles also preserve the full callable contract. If a public generic helper performs a clone-producing operation, its inferred clone-safety obligation follows the import and is checked where the helper is specialized. ## Packages A **package** is a directory with an `Aura.toml` manifest and usually a `src/` directory: ``` app/ ├── Aura.toml └── src/ └── main.au ``` ```toml [package] name = "app" version = "0.1.0" edition = "2026" ``` Manifest package names must be valid Aura identifiers — letters, digits, and underscores. Hyphenated names are rejected because `import my-util.math` would not parse as an Aura expression. Commands that take a source file inside a package infer the nearest package root automatically. `aura run src/main.au` from inside `app/` works the same as running it from the repo root. ## Dependencies Dependencies live under `[dependencies]` in the manifest: ```toml [dependencies] util = { path = "../util" } jsonx = { git = "https://github.com/example/jsonx.git", branch = "main" } ``` - **Path dependencies** point at another local package. Good for related crates in the same repository or workspace. - **Git dependencies** point at a git repository. Optional `rev`, `tag`, or `branch` selectors pin the version; without one, the dependency defaults to `branch = "main"`. Both shapes are pinned by exact revision (or canonical path) in `Aura.lock`. Repeat runs resolve the same code until you ask for an update: ```bash aura deps update aura deps update util ``` ## Package Names Are Import Roots A dependency is imported by its package name: ```aura import util.math print(util.math.double(10)) ``` Because the import syntax uses the package name directly, a manifest that declares `name = "my-app"` is rejected: `import my-app.foo` would try to subtract `app.foo` from `my`. ## Workspaces When several packages live together, a **workspace** manifest coordinates them: ```toml [workspace] members = ["app", "util"] ``` Each member is still an ordinary package with its own `Aura.toml`. The workspace root owns the shared `Aura.lock`. ## A Good Module Boundary A module boundary should usually hide representation and expose behaviour: ```aura public class Counter: value: int32 = 0 public def inc(mut self): self.value += 1 public def get(self) -> int32: return self.value ``` Callers see `Counter.inc()` and `Counter.get()`; they never reach `.value` directly. The internal representation is free to change in ways the external contract does not. Keep helper functions private unless another module genuinely needs them. A smaller public surface is easier to keep stable. ## Notes On Editor Tooling `aura analyze` and `aura complete` can read an editor buffer through `--stdin` while resolving imports relative to the file being edited. These stdin-mode commands deliberately do not write `Aura.lock`. Lockfile changes happen only when you `check`, `run`, `build`, or explicitly `deps update`. Reference: [Packages](/manual/packages), [CLI And Tooling](/manual/cli-and-tooling). ## Source: docs/learn/native-builds.md # Running And Shipping Aura has two execution paths: the MIR runtime behind `aura run`, and the native code generator behind `aura build`. They target the same language surface and are exercised by the same test suite, but they fit slightly different moments in a project. ## `aura run` `aura run` parses, type-checks, lowers the program to Aura's mid-level intermediate representation, and executes that representation. It is fast to start and shares code paths with the rest of the tooling, so compiler diagnostics, traces, and editor integrations behave the same as the code you are editing. Use `run` for: - iterating quickly while writing a program - examples, smoke tests, and scratch files - anything that lives in a test runner or script ## `aura build` `aura build` compiles the program to a standalone native binary: ```bash aura build -o ./app examples/basics/main_function.au ./app ``` The resulting binary is self-contained: it does not need the original `.au` source to run, and it does not re-invoke the compiler at launch. The build pipeline still needs the host C compiler to produce the artifact. Use `build` when: - you want a standalone executable you can ship or deploy - you are validating native behaviour for a controlled deployment on the direct backend - a program's runtime characteristics are part of what you are testing ## Backends ```bash aura build --backend auto -o ./app app.au aura build --backend direct -o ./app app.au ``` `auto` is the default. It first tries the direct native backend and may fall back to a standalone launcher that embeds checked MIR and the MIR runtime. Selecting `direct` explicitly forbids fallback and is useful when CI must prove direct emission remains available. ## Runtime Diagnostics Built binaries embed source and frame metadata for runtime failures. A simple failure at minimum renders its stable code, file, line, and caret: ``` error[AU4003]: list index `10` is out of bounds for length `3` --> app.au:5:20 | 5 | x: int32 = values[10] | ^ ``` Arithmetic traps, list bounds errors, recursion-limit failures, and resource cleanup paths are expected to behave identically between `aura run` and the native binary. If you observe a difference, it is a bug worth reporting. The frame data is captured once at the trap site, before runtime cleanup can discard the active call/task state. Human output synthesizes readable call-chain and, for child failures, task-ancestry notes from those typed records. When `aura run --backend direct --format json` launches the binary, a private bounded channel returns the same schema-version-1 diagnostic to the CLI; tools never need to parse the human text. The internal transport uses a separate trap marker so a missing record is not confused with `main` returning status `1`; its descriptors are hidden and close-on-exec before user code starts. ## A Checklist Before Shipping Before a native binary goes anywhere important: - Run `aura check` on the source. - Run the program through `aura run` to confirm behaviour interactively. - Build with `aura build` and run the binary against the same scenarios. - For programs that do I/O or start processes, run them against the real resources — files that exist, sockets that are open, services that are reachable — in the built executable, not only through `aura run`. Reference: [CLI And Tooling](/manual/cli-and-tooling). ## Source: docs/learn/ownership-and-borrowing.md # Values, Moves, And Borrows This is the central chapter of the book. Almost everything in Aura — how functions receive data, how collections hold it, how tasks share it, how resources get cleaned up — follows from the rules introduced here. The short version: - Every value has an owner. - Moving a value transfers ownership. - Borrowing lets another piece of code use a value without taking it. - Mutable borrows are exclusive. - Resources should live inside a `with` block. Read the rest of the chapter to see why each of those matters. ## Copy Values And Move Values Some values are cheap enough to duplicate that the language just does it. Numbers, `bool`, `Duration`, and queue handles are **copy types**. Assigning one to a new name produces another usable binding: ```aura count = 3 other = count print(count) print(other) ``` Task handles are conditional. `Task[T]` is copyable when `T` is copyable, a `Queue[...]` handle, or a recursively repeatable `Task[...]` handle. A task returning `str`, `list[...]`, or another non-copy owned value instead has a move-only handle so aliases cannot duplicate its single result-observation right. Everything else — `str`, `list[T]`, `dict[K, V]`, `set[T]`, `random.Rng`, ordinary class instances, `TaskGroup`, file handles, process resources, and network resources — is a **move type**. Assigning a move value transfers ownership: ```aura name = "aura" other = name # name has moved into other. Using name is a compile error. print(other) ``` The rule prevents two bindings from thinking they are responsible for the same owned resource. It is the reason a string, a file handle, and a task group can all be closed automatically when their owner goes out of scope. ## Cloning When Two Owners Are Needed If a move type supports independent duplication, a program asks for it explicitly with `.clone()`: ```aura name = "aura" copy = name.clone() print(name) print(copy) ``` Collections clone their elements when `copy()` creates independent storage: ```aura jobs = ["parse", "check", "build"] snapshot = jobs.copy() print(jobs.len()) print(snapshot.len()) ``` That requires every produced element to be clone-safe. `random.Rng` deliberately has no clone route, and putting one inside a list, dictionary, class, or enum does not change that. A generic clone helper is still valid: Aura infers the requirement and rejects only a specialization that would duplicate an `Rng`. Duplicate close to the reason for duplication. An explicit `clone()` or `copy()` at the call site tells the reader that the program is deliberately keeping both values. ## Closures Own Their Captures A closure takes its captured values when the lambda expression is evaluated: ```aura label = "compile" length: def() -> int64 = lambda: label.len() print(length()) print(length()) ``` `label` is non-Copy, so it moves into `length`. The closure can still be called repeatedly because its body only reads the captured string. If the body returned `label` directly, the call would consume the capture and therefore the complete closure; a second call would be a moved-value error. To keep both owners, clone before creation: ```aura label = "compile" captured = label.clone() length: def() -> int64 = lambda: captured.len() print(label) print(length()) ``` Copy captures are snapshots and leave the source usable. Shared and mutable enclosing parameters are capabilities, so a closure cannot capture them as owned values. Captured environments are read-only in the current phase. ## Shared Borrows When a helper should read a value without owning it, the parameter uses `T`: ```aura def render_title(title: str) -> str: return title.to_upper() title = "manual" print(render_title(title)) print(title) ``` The call site writes no capability prefix; Aura reads the bare shared form from the function signature. The caller keeps ownership, and the helper cannot move a non-copy value out through that shared access. Classes make the benefit obvious: ```aura class Job: id: int32 label: str def render(job: Job) -> str: return f"{job.id}: {job.label}" job = Job(id=7, label="compile") print(render(job)) print(render(job)) ``` The same job is rendered twice because `render` never takes ownership. ## Mutable Borrows When a helper should mutate a caller-owned value, the parameter uses `mut T`: ```aura def add_job(jobs: mut list[str], job: own str): jobs.append(job) mut jobs = list[str]() add_job(jobs, "parse") add_job(jobs, "check") print(jobs.len()) ``` Two rules apply to mutable borrows: 1. The caller's binding must itself be mutable. You cannot take `mut` access from an immutable binding or a temporary value. 2. Mutable access is **exclusive**. If one argument to a call takes `mut`, no other argument in that call may borrow the same value. This is not a stylistic preference; overlapping mutable aliases would make the order of effects unclear. Aura rejects them at the call boundary. ## Methods And `self` Methods declare how they receive `self`, and the receiver form determines what the method is allowed to do: ```aura class Counter: value: int32 def get(self) -> int32: return self.value def inc(mut self): self.value += 1 ``` Bare `self` reads through a shared borrow; `self` is its explicit synonym. `mut self` writes. A consuming method uses `own self` and takes ownership of the whole instance. A borrowed method may look at non-copy fields but cannot move them out: ```aura class Label: text: str def show(self) -> str: return self.text.clone() ``` `self.text.clone()` returns a new owned `str` to the caller. Returning `self.text` without cloning would try to move a `str` out through a shared borrow, which the compiler rejects. ## Field Moves Owned fields are independent. A program can move one field out of a class without giving up the rest — but the moved field becomes unusable until it is reassigned: ```aura class Packet: id: int32 body: str mut packet = Packet(id=1, body="hello") body = packet.body print(packet.id) packet.body = "replacement" print(packet.body) ``` `packet.id` is still available because it was not moved. `packet.body` became uninitialised after the first move and could only be used again once it was reassigned. This is the same rule as for top-level bindings, applied field by field. ## Collections And Ownership Collection operations that store values declare explicit `own` positions. For For example, `list.append(value: own T)`, dictionary indexed assignment, and `set.add(value: own T)` move non-copy values into their collection. If the caller still needs one, clone it. ```aura mut jobs = list[str]() label = "compile" jobs.append(label.clone()) print(label) ``` Lookup methods such as `list.get` and `dict.get` return cloned owned values. The collection keeps its element, and the caller receives an independent copy: ```aura names = ["ada", "grace"] match names.get(0): case Some(name): print(name) case None: print("missing") ``` This is why a program can read clone-safe values from a collection repeatedly without juggling ownership. A value containing `random.Rng` must instead leave through an ownership-transferring operation such as `list.pop`, `dict.remove`, or a Queue receive. ## Tasks And Borrowing Child tasks receive **owned captures**. The start operation moves or copies each argument into task-owned storage before the child can outlive the caller. The target function can then borrow that capture or consume it: ```aura def worker(label: str): print(label) with group = TaskGroup(): label = "compile" group.start_soon(worker, label) ``` The capture itself is owned by the task, so starting it still moves the caller's non-copy value. If the parent also needs the label, clone before starting the child: ```aura with group = TaskGroup(): label = "compile" group.start_soon(worker, label.clone()) print(label) ``` `TaskGroup` itself is a resource. Normal practice is to keep it scoped with `with`, so that leaving the block waits for the children and accounts for their results. Bare shared target parameters borrow their task-owned capture; `own` targets consume it. `mut` targets are rejected because mutation of detached capture storage would have no caller-visible writeback. Ownership alone is not enough to cross a task boundary. Every capture and result must also be structurally `Transfer`: Copy data, `str`, recursively transferable collections and user data, and Queue/Task handle identities can cross. `random.Rng`, `TaskGroup`, capability views, and live file, process, or network resources cannot. Keep a live resource on the task that creates it and exchange owned descriptions, bytes, snapshot results, or handles. Aura still uses this rule as the share-nothing boundary between pinned scheduler workers. Queue and Task handle state is synchronized across workers; every other capture and result crosses as owned `Transfer` data. For a non-repeatable but transferable task result, the first call to `result`, `result_or_none`, or `result_or` consumes the task handle even if it times out, is cancelled, fails, or returns a fallback. Use a Queue protocol when several consumers need independently owned messages. ## Resources And Cleanup Owned resources — files, listeners, streams, processes, supervisors, task groups — should live inside `with` blocks: ```aura import fs with file = try fs.open("data.txt"): text = try file.read_all() print(text) ``` When the block exits, Aura runs the resource's cleanup path. Cleanup fires on normal exit **and** on runtime errors that unwind through the scope, in both `aura run` and built programs. `with` is the place where "I borrowed a resource" becomes "the resource has definitely been released." ## A Checklist When a program starts to feel tangled, run down this list: - Write `own T` when the function consumes the argument; a bare parameter grants shared access. - Pass `T` when the function only needs to inspect. - Pass `mut T` when the function should update a caller-owned value. - Clone as locally as possible when two owners are genuinely needed and the value is clone-safe. - Put resources in `with` blocks. - Put concurrent child work inside a `TaskGroup`. - Let `Result`, `Option`, and the outcome enums carry control flow. Do not smuggle failure through strings or magic values. The goal is not to fight the checker. The goal is to make the program say who is responsible for every value. Reference: [Ownership And Borrowing](/manual/ownership-and-borrowing). ## Source: docs/learn/results-and-options.md # Results, Options, And `try` A program that runs for any length of time has to deal with two uncomfortable facts: values can be absent, and operations can fail. Aura represents both in the type system so the code is honest about which calls might go wrong and how. This chapter introduces `Option[T]`, `Result[T, E]`, the `try` expression, and the outcome enums the runtime uses for queues, tasks, and I/O. Together they are how Aura replaces exceptions, null pointers, and sentinel values with ordinary control flow. ## `Option[T]`: A Value May Be Missing `Option[T]` is either `Some(value)` or `None`. Use it when absence is expected and is not itself an error: - a list index may be out of range - a dictionary key may be absent - a stream may reach end-of-file - a timed wait may finish with no value ```aura names = ["Ada", "Grace"] match names.get(3): case Option.Some(name): print(name) case Option.None: print("missing") ``` The short-form patterns `Some(name)` and `None` also work when the compiler already knows the scrutinee's type, but the qualified form is always clear and reads well in reference material. ## `Result[T, E]`: A Caller Must Decide `Result[T, E]` is either `Result.Ok(value)` or `Result.Err(error)`. Use it when an operation may fail and the caller should decide what to do: ```aura def divide(left: int32, right: int32) -> Result[int32, str]: if right == 0: return Result.Err("division by zero") return Result.Ok(left // right) match divide(10, 2): case Result.Ok(value): print(value) case Result.Err(message): print(message) ``` A command-line tool might print the error and stop with a non-zero exit code. A server might turn it into a response. A parser might recover and move on. None of those choices belongs in the library; they belong at the call site, which is exactly where the `match` lives. ## Parsing Is A `Result` The parsing builtins return `Result`: ```aura def read_limit(text: str) -> Result[int32, str]: match parse_int32(text): case Result.Ok(value): if value < 0: return Result.Err("limit must be non-negative") return Result.Ok(value) case Result.Err(message): return Result.Err(message) ``` The signature is honest: the caller will receive either an `int32` or a `str` error message. There is no hidden path through which this function might throw. ## `try`: Propagate Failure That last function has a familiar shape. It calls a sub-operation, checks whether it failed, and if it did, hands the same error back to its caller. That pattern is common enough to deserve a short form, so Aura provides one: **`try`**. `try expr` evaluates `expr`. If the result is `Result.Ok(value)`, the expression produces `value` and execution continues. If the result is `Result.Err(error)`, the current function returns that error immediately. ```aura def parse_pair(left: str, right: str) -> Result[int32, str]: a = try parse_int32(left) b = try parse_int32(right) return Result.Ok(a + b) ``` `try` is for the common case where the current function cannot usefully recover. It keeps the happy path readable while preserving an explicit `Result` return type. Use `match` instead when the function has a local recovery strategy: ```aura def parse_or_zero(text: str) -> int32: match parse_int32(text): case Result.Ok(value): return value case Result.Err(_message): return 0 ``` Two constraints on `try`: 1. It can only appear in a function whose return type is a compatible `Result`. 2. The error type of the inner `Result` must match the outer function's error type. If they differ, convert explicitly with a `match` or a helper. ## Domain-Specific Outcomes Not every failure is well-described by a plain `Result[T, str]`. Aura APIs use richer enums when the caller benefits from distinguishing outcomes. | API family | Outcome type | Why this shape | | --- | --- | --- | | `fs`, `io`, `net` | `Result[T, io.Error]` | Operating-system and protocol failures have named categories (`NotFound`, `TimedOut`, `BrokenPipe`, ...). | | `process` | `Result[T, process.Error]` | Spawning, waiting, status checks, and pipes have process-specific failure modes. | | `Queue.put` | `Result[None, SendError[T]]` | A failed send returns the unsent value so the caller can retry, queue elsewhere, or log it. | | `Queue.get` | `QueueReceive[T]` | A receive can produce an item, observe a close, time out, or be cancelled — four distinct outcomes. | | `Task.result` | `TaskResult[T]` | A task can finish normally, fail, time out, or be cancelled. | | `wait_any` | `WaitAny[T]` | The caller sees which task completed, with its value or error. | | `wait_all` | `WaitAll[T]` | Either every value is available, or the first failing index is reported. | Task-result and multi-task wait APIs clone a stored successful value. Their result type must therefore be clone-safe: an observation that would return `random.Rng`, including through a wrapper, is rejected with `AU3007`. Queue receive outcomes transfer one owned item and do not have this restriction. Using the right enum lets a program handle one case specifically while still handling the others: ```aura import fs import io def read_config(path: str) -> str: match fs.read_to_string(path): case Result.Ok(text): return text case Result.Err(io.Error.NotFound): return "mode=default" case Result.Err(error): print(error) return "mode=default" ``` "File not found" is a normal condition for a config file with a default value. Every other I/O error is reported and falls back to the same default. Policy is local, specific, and visible. ## Retrying A Result Worker When every `Err` should be retried under one simple attempt budget, pass a capture-free worker to `control.retry`: ```aura import control def fetch_once() -> Result[str, str]: return Result.Err("service unavailable") result = control.retry( fetch_once, max_attempts=3, initial_backoff=10ms ) ``` The first attempt runs immediately. Later attempts wait for `10ms`, then `20ms`, and so on. There is no delay after the final attempt, and the helper returns that final attempt's exact `Err`. A zero backoff skips sleeping. Worker traps and task cancellation propagate; they do not masquerade as the worker's error type. This helper retries every error. Keep an explicit loop when the application must classify errors, add jitter, or stop on a status such as an HTTP `429`. The maintained [`retrying_network_worker.au`](../../examples/agents/retrying_network_worker.au) shows that policy-rich form, while [`retry_with_backoff.au`](../../examples/agents/retry_with_backoff.au) demonstrates the generic helper. ## Choosing Between Them A rule of thumb for day-to-day code: - Use **`Option[T]`** when absence is an ordinary state. - Use **`Result[T, E]`** when failure needs a reason. - Use **`try`** when the only useful local behaviour is "return this error to my caller." - Use **`match`** when the current function can make a decision. - Use the **domain-specific outcome enum** when the API hands you one. A `QueueReceive`, a `TaskResult`, or a `process.Wait` is already the right shape; collapsing it into a string loses information the API went to some trouble to preserve. The next chapter turns to program structure: splitting code across files, modules, and packages so domain types and their error shapes stay organised as programs grow. Reference: [Enums And Pattern Matching](/manual/enums-and-match), [Expressions](/manual/expressions). ## Source: docs/learn/small-programs.md # The First Program The best way to meet a language is to write a program that actually reports something. In this chapter we will build a small classifier: it takes a list of numbers, sorts each one into a category, counts how often each category appears, and prints a report. Along the way we will meet bindings, functions, control flow, integer parsing, maps, and `match`. Nothing here is advanced, but everything here shows up in real programs. ## Running A Script Aura files run top to bottom. A script can mix prints, bindings, and computation: ```aura print('aura') print(40 + 2) ``` Ordinary strings may use matching single or double quotes, and both forms have the same escape rules. F-strings remain double-quoted as `f"..."`. Save that as `greeting.au` and run `aura run greeting.au`. Scripts are useful for quick tools and examples. When a program benefits from an explicit entry point — especially when it will be built as a native binary that should return an exit code — use `main`: ```aura def main() -> int32: print("ready") return 0 ``` `main` takes no parameters and returns `int32` or `None`. A file may use either style, but not both at once. ## Bindings Use `name = expression` when the type of the right-hand side is clear: ```aura limit = 10 label = "jobs" enabled = true ``` Bindings are immutable by default. When a binding will be reassigned, mark it `mut`: ```aura mut count = 0 count = count + 1 count += 1 ``` Aura infers the type of most bindings from their initial value. Add an explicit annotation when the compiler cannot work it out on its own — especially for empty collection literals, which have no elements to guess from: ```aura values: list[int32] = [] counts: dict[str, int32] = {} seen = set[str]() ``` Annotations are also useful at module boundaries and in function signatures, where the type forms part of the program's public contract. ## Functions A function declares its parameters and its return type: ```aura def classify(value: int32) -> str: if value < 0: return "negative" elif value == 0: return "zero" elif value < 10: return "small" else: return "large" ``` Functions that do not return a meaningful value may omit the return type: ```aura def log_value(value: int32): print(value) ``` Parameters may have defaults, so callers can omit them: ```aura def classify_with_limit(value: int32, limit: int32 = 10) -> str: if value < 0: return "negative" elif value < limit: return "small" else: return "large" print(classify_with_limit(4)) print(classify_with_limit(40, limit=100)) ``` Named arguments are always available and are worth reaching for whenever a call would otherwise be hard to read. ## Small Callbacks With Lambdas When a callback is one expression, write a contextually typed lambda: ```aura offset: int32 = 40 add: def(int32) -> int32 = lambda value: value + offset print(add(2)) ``` The `def(int32) -> int32` annotation tells the compiler the parameter and result types. Lambdas do not repeat those types inline. The outer `offset` is a Copy value, so the closure snapshots it when the lambda is created. A non-Copy owned value instead moves into the closure; clone first when the outer scope also needs an owner. Read-only closures may be called repeatedly. A closure that consumes a non-Copy capture is single-use. Use a named function when the callback needs multiple statements. A zero-parameter lambda can infer its result from the body. Lambdas with parameters need their parameter types from context. Capture-free lambdas may be stored anywhere a function value can; capturing closures stay in immutable locals, direct calls, compiler-known callbacks, or one qualifying task start. ## Control Flow `if`, `elif`, and `else` chain as you would expect: ```aura if value < 0: print("negative") elif value == 0: print("zero") else: print("positive") ``` `for value in range(n)` counts from zero up to (but not including) `n`. With two arguments, `range(start, stop)` uses an explicit start: ```aura mut total = 0 for value in range(5): total += value print(total) for value in range(-2, 3): print(value) ``` Use `while` when the stop condition is not a simple range: ```aura mut current = 1 while current < 100: current = current * 2 print(current) ``` `break` exits the nearest loop; `continue` skips to the next iteration: ```aura for value in range(10): if value == 2: continue if value == 6: break print(value) ``` ## `match` `match` is the tool for decisions with a shape. It can be used as a statement or as an expression that produces a value. ```aura def status_name(code: int32) -> str: return match code: case 0: "ok" case 1: "retry" case 2: "degraded" case _: "failed" ``` Integer and `str` matches use `_` as a wildcard because their value spaces are open. Boolean matches are exhaustive when both `true` and `false` are covered: ```aura def enabled_name(enabled: bool) -> str: return match enabled: case true: "enabled" case false: "disabled" ``` For enums, `match` becomes even more useful: the compiler will tell you when a variant is missing. [Shaping Data](/learn/data-modeling) shows that form in detail. ## Turning Text Into Numbers Aura expresses parsing with `Result`, so a bad input becomes explicit control flow: ```aura def parse_count(text: str) -> int32: match parse_int32(text): case Result.Ok(value): return value case Result.Err(message): print(f"bad count: {message}") return 0 print(parse_count("42")) print(parse_count("forty-two")) ``` `Result.Ok` carries the parsed value; `Result.Err` carries a message. When an operation can fail in a way the caller should care about, this is the shape the library will usually hand back. ## Putting It Together: A Classification Report This program classifies a list of numbers, counts how often each category appears, and prints the totals. ```aura def classify(value: int32) -> str: if value < 0: return "negative" elif value == 0: return "zero" elif value < 10: return "small" else: return "large" def bump(counts: mut dict[str, int32], key: own str): match counts.get(key): case Some(value): counts[key] = value + 1 case None: counts[key] = 1 values = [-3, 0, 1, 2, 10, 18, 21] mut counts: dict[str, int32] = {} for value in values: label = classify(value) bump(counts, label) for key, value in counts.items(): print(f"{key}: {value}") ``` There are two details in `bump` worth slowing down for. `counts: mut dict[str, int32]` says the helper will mutate a dictionary owned by its caller. The parameter declaration selects mutable access; the caller writes no capability prefix at the call site. `dict.get` borrows its key, so the same owned `key` can be moved into the later `counts.set`. The `own` annotation says `bump` takes responsibility for storing the category string. Run the program and you should see a tally for each category that appeared in `values`. ## A Rule Of Thumb Small Aura programs read well when type boundaries line up with data boundaries: - parse input into typed values as early as possible - use enums for states that have names - use `Option[T]` when a value may be missing - use `Result[T, E]` when an operation may fail - borrow values for helpers that do not need ownership Every one of those rules is still the right rule when the program grows. The next chapter puts them to work on richer data types. Reference: [Statements](/manual/statements), [Functions](/manual/functions), [Closures](/manual/closures), [Expressions](/manual/expressions). ## Source: docs/learn/testing.md # Testing A language that checks ownership and failure at compile time still cannot tell you whether your logic is right. That is what tests are for, and Aura ships a runner so you do not have to pick one. ## Your First Test Tests live in `tests/` next to your package manifest. A test is a parameterless function whose name starts with `test_`: ```aura def subtotal(prices: list[float64]) -> float64: mut sum = 0.0 for price in prices: sum += price return sum def test_adds_prices(): assert subtotal([1.5, 2.5]) == 4.0 def test_empty_list_is_zero(): assert subtotal([]) == 0.0 ``` Run every test in the package: ```bash aura test ``` ```text ok tests/subtotal_test.au::test_adds_prices ok tests/subtotal_test.au::test_empty_list_is_zero 2 passed; 0 failed ``` Each case is named `path::function`, so a failure tells you exactly which file and which function to open. The command exits non-zero when anything fails, which is all a CI job needs. ## Reading A Failure Change the expected total to something wrong and run it again: ```text FAILED tests/subtotal_test.au::test_wrong_expectation error[AU4001]: assertion failed --> tests/failing_test.au:8:5 | 8 | assert subtotal([1.5, 2.5]) == 5.0 | ^ = note: left = 4.0 = note: right = 5.0 = note: Aura call chain (innermost first): test_wrong_expectation at 7:1 ``` `assert` is not a plain boolean check. When a comparison fails, the compiler has already arranged for both sides to be reported: `left = 4.0`, `right = 5.0`. You do not have to rerun anything with print statements to find out what the values were. Assertions take an optional message, evaluated only when the assertion fails: ```aura def test_port_is_in_range(): port = 8080 assert port > 1024, f"port {port} is reserved" ``` ## One Test, Many Cases When the same logic needs several inputs, return a list of labeled case functions from a `test_*` function: ```aura def parse_port(text: str) -> Option[int64]: match parse_int64(text): case Result.Ok(port): return Option.Some(port) case Result.Err(_): return Option.None def valid_case(): assert parse_port("8080") == Option.Some(8080) def empty_case(): assert parse_port("") == Option.None def test_ports() -> list[(str, def() -> None)]: return [("valid", valid_case), ("empty", empty_case)] ``` Each entry becomes its own case, reported and counted separately: ```text ok tests/parse_test.au::test_ports[valid] ok tests/parse_test.au::test_ports[empty] 2 passed; 0 failed ``` The case functions are ordinary function values — the same first-class functions you can pass anywhere else in the language. They must be capture-free and take no arguments. ## Setup And Teardown A file may define `setup()` and `teardown()`, which run around every case in that file: ```aura def setup(): print("setup") def teardown(): print("teardown") def test_total(): print("case") assert 20 + 21 == 41 ``` The order is `setup`, then the case, then `teardown`. Teardown runs even when the case fails, so a temporary file or spawned process gets cleaned up either way. Each phase runs in isolation, so state does not leak between them through module values — use the filesystem or another external effect when a test genuinely needs to observe lifecycle state. ## Running A Subset While you are working on one thing, run only that thing. `-k` matches a substring of the full case name: ```bash aura test -k valid ``` ```text ok tests/parse_test.au::test_ports[valid] 1 passed; 0 failed ``` You can also pass explicit files or directories instead of the default `tests/` tree: ```bash aura test tests/parse_test.au ``` ## Tests In CI `--format json` prints one machine-readable document instead of progress lines, with a `schema_version`, a summary, and one record per case including its duration and failure diagnostic: ```bash aura test --format json ``` Cases run under a 30-second timeout by default; `--timeout-ms` changes it for slow integration tests. ## Where To Go Next Any file in `tests/` without a `test_*` function still runs as a single case through `main()` or its top-level statements, which is handy for end-to-end scripts you want executed rather than asserted. The [Assertions](/manual/assertions) chapter is the normative reference for `assert`, operand reporting, and evaluation order, and [CLI And Tooling](/manual/cli-and-tooling) specifies discovery, selection, the JSON schema, and exit codes. ## Source: tutorials/00-overview.md # Overview Aura is a compiled, statically typed programming language with Python-inspired syntax, explicit ownership, native executables, and no garbage collector. If you know Python, Aura will feel familiar: indentation defines blocks, functions use `def`, classes use `class`, and semicolons are unnecessary. The compiler assigns every expression a type, checks how values and resources are owned, and validates the program before execution. Aura 0.3 focuses on reliable applications, agents, and ML infrastructure. The long-term goal is a general-purpose systems language capable of building the full software stack, including operating systems and device drivers. These tutorials teach the language as it exists in this repository today, not the full proposal surface. ## What You Can Learn Today - top-level scripts and explicit `main` - bindings, mutability, `None`, and the current builtin type names - functions, owned return values, typed parameters, and shared or mutable access - classes, keyword construction, defaults, receivers, and methods - ownership, borrowing, move semantics, copy types, and cloning - owned `list[T]`, `dict[K, V]`, and `set[T]` collections with literals, indexing, and iteration - enums, exhaustive `match`, built-in `Result[T, E]`, `Option[T]`, and `SendError[T]` - strings, string parsing/formatting, numbers, signed computed Duration values, and the current builtin methods - `if`, `elif`, `else`, `while`, `for range(...)`, `break`, and `continue` - statement-form `match` over enum variants plus literal `bool`, integer, and `str` cases - `with`, `try expr`, queues, structured task groups, task waiting helpers, and task timeouts - expression-form `match`, nested enum patterns, and multi-payload variants - owned returns, including ordinary copies and explicit non-copy clones or transfers - user-defined generic classes, enums, and functions - trait declarations, trait impls, and bounded generic calls - local file modules with `import`, `from ... import ...`, and `public` visibility - `Aura.toml` packages with local path dependencies, git dependencies, and workspaces - CLI inspection commands and compiler-backed editor tooling ## What The Bootstrap Compiler Currently Supports Today's working subset includes: - `class`, `enum`, and `def` - `trait` plus `impl Trait for Type` - top-level executable statements - explicit type annotations and inferred bindings - mutable reassignment with `mut` - omitted `-> None` return types - ownership and borrowing with `T` and `mut T` - user-defined enums plus built-in `Result`, `Option`, and `SendError` - user-defined generic classes, enums, and functions - builtin `list[T]`, `dict[K, V]`, and `set[T]` collections with literals - class methods with shared `self`, consuming `own self`, and mutable `mut self` - arithmetic, comparisons, strings, booleans, and Duration literals, constructors, conversions, and checked operators - `if`, `elif`, `else`, `while`, `for`, `match`, `with`, `break`, and `continue` - `print`, `range`, `cancelled`, `sleep`, `wait_any`, and `wait_all` - machine-readable compiler output for AST, analysis, and completions ## Current Boundaries Notable limits include: - full dependency registries and version solving beyond local/git package dependencies - further direct-backend hardening and the remaining coverage push toward 100% ## Recommended Companion Material Keep the `examples/` tree open while reading. The categorized examples mirror these chapters and stay runnable as the language evolves. If you are coming from Python, the single most important chapter is [06-ownership-and-borrowing.md](06-ownership-and-borrowing.md). It explains how Aura manages memory without a garbage collector and shows you how to fix every common compiler error you will encounter. ## Source: tutorials/01-running-programs.md # Running Programs Aura currently runs through the bootstrap CLI, `aura`. You invoke it from the repository root using `cargo run -p aura --`. ## Your First Program Create a file called `hello.au`: ```aura check-pass print("hello, aura") ``` Run it: ```bash cargo run -p aura -- run hello.au ``` You should see `hello, aura` printed to the terminal. ## The Core Commands The three commands you will use most often: ```bash cargo run -p aura -- check examples/classes/point_distance.au cargo run -p aura -- run examples/classes/point_distance.au cargo run -p aura -- build -o ./target/aura-point examples/point.au ``` - **`check`** -- parse and type-check the file without running it. Use this for fast feedback while editing. - **`run`** -- execute the program through the MIR runtime. This is the easiest way to test your code. - **`build`** -- compile to a standalone native binary. The output binary does not depend on the original `.au` source files at runtime. ## Build Backends The `build` command accepts a `--backend` flag: - `--backend auto` (the default) -- first tries direct native emission and may fall back to a standalone launcher with embedded MIR - `--backend direct` -- requires direct native emission and rejects programs that cannot use it In practice, the default is what you want. Use `direct` for backend testing or when an embedded-MIR fallback is unacceptable. The build step requires a host C compiler. A source-checkout CLI may use Cargo to refresh the native runtime; installed release archives carry that runtime and do not require Rust. Built binaries preserve file, line, and caret context for runtime failures. ## Inspection Commands These commands are for debugging and understanding your code: - **`ast`** -- print the parsed syntax tree - **`ast-json`** -- print the syntax tree as machine-readable JSON - **`mir`** -- print the lowered MIR for the checked program - **`analyze`** -- print machine-readable compiler analysis (diagnostics, symbols, hover, definition) - **`complete`** -- print completion items for a position in the file `check`, `run`, and `build` accept `--format human|json` for diagnostics. The JSON form is schema-versioned and preserves the compiler's stable `AU####` code, primary and related spans, notes, help, machine-applicable edits, and typed runtime `call_frames` and `task_ancestry`. The two frame arrays are always present and are empty for diagnostics without runtime frames. ```bash cargo run -p aura -- ast examples/classes/point_distance.au cargo run -p aura -- mir examples/control_flow/while_break_continue.au cargo run -p aura -- analyze examples/classes/point_distance.au cargo run -p aura -- complete --line 5 --character 11 --trigger . examples/point.au ``` For `complete`, `--line` and `--character` use zero-based positions. Member completion expects the cursor positioned just after `.`. Use `help` and `--version` to see CLI usage and the current version: ```bash cargo run -p aura -- help cargo run -p aura -- --version ``` The source-checkout command prints the build channel and a 12-hex-digit source commit, such as `aura 0.3.2-dev (0123456789ab)`. Release archives identify their channel as `aura 0.3.2-preview (0123456789ab)`. Use `deps update` to refresh git dependencies without deleting `Aura.lock` manually: ```bash cargo run -p aura -- deps update cargo run -p aura -- deps update util ``` ## Stdin Mode For Editors All commands support stdin for editor integration. Provide a virtual path so the compiler can resolve local imports: ```bash cat examples/modules/simple_import.au | cargo run -p aura -- run --stdin "$(pwd)/examples/modules/simple_import.au" ``` This is how the VS Code language server communicates with the compiler for unsaved editor buffers. ## Package-Aware Commands When a file lives under a package with `Aura.toml`, the CLI automatically resolves local modules from `src/`, resolves path and git dependencies by package name, and updates `Aura.lock`: ```bash cargo run -p aura -- run examples/packages/local_path_dependencies/app/src/main.au cargo run -p aura -- run examples/packages/workspace/app/src/main.au ``` Run the dependency update command from a package or workspace directory when you want to refresh moving git references: ```bash cd examples/packages/local_path_dependencies/app cargo run -p aura -- deps update cargo run -p aura -- deps update util ``` See [18-packages-and-workspaces.md](18-packages-and-workspaces.md) for details. ## Scripts And `main` Aura supports two entry styles. ### Top-level script Write executable statements directly at the top level. This is the simplest way to start: ```aura check-pass a = 56 b = 100 print(a + b) ``` See [examples/basics/top_level_script.au](../examples/basics/top_level_script.au). ### Explicit `main` For programs that return an exit code, declare a `main` function: ```aura check-pass def main() -> int32: print(5) return 0 ``` See [examples/classes/point_distance.au](../examples/classes/point_distance.au). Do not mix top-level executable statements with `main` in the same file. Choose one style. ## Editor Tooling The VS Code language server uses compiler-backed `analyze` and `complete` output, which means the editor and CLI share the same type-checking engine for: - diagnostics - symbols - hover - go-to-definition - completions See [08-tooling.md](08-tooling.md) for setup instructions. ## Source: tutorials/02-bindings-and-types.md # Bindings And Types In Aura, every value has a type known at compile time. Bindings are introduced with assignment -- no `let` keyword is needed. ## Inferred Bindings The compiler infers the type from the right-hand side: ```aura check-pass a = 56 b = 100 total = a + b ``` Here `a`, `b`, and `total` are all `int64` because integer literals default to `int64`. The shorter type spelling `int` is an alias for `int64`. See [examples/basics/top_level_script.au](../examples/basics/top_level_script.au). ## Annotated Bindings You can write the type explicitly when you want to be clear or when the compiler needs help: ```aura check-pass a: int32 = 6 b: int32 = 10 c: int32 = a + b ``` Type annotations are required when the compiler cannot infer the type, for example with empty collections: ```aura check-pass mut names: list[str] = [] mut counts: dict[str, int32] = {} ``` See [examples/basics/main_function.au](../examples/basics/main_function.au). ## Mutable Bindings Bindings are immutable by default. Use `mut` when you need to reassign: ```aura check-pass mut counter: int32 = 1 counter = counter + 1 counter += 3 ``` If you forget `mut` and try to reassign, the compiler will reject the code. This is intentional -- immutable by default makes it easy to see which values change. See [examples/basics/mutable_bindings.au](../examples/basics/mutable_bindings.au). Reusing an existing name updates that binding. The current compiler does not create a new shadowed binding in the same scope. ## `None` Is The Unit Type And Value Aura uses `None` as both the unit type and the sole unit value: ```aura check-pass status: None = None ``` Functions that omit a return type annotation implicitly return `None`. You will see this throughout the tutorials. ## Builtin Scalar Types Aura has a rich set of numeric types. If you are not sure which to use, start with `int` for integers and `float64` for decimals: | Type | Description | When to use | |------|-------------|-------------| | `int` | Alias for `int64` | Default integer spelling | | `int32` | 32-bit signed integer | Fixed-width APIs and 32-bit range/layout contracts | | `int64` | 64-bit signed integer | Same type as `int`; large counts and timestamps | | `float64` | 64-bit floating point | Default for decimal math | | `float32` | 32-bit floating point | When memory or precision constraints require it | | `bool` | `true` or `false` | Conditions and flags | | `str` | Owned text | Any text data | | `Duration` | Signed nanosecond time span | Computed backoff and concurrency timeouts (`5ms`, `1s`, `2m`) | | `None` | Unit type | Functions with no meaningful return | The full set of integer types covers `int8` through `int128`, `uint8` through `uint128`, plus `intsize` and `uintsize` for platform-sized integers. `int` is not an additional width: it is exactly `int64`. Use other explicit widths when you need control over memory layout, value ranges, or a fixed-width API contract. Integer literals default to `int64`. Floating-point literals default to `float64`, but both kinds of literal adopt a compatible expected numeric type from an annotation, parameter, return type, or field. An integer literal may adopt `float32` or `float64` only when its integer value is exactly representable there: ```aura check-pass count: int32 = 12 ratio: float32 = 3.25 whole_ratio: float64 = 2 ``` This float-context rule applies only to literals. It never converts an already-bound integer value. If an integer literal is not exact in the expected floating type, the compiler asks you to use an explicit floating spelling or `.to_float()` so that rounding is visible in the source. APIs that explicitly use `int32`, including queue capacities and a numeric `main()` exit status, remain exact `int32` contracts. Position APIs use `int64`: this includes ranges, list indexes, slice endpoints, enumeration positions, and Array coordinates. Length members match that position domain: `str.len()`, `str.byte_len()`, `list.len()`, `dict.len()`, and `set.len()` all return `int64`. `values.get(0)` and `index = 0` both use `int64`, so the binding can flow directly into an index operation. Fixed-width `int8`, `int16`, `int32`, `uint8`, `uint16`, and `uint32` bindings widen losslessly only at position sites. Ordinary assignments and function arguments still require exact types. ## Builtin Container Types Aura provides three owned collection types and several runtime types: | Type | Description | |------|-------------| | `list[T]` | Ordered, growable list | | `dict[K, V]` | Key-value dictionary | | `set[T]` | Unordered collection of unique values | | `Array[T]` | Fixed-shape contiguous numeric array; `T` is `int32`, `int64`, `float32`, or `float64` | | `Option[T]` | A value that may or may not be present | | `Result[T, E]` | Success or failure | | `Queue[T]` | Typed queue for concurrency | | `Task[T]` | Handle to a spawned task | | `TaskGroup` | Structured task scope | `Option[T]` and `Result[T, E]` are covered in [10-results-and-options.md](10-results-and-options.md). Queues and tasks are covered in [13-concurrency.md](13-concurrency.md). `Array[T]` is an owned non-Copy value with a fixed rank-at-least-one shape. Construct it explicitly with an Array constructor: ```aura check-pass source: list[float64] = [1.0, 2.0, 3.0, 4.0] matrix = Array[float64].from_list(source, [2, 2]) zeros = Array[int32].zeros([3, 4]) filled = Array[float32].full([2, 2], 0.5) ``` `from_list` copies the scalar elements, so `source` remains usable. Assignment of an Array transfers ownership, while `.clone()` returns an independent Array. All four maintained Array specializations satisfy `Transfer`. See [examples/numbers/numeric_arrays.au](../examples/numbers/numeric_arrays.au) and the [Numeric Arrays Manual](../docs/manual/numeric-arrays.md). ## `list[T]` And List Literals Create a list with a list literal: ```aura check-pass mut numbers = [1, 2, 3] ``` Or with the explicit empty constructor: ```aura check-pass values = list[int32]() ``` The element type must be consistent: ```aura check-fail:AU2002 mut ok = [1, 2, 3] mut bad = [1, "two"] # rejected: mixed types ``` Empty list literals need a type annotation: ```aura check-pass mut names: list[str] = [] ``` Common list operations: ```aura check-pass def main(): mut items = [10, 20, 30] items.append(40) # append an element print(items.len()) # 4 print(items[0]) # 10 -- indexed access print(20 in items) # true popped = items.pop() # removes and returns the last element print(popped) # 40 ``` Negative list indexes count from the end. The same normalization applies to direct reads and writes and to `get`, `set`, `pop`, and `swap`: ```aura fragment print(items[-1]) # final element match items.get(-2): case Option.Some(value): print(value) case Option.None: pass items[-1] = 50 items.insert(-1, 45) # inserts before the final element end_index: int64 = items.len() items.insert(end_index, 60) # appends ``` Normalization is `len + index`, performed once. `get` returns `None` if the result is still out of range; direct access, `pop`, `set`, and `swap` raise a runtime error. `insert` clamps positions to the range from zero through the current length. List slicing uses the same loud boundary philosophy and returns a fresh owned list: ```aura check-pass values = [10, 20, 30, 40] middle = values[1:3] # [20, 30] prefix = values[:2] # [10, 20] suffix = values[-2:] # [30, 40] copy = values[:] # an independent list ``` Every written endpoint uses the `int64` position domain, negatives normalize once, and both effective bounds must be in `0..=len`. A start greater than end is also an `AU4003` runtime error. Aura does not copy Python's clamping or reversed-range-as-empty behavior. Slicing copies Copy elements and clones clone-safe non-Copy elements; it never creates a view. The method surface includes `len`, `is_empty`, `copy`, `append`, `pop`, `get`, `insert`, `set`, `remove`, `index`, `count`, `swap`, `extend`, `clear`, `reverse`, `sort`, `map`, `filter`, `reserve`, and `with_capacity`. Operations that compare elements require the element type to define equality. This includes `remove`, `index`, `count`, `in`, and `not in`. Closures, `random.Rng`, opaque FFI handles, and values containing them have no equality relation, so these operations are rejected with `AU2008`. The four callable-powered algorithms use named function values: ```aura check-pass def doubled(value: int32) -> int32: return value * 2 def is_even(value: int32) -> bool: return value % 2 == 0 values: list[int32] = [3, 1, 2, 4] mapped = values.map(doubled) filtered = values.filter(is_even) mut ordered = values.copy() ordered.sort() ``` `map` and `filter` are eager shared reads that return fresh owned lists and retain `values`. `filter` clones accepted elements, so the element type must be clone-safe. Natural and keyed `sort` calls are stable in-place mutations. The `key` callback runs once per element from left to right before mutation; a key trap leaves the list unchanged. Algorithm callbacks take their element with the exact bare/shared capability shown above, not `mut` or `own`. `list.len()`, `range(...)`, and list indexes share the `int64` position domain: ```aura fragment for index in range(items.len()): print(items[index]) ``` The free `len(value)` builtin delegates to the same member and has the same `int64` result: ```aura fragment assert len(items) == items.len() assert len("A🎉") == "A🎉".len() ``` For `str`, `len()` counts Unicode scalar values and `byte_len()` counts the UTF-8 encoding bytes. Both counts are `int64`, so `"A🎉".len()` is `2` while `"A🎉".byte_len()` is `5`. Indexed reads work directly for copy element types. For clone-safe non-copy element types like `str` or ordinary user-defined classes, use `get(index)` for an explicit cloned read. Appending `.clone()` after `items[index]` cannot repair the read because the illegal move would happen before the method call. A value containing `random.Rng` must be transferred with `pop(index)` because it cannot be cloned, and the rejection names that reason directly: ```aura check-pass names = ["Ada", "Grace"] match names.get(0): case Option.Some(value): print(value) case Option.None: pass ``` See [examples/collections/list_basics.au](../examples/collections/list_basics.au), [examples/collections/list_iteration.au](../examples/collections/list_iteration.au), [examples/collections/list_polish.au](../examples/collections/list_polish.au), [examples/collections/slices.au](../examples/collections/slices.au), and [examples/collections/list_algorithms.au](../examples/collections/list_algorithms.au). For integer types, the runtime enforces the annotated width. A binding like `value: int8 = 127` is valid, but exceeding that range at runtime produces an error and preserves the declared type. ## `dict[K, V]` And Dictionary Literals Create a dictionary with a literal: ```aura check-pass mut counts = {"aura": 1, "codex": 2} ``` Or with the explicit empty constructor: ```aura check-pass counts = dict[str, int32]() ``` Empty dictionary literals need a type annotation: ```aura check-pass mut counts: dict[str, int32] = {} ``` Dictionaries support indexed reads when the value type is copy, and indexed writes for all value types: ```aura fragment counts["aura"] = 5 print(counts["aura"]) ``` Dictionary lookups work inside larger expressions including f-strings: ```aura fragment print(f"value: {counts['aura']}") ``` For a non-copy value type, direct `dictionary[key]` is rejected; Aura never performs a hidden clone. When the value type is clone-safe, `get(key)` gives an explicit cloned optional read and `remove(key)` transfers the stored value out. When the value type carries `random.Rng` state, only `remove(key)` works, and the rejection explains that `get(key)` would also be rejected. `items()` returns `list[(K, V)]` in insertion order: ```aura fragment entries = counts.items() match entries.get(0): case Option.Some((key, value)): print(key) print(value) case Option.None: pass ``` The method surface includes `len`, `is_empty`, `copy`, `get`, `remove`, `keys`, `values`, `items`, `clear`, `update`, `reserve`, and `with_capacity`. Use indexed assignment for storage and `in` for membership. See [examples/collections/dict_basics.au](../examples/collections/dict_basics.au). ## `set[T]` And Set Literals Create a set with value-only entries inside curly braces. Dictionary literals use `key: value` pairs: ```aura check-pass mut seen = {1, 2, 2, 3} # duplicates are removed print(seen.len()) # 3 ``` Or with the explicit empty constructor: ```aura check-pass names = set[str]() ``` Empty sets use the typed constructor shown above. The method surface includes `len`, `is_empty`, `copy`, `add`, `remove`, `discard`, `clear`, `reserve`, and `with_capacity`. Use `in` for membership. Sets deduplicate values. Bare iteration is shared; `for value in own set:` consumes the set. See [examples/collections/set_basics.au](../examples/collections/set_basics.au). ## Owned Comprehension Results List, set, and dictionary comprehensions build fresh owned collections: ```aura check-pass values = [1, 2, 3, 4] squares = [value * value for value in values] even = {value for value in values if value % 2 == 0} labels = {value: str(value) for value in values} ``` Each clause uses the same bare-loop rules as `for value in values:`. A list or Set target is shared, so storing a non-copy target in the new collection needs an explicit clone: ```aura check-pass names = ["Ada", "Grace"] names_copy = [name.clone() for name in names] ``` Aura does not silently clone. Queue is the existing exception: a bare Queue clause receives each item already owned, so that item may move directly into the result. The result collection is always owned and eager. See [examples/collections/comprehensions.au](../examples/collections/comprehensions.au). ## Literal Defaults Summary of literal type rules: - integer literals default to `int64` (`int` is an alias for `int64`) - integer literals can adopt an expected floating type only when exactly representable - floating-point literals default to `float64` - duration literals like `5ms`, `1s`, and `2m` have type `Duration` - negative numeric literals are supported: `-5`, `-3.5`; Duration literals remain non-negative, so use a constructor such as `Duration.ms(-5)` for a negative Duration value ```aura check-pass offset: int32 = -5 temperature: float64 = -3.5 short_wait: Duration = 5ms ``` ## Source: tutorials/03-functions.md # Functions Functions are declared with `def` and require explicit parameter types. ## Basic Functions ```aura check-pass def add(a: int32, b: int32) -> int32: return a + b ``` The return type follows `->`. If a function does not return a value, you can omit the return type and it defaults to `None`: ```aura check-pass def greet(): print("hello") ``` Reaching the end of a `None`-returning function is allowed. You can also use a bare `return`: ```aura check-pass def log_value(value: int32): print(value) return ``` See [examples/basics/main_function.au](../examples/basics/main_function.au). ## Parameters Parameters are written with explicit types: ```aura fragment def distance(a: Point, b: Point) -> float64: dx = a.x - b.x dy = a.y - b.y return (dx * dx + dy * dy).sqrt() ``` See [examples/classes/point_distance.au](../examples/classes/point_distance.au). An unmodified parameter grants shared access for every type. An implementation may pass copy bits directly, but that does not change the source-level contract. Write `own` when the function takes ownership: ```aura fragment def archive(doc: own Document): print(doc.title) ``` The choice is fixed at the declaration. For an unresolved generic `T`, the bare form is a declaration-stable shared borrow even if a later call uses a copy type; use `value: own T` for an identity, storing, or consuming helper. ## Borrowed Parameters When a function only needs to read a value, give it shared access. The caller keeps ownership and can continue using the value after the call. See [06-ownership-and-borrowing.md](06-ownership-and-borrowing.md) for the full explanation. Use `T` for read-only access: ```aura fragment def read(counter: Counter) -> int32: return counter.value ``` Use `mut T` for mutable access -- the function can modify the value and changes persist back to the caller: ```aura fragment def bump(counter: mut Counter): counter.value += 1 ``` A `mut` parameter requires a mutable binding at the call site: ```aura fragment mut counter = Counter(value=41) bump(counter) print(counter.value) # 42 ``` Aura rejects overlapping arguments when `mut` is involved. Mutable access must be exclusive -- no other overlapping access can exist in the same call: ```aura check-pass # This would be rejected: # bad(a: mut Counter, b: Counter) called with bad(c, c) ``` This rule prevents subtle bugs where a function reads from and writes to the same value through different parameters. See [examples/basics/borrow_parameters.au](../examples/basics/borrow_parameters.au). Task targets may use bare shared or `own` parameters. Arguments are moved or copied into task-owned capture storage before the child runs, and a shared target borrows that capture. `mut` targets are rejected. ## Calling Functions Aura supports positional and named arguments: ```aura check-pass def subtract(left: int32, right: int32) -> int32: return left - right print(subtract(10, 3)) print(subtract(left=10, right=3)) print(subtract(10, right=3)) ``` Function parameters remain positionally bindable. A `*` keyword-only marker is not part of Aura 0.3's structural callable model and receives `AU1101`. Rules: - positional arguments come before named arguments - named arguments match declared parameter names exactly - a parameter cannot be provided more than once ## Default Parameter Values Parameters can have defaults, which must come after required parameters: ```aura check-pass def greet(name: str = "world"): print("hello " + name) greet() # "hello world" greet(name="aura") # "hello aura" ``` Default values are evaluated on each call, in parameter order. They cannot reference other parameters, and are not allowed in trait or trait-impl method declarations. Bare shared defaults are valid and the temporary lives through the call; `own` defaults are consumed. `mut` defaults are rejected because mutations to a caller-invisible temporary would be lost. See [examples/basics/default_arguments.au](../examples/basics/default_arguments.au). ## Builtin Named Arguments Some builtins also support named arguments: ```aura check-pass for value in range(stop=3): print(value) for value in range(start=3, stop=5): print(value) print(value=42) ``` See [examples/basics/named_builtin_arguments.au](../examples/basics/named_builtin_arguments.au). ## What Functions Can Return Functions may return any concrete type accepted in a return annotation, including scalars, tuples, strings, collections, numeric arrays, classes, enums, generic specializations, function values, `Result[T, E]`, `Option[T]`, `Task[T]`, and `None`. Every result is owned; return types never describe a borrow into an argument or local value. Every function return is an owned value. Returning a copy type produces an ordinary independent copy: ```aura check-pass class User: score: int32 def score(user: User) -> int32: return user.score ``` The call produces an ordinary `int32` copy. Methods use the same `-> T` return annotation. When several shared parameters have copy types, the function can select and return any one of their values without a source label: ```aura check-pass def choose_positive(left: int32, right: int32) -> int32: if left > 0: return left return right ``` Returning a non-copy value requires ownership. Clone from shared input when the type is clone-safe, accept an `own` parameter and move from it, or provide an owner operation such as an `own self` method. A shared parameter cannot expose one of its non-copy fields as a return value. Every function result is an owned value. Returning a Copy value copies it; returning a non-Copy value requires constructing, cloning, or moving a value the function owns. Return annotations contain only the result type and never name an argument, field, or lifetime source. ## Generic Functions Functions can be generic over type parameters: ```aura check-pass def identity[T](value: own T) -> T: return value ``` The compiler infers type arguments from the arguments you pass and, when needed, from the expected return type. See [15-generics.md](15-generics.md) for the full story. ## Function Values A module-level named function can be stored and passed like any other copy value. Write its type in declaration-shaped form: ```aura check-pass class Pipeline: transform: def(int32) -> int32 def double(value: int32) -> int32: return value * 2 def apply(transform: def(int32) -> int32, value: int32) -> int32: return transform(value) selected = double pipeline = Pipeline(transform=selected) transforms: list[def(int32) -> int32] = [selected] print(apply(pipeline.transform, 3)) print(transforms[0](4)) ``` `def(T1, mut T2, own T3) -> R` contains parameter modes and types, but no parameter names or default expressions. Bare parameters are shared. An inferred binding such as `selected = consume` retains the exact contract, and you can also write it explicitly: ```aura fragment mutate: def(mut Counter) -> None = increment consume: def(own str) -> str = take callbacks: list[def(mut Counter) -> None] = [mutate] ``` Calling `mutate` requires a mutable place; calling `consume` moves a non-copy argument. A function with either contract does not fit a bare shared `def(T) -> R` annotation. A function binding whose target declaration is statically known keeps that declaration's names and defaults, so `selected(name="Aura")` and `selected()` work when the original parameter is named `name` and has a default. The structural function type itself retains neither, so a value returned through a structural annotation requires the complete positional argument list. A direct conditional selection keeps names and default availability when all candidates agree, and an omitted argument runs the selected function's own default. Class fields and mutable collections preserve the full parameter types and `mut`/`own` capabilities, but deliberately erase names and defaults; call a value loaded from either with the complete positional list. Function values are code pointers, so they are copy values and satisfy `Transfer`. You can use one as the target of `TaskGroup.start(...)` or `start_soon(...)`. Specialize a generic function explicitly (`show_int = show[int32]`) or give it a concrete expected function type. The expected type may come from an annotation, argument, field, collection element, or function-typed parameter default. Bound instance methods, associated-method values, and trait-method values are unavailable. Task targets may be direct associated methods without `self`; that task-target form does not create a general associated-method value. See [examples/basics/function_values.au](../examples/basics/function_values.au). ## Expression Closures Use a lambda when the callable is one expression and its parameter types are already clear from context: ```aura check-pass def main(): offset: int32 = 40 add: def(int32) -> int32 = lambda value: value + offset print(add(2)) ``` The annotation supplies `value: int32` and the `int32` result. A lambda does not put types or defaults in its own parameter list. Use `own value` or `mut value` only when the expected function type has that same capability. Multi-statement logic still belongs in a named `def`. With no parameters, context is optional: `lambda: 42` can infer `def() -> int64` from its body. A lambda with parameters still needs all of their types from context. Captures happen when the lambda is created. Copy values such as `offset` are snapshotted. A non-Copy owned value moves into the closure: ```aura check-pass def main(): name = "Aura" length: def() -> int64 = lambda: name.len() print(length()) print(length()) ``` This is repeatable because the body only reads `name`. A body that returns or otherwise consumes a non-Copy capture makes the closure single-use. Clone before creation when the outer code must keep an independent owner. Shared or mutable enclosing parameters cannot be captured, captured state cannot be mutated in this phase, and a closure can cross a task boundary only when every captured value is Transfer. Capture-free lambdas work anywhere a function value works. A capturing closure may stay in an immutable local, be called directly, enter a compiler-known repeatable callback, or move into a qualifying task start. It cannot be stored in a `def` field or collection or returned through an annotated `def` result. See [examples/basics/closures.au](../examples/basics/closures.au) and the normative [Closures](../docs/manual/closures.md) page. ## Current Limits - return values are always owned - clone-based non-copy returns require the returned type to be clone-safe - method values and multi-statement closure bodies are not part of this stage ## Source: tutorials/04-control-flow.md # Control Flow Aura supports the standard control-flow constructs: conditionals, loops, pattern matching, and early exit. ## `if`, `elif`, and `else` ```aura check-pass score: int32 = 90 if score < 50: print("low") elif score < 80: print("mid") else: print("high") ``` Conditions must evaluate to `bool`. Unlike Python, Aura does not support truthy or falsy coercions -- you must write explicit comparisons. See [examples/control_flow/if_elif_else.au](../examples/control_flow/if_elif_else.au). Aura supports boolean operators in conditions: ```aura fragment if ready and not blocked: print("ready") allowed = is_admin or is_owner ``` See [examples/control_flow/boolean_logic.au](../examples/control_flow/boolean_logic.au). ## Conditional Expressions Use `value if condition else alternative` when a branch chooses one value: ```aura fragment label = "ready" if ready else "waiting" ``` The condition is evaluated first and must be `bool`. Aura then evaluates exactly one arm. Both arms must produce the same static type; an expected type from a return, annotation, or call argument is used to type literals in both arms. Conditional expressions bind less tightly than `or` and associate to the right. A nested expression therefore reads as an `if`/`elif` choice: ```aura fragment label = "high" if score >= 80 else "mid" if score >= 50 else "low" ``` Moving a non-copy value in either arm makes that value unavailable after the conditional, because either runtime path may be selected. See [examples/control_flow/conditional_expressions.au](../examples/control_flow/conditional_expressions.au). ## Membership Tests `in` and `not in` ask whether a container holds a value: ```aura check-pass ports = [80, 443] print(443 in ports) print(8080 not in ports) ``` The container decides what the test means and what the value must be: | Container | Tests | Value must be | | --- | --- | --- | | `list[T]` | element membership | `T` | | `set[T]` | element membership | `T` | | `dict[K, V]` | key membership | `K` | | `str` | substring containment | `str` | Membership reads both operands and moves neither, so a non-copy container and a non-copy value are both still usable afterwards. The element or key type must define equality; closures, `random.Rng`, opaque FFI handles, and values containing them are rejected with `AU2008`. A container Aura cannot test reports `AU2003`, and a value of the wrong type reports `AU2002`. ## Chained Comparisons Comparisons chain the way they do in Python, so a range check reads as one expression: ```aura check-pass def in_range(value: int32, low: int32, high: int32) -> bool: return low <= value < high ``` `low <= value < high` means `low <= value and value < high`, except that `value` is evaluated only once. The chain stops at its first false link, so the operands after it are never evaluated. Equality, ordering, and membership all chain at the same level, so `a == b < c` is also one chain. The checker still checks every operand as if it were evaluated. A chain that would move a value only on a path short-circuiting skips is rejected, which is the same conservative rule the other branching forms use. See [examples/control_flow/membership_and_chains.au](../examples/control_flow/membership_and_chains.au). ## `while` ```aura check-pass mut n: int32 = 0 while n < 10: print(n) n += 1 ``` Use `while true:` with `break` for loops with complex exit conditions: ```aura check-pass mut attempts: int32 = 0 while true: attempts += 1 if attempts >= 3: print("giving up") break ``` ## `break` and `continue` Both work inside `while` and `for` loops: ```aura check-pass mut n: int32 = 0 while n < 10: n += 1 if n % 2 == 0: continue # skip even numbers if n > 7: break # stop after 7 print(n) ``` See [examples/control_flow/while_break_continue.au](../examples/control_flow/while_break_continue.au). ## `pass` Use `pass` when a block must exist but has no statements. This is the same as Python: ```aura check-pass class Empty: pass def noop(): pass ``` See [examples/basics/pass_keyword.au](../examples/basics/pass_keyword.au). ## `for` Over `range` ```aura check-pass mut total: int64 = 0 for value in range(6): if value == 3: continue if value == 5: break total += value ``` `range(stop)` counts from `0` to `stop - 1`. `range(start, stop)` counts from `start` to `stop - 1`. See [examples/control_flow/for_range.au](../examples/control_flow/for_range.au). ## `for` Over Collections Lists and sets can be iterated in ownership modes. The choice matters because of Aura's ownership model (see [06-ownership-and-borrowing.md](06-ownership-and-borrowing.md)): **Bare/default** -- reads through a shared borrow. The collection stays valid: ```aura check-pass names = ["Ada", "Grace"] for name in names: print(name) print(names.len()) # still usable ``` **Owned** -- consumes the collection. After the loop, it is no longer valid: ```aura check-pass def main(): names = ["Ada", "Grace"] for name in own names: print(name) # names is consumed -- cannot use it after this loop ``` `for name in names:` is the explicit spelling of shared iteration. **By mutable borrow** -- modifies elements in place. Requires a `mut` binding: ```aura check-pass mut scores = [1, 2, 3] for item in mut scores: item += 1 # scores is now [2, 3, 4] ``` Use bare `for x in collection` for ordinary reads, `for x in own collection` when you are done with it, and `for x in mut collection` when you need to update list elements. See [examples/collections/list_iteration.au](../examples/collections/list_iteration.au) and [examples/collections/list_polish.au](../examples/collections/list_polish.au). Sets support bare shared and `own` iteration: ```aura check-pass seen = {1, 2, 3} for value in seen: print(value) ``` See [examples/collections/set_basics.au](../examples/collections/set_basics.au). ## `enumerate` And `zip` `enumerate(...)` gives you the position alongside each value: ```aura check-pass hosts = ["alpha", "beta"] for index, host in enumerate(hosts): print(f"{index}: {host}") ``` `zip(...)` walks two sequences together and stops at the shorter one: ```aura fragment ports = [80, 443, 8080] for host, port in zip(hosts, ports): print(f"{host}:{port}") ``` Both are compiler-known `for` loop forms. They do not produce values that can be stored, so `pairs = enumerate(hosts)` is rejected with guidance to use the loop spelling. Both read their operands by position, so each one must be a `list[T]` or a `set[T]`, and both iterate over the bare-loop shared default: no `own` or `mut` modifier, the operands stay borrowed for the whole loop, and a non-copy element binding cannot be moved out. If you define your own `enumerate` or `zip` function, yours wins. See [examples/control_flow/enumerate_and_zip.au](../examples/control_flow/enumerate_and_zip.au). ## Comprehensions Comprehensions package nested bare loops and filters into an eager collection expression: ```aura check-pass values = [1, 2, 3, 4] even_squares = [value * value for value in values if value % 2 == 0] pairs = [ left * 10 + right for left in values if left < 3 for right in values if right < 3 ] ``` The output expression is written first, but the first iterable runs first. Filters run left to right and nested clauses are outer-major, so `pairs` is `[11, 12, 21, 22]`. A dictionary comprehension evaluates its key before its value: ```aura fragment labels = {value: value * 10 for value in values if value >= 3} ``` Every clause uses the bare-loop contract. List and set sources are shared, Range yields copy values, `enumerate`/`zip` keep their loop behavior, and Queue receives owned items through its existing carve-out. Comprehension targets do not leak outside the expression. There is no `mut`/`own` clause spelling and no lazy generator expression. Use an explicit loop when you need mutation, `break`, `continue`, or incremental stream processing. See [examples/collections/comprehensions.au](../examples/collections/comprehensions.au). ## Current Limits The current compiler supports `for` over: - `range(stop)` and `range(start, stop)` with named-argument forms - bare/`own` `list[T]`, plus `mut list[T]` - bare/`own` `set[T]` - `Queue[T]` (iterates until the queue closes) It also supports the `enumerate(seq)` and `zip(first, second)` loop forms over `list[T]` and `set[T]`. Not yet supported: - user-defined iterable protocols - `enumerate` or `zip` over a `Range` or `Queue[T]` - `mut set[T]` - custom step values for `range` Queue iteration is different: it receives each item already owned, and the Queue handle is copyable. The explicit `own` and `mut` forms are rejected for Queue; use `for item in queue:`. ## Source: tutorials/05-classes-and-data.md # Classes And Data The implemented class model currently covers fields, default values, positional and named construction, member access, `public` fields and methods, instance methods, associated methods, mutating methods, explicit `copy class` declarations, and indirect recursive fields. ## Declaring A Class ```aura check-pass class Point: x: float64 y: float64 ``` See [examples/classes/point_distance.au](../examples/classes/point_distance.au). Generic classes are also supported: ```aura check-pass class Box[T]: value: T ``` Aura also supports explicit copy classes when every field is itself copyable: ```aura check-pass copy class Point: x: int32 y: int32 ``` See [examples/classes/copy_class.au](../examples/classes/copy_class.au). ## Constructing A Value Class construction accepts named fields: ```aura fragment p1 = Point(x=0.0, y=0.0) ``` ## Accessing Fields ```aura fragment dx = a.x - b.x ``` Reading a non-copy field from an owned value moves that field out of the instance. You can still read other untouched fields, but you cannot read the moved field again until you assign a new value back into it. See [06-ownership-and-borrowing.md](06-ownership-and-borrowing.md) for a full explanation of move semantics, copy types, and common patterns for working with fields. ## Default Field Values The implemented subset supports field defaults: ```aura check-pass class ServerConfig: host: str = "localhost" port: int32 = 8080 ``` You can then omit those fields during construction: ```aura fragment local = ServerConfig() named = ServerConfig(host="aura.dev") ``` See [examples/classes/default_fields.au](../examples/classes/default_fields.au). ## Recursive Fields With `indirect` Recursive class fields must be marked `indirect`. This gives the child an out-of-line representation and keeps the parent size finite: ```aura check-pass class Node: value: int32 next: indirect Node? ``` The `?` suffix is shorthand for `Option[...]`, so `indirect Node?` means an optional owned child stored indirectly. See [examples/classes/indirect_recursive.au](../examples/classes/indirect_recursive.au). ## `public` Fields And Methods Aura enforces class visibility across module boundaries. Fields and methods are private by default and must be marked `public` to be used from another module: ```aura check-pass class User: public name: str age: int32 public def read_name(self) -> str: return self.name.clone() ``` Within the same module, private fields and methods are still accessible. Across modules: - constructing a class by keyword arguments only exposes `public` participating fields - reading a private field is rejected - calling a private method is rejected See [examples/modules/simple_import.au](../examples/modules/simple_import.au). ## Methods Aura supports methods declared directly inside the class body. ```aura check-pass class Counter: value: int32 def read(self) -> int32: return self.value ``` ## Receiver Forms The current compiler accepts these receiver forms. For a full explanation of how borrowing works and why these distinctions matter, see [06-ownership-and-borrowing.md](06-ownership-and-borrowing.md). - `self` - shared receiver and the default spelling; read-only access - `own self` - consuming receiver; takes ownership of a non-copy instance - `mut self` - mutable receiver; exclusive access, can modify fields in place - no receiver - associated method; called on the class, not an instance A receiver must be first and is never typed explicitly. `self: Counter` is rejected because it looks like an instance receiver but would otherwise be an ordinary parameter; use `self`, `own self`, or `mut self`. Example: ```aura check-pass class Counter: value: int32 def take(own self) -> int32: return self.value def read(self) -> int32: return self.value def bump(mut self): self.value += 1 def zero() -> Counter: return Counter(value=0) ``` Call them through an instance: ```aura fragment counter = Counter(value=4) print(counter.read()) ``` Method calls follow the same argument rules as ordinary functions, so methods and associated methods can also use named arguments: ```aura check-pass class Greeter: prefix: str def say(self, name: str) -> str: return self.prefix + name def named(prefix: own str) -> Greeter: return Greeter(prefix=prefix) greeter = Greeter.named(prefix="hello, ") print(greeter.say(name="aura")) ``` ## Associated Methods Methods without a receiver are called through the class name: ```aura check-pass class Counter: value: int32 def zero() -> Counter: return Counter(value=0) ``` ```aura fragment print(Counter.zero().read()) ``` See [examples/classes/methods.au](../examples/classes/methods.au). ## Mutating Methods Aura supports `mut self` methods and member-target assignment. ```aura check-pass class Counter: value: int32 def bump(mut self): self.value += 1 def reset(mut self): self.value = 0 ``` ```aura fragment mut counter = Counter(value=4) counter.bump() counter.reset() ``` See [examples/classes/mutating_methods.au](../examples/classes/mutating_methods.au). Constructors support positional field arguments as long as they come before any named fields: ```aura check-pass class Point: x: int32 y: int32 = 9 first = Point(1, 2) second = Point(7) ``` ## Current Limits The bootstrap compiler does not yet support: - separate `impl` blocks ## Source: tutorials/06-ownership-and-borrowing.md # Ownership And Borrowing If you are coming from Python, this is the most important chapter in the tutorial. Aura does not use a garbage collector. Instead, it tracks who owns each value and when that value can be freed. This system is called **ownership**, and the way you temporarily lend values without giving them away is called **borrowing**. This chapter walks through the full model with practical examples, explains why the rules exist, and shows you how to fix every common compiler error you will encounter. ## Why Ownership? In Python, every value lives on a heap and a garbage collector cleans up when nothing points to it anymore. This is simple, but it has costs: unpredictable pauses, higher memory use, and no deterministic cleanup. Aura takes a different approach. Every value has exactly **one owner** at any point in time. When the owner goes out of scope, the value is freed immediately. No garbage collector, no reference counting, no surprises. This gives you: - **Predictable performance** -- no GC pauses - **Deterministic cleanup** -- resources like files and connections close at a known point - **Memory safety** -- the compiler rejects programs that would read freed or invalid memory The trade-off is that you need to think about who owns what. The compiler enforces the rules and gives you clear error messages when something is wrong. ## Copy Types vs Move Types Aura divides all types into two categories: **copy types** and **move types**. Understanding this distinction is the foundation of everything that follows. ### Copy types Copy types are small, fixed-size values that are cheap to duplicate. When you assign a copy type to a new binding or pass it to a function, Aura silently makes a copy. Both the original and the new binding are fully independent. The built-in copy types are: - all integer types: `int` (the `int64` alias), `int8`, `int16`, `int32`, `int64`, `int128`, `intsize` - all unsigned types: `uint8`, `uint16`, `uint32`, `uint64`, `uint128`, `uintsize` - `float32`, `float64` - `bool` - `Duration` Copy types behave the way Python developers expect: ```aura check-pass x: int32 = 10 y = x # copies the value print(x) # 10 -- still usable print(y) # 10 -- independent copy ``` There is no surprise here. You can use `x` and `y` freely because integers are copy types. ### Move types Move types are values that own heap-allocated data or manage a unique resource. When you assign a move type to a new binding, Aura **moves** ownership. The original binding becomes invalid. The built-in move types include: - `str` - `list[T]`, `dict[K, V]`, `set[T]` - `random.Rng` - `TaskGroup` - user-defined classes (by default) `Queue[T]` is a copy handle to shared runtime state. `Task[T]` is always safe to transfer between tasks, but it is copyable only when its result can be observed repeatedly: `T` must be copyable, a `Queue[...]` handle, or a recursively repeatable `Task[...]` handle. A task returning `str`, `list[...]`, or another non-copy owned value therefore has a move-only handle. Copying an allowed handle never copies a queued value or task result. Here is where Python intuition breaks down: ```aura check-pass def main(): name: str = "aura" other = name # ownership moves to `other` print(other) # "aura" -- works fine ``` If you try to use `name` after the move: ```aura check-fail:AU3001 def main(): name: str = "aura" other = name print(other) print(name) # COMPILE ERROR ``` The compiler rejects this with: ``` error: use of moved value `name` ``` **Why does this happen?** After `other = name`, the `other` binding owns the string data. If `name` were still valid, you would have two bindings pointing to the same heap memory. When both go out of scope, the memory would be freed twice -- a crash. Aura prevents this at compile time. ### The Python comparison | Python | Aura | |--------|--------| | `y = x` always creates a reference, both point to the same object | `y = x` copies for copy types, moves for move types | | Garbage collector handles cleanup | Owner handles cleanup when it goes out of scope | | You never think about who owns what | You always know who owns what | ## Cloning: Explicit Copies Of Move Types When a move type supports independent duplication, call `.clone()`: ```aura check-pass name: str = "aura" other = name.clone() # explicit copy -- name stays valid print(name) # "aura" print(other) # "aura" ``` Collections expose `copy()`: ```aura check-pass def main(): mut xs: list[int32] = [1, 2, 3] ys = xs.copy() # independent copy xs.append(4) print(xs.len()) # 4 print(ys.len()) # 3 -- unaffected ``` Explicit duplication makes the allocation and element-copying cost visible. Assignment continues to follow the ordinary copy-or-move rule. Move types are not automatically cloneable. `random.Rng` exposes no clone route, and a class, enum, or collection containing one cannot be cloned through a public clone-producing operation. Generic clone helpers infer this requirement and reject an unsafe concrete specialization with `AU3007`. List and str slices are another explicit owned-copy boundary: ```aura check-pass names = ["Ada", "Grace", "Margaret"] selected = names[1:] # fresh owned list[str] label = "A🎉Z"[1:2] # fresh owned str containing 🎉 print(names.len()) # the sources remain valid ``` A list slice copies Copy elements and clones non-Copy elements, so its element type must be clone-safe. It rejects a value containing `random.Rng` with `AU3007` and a non-repeatable Task result right with `AU3009`. A str slice copies its Unicode-scalar range. Neither slice is a view: mutating the returned List cannot mutate the source, and the slice cannot be an assignment target. ## Closures Capture By Value A contextually typed lambda owns every outer local it uses: ```aura check-pass def main(): label = "compile" length: def() -> int64 = lambda: label.len() print(length()) print(length()) ``` `label` moves into the closure when the lambda expression is evaluated. Both calls work because the body only reads its capture. If the body consumed a non-copy capture, the call would consume the closure and a second call would report `AU3001`. Copy captures are snapshots and leave their sources usable. When outer code also needs a non-copy value, clone before creating the closure: ```aura check-pass def main(): label = "compile" captured = label.clone() length: def() -> int64 = lambda: captured.len() print(label) print(length()) ``` Bare and `mut` enclosing parameters are borrowed capabilities and cannot be captured. Closure environments are read-only in Phase 6.3. A closure may cross a task boundary only when every captured value is Transfer. Stored and arbitrary parameter `def` types remain capture-free. Keep a capturing closure in an immutable local, call it directly, pass it to a compiler-known repeatable callback, or move a qualifying closure into one task start; do not erase its environment metadata through a field, collection, or annotated return. ## Passing Values To Functions Bare function parameters grant logical shared access for every type. An implementation may pass copy bits directly, but that does not change the source-level contract. To transfer a move value to a function, write `own`: ```aura check-fail:AU3001 class Document: title: str pages: int32 def archive(doc: own Document): print(doc.title) def main(): doc = Document(title="Report", pages=42) archive(doc) print(doc.pages) # COMPILE ERROR: use of moved value `doc` ``` The explicit `own` parameter took ownership of `doc`. After the call, `doc` is no longer valid in the calling scope. If the declaration were simply `doc: Document`, it would borrow and the caller could keep using it. For copy types, shared access can be implemented by passing copied bits: ```aura check-pass def double(x: int32) -> int32: return x * 2 value: int32 = 5 print(double(value)) # 10 print(value) # 5 -- still valid, it was copied ``` ## Borrowing: Lending Without Giving Away Most of the time you want a function to read or modify a value without taking ownership. This is what **borrowing** does. A borrow is a temporary loan: the function can access the value, but the caller keeps ownership. Aura has two kinds of borrows: - `T` -- shared, read-only access - `mut T` -- exclusive, mutable access ### Shared access with a bare type A shared borrow lets a function read a value without consuming it: ```aura check-pass class Counter: value: int32 def read(counter: Counter) -> int32: return counter.value mut counter = Counter(value=41) print(read(counter)) # 41 print(counter.value) # 41 -- counter still belongs to us ``` The bare `counter: Counter` declaration is the shared contract: this function is looking, not taking. After the call returns, the borrow ends and the caller still owns the value. You can have multiple shared borrows active at the same time because none of them can modify the value: ```aura fragment def sum_values(a: Counter, b: Counter) -> int32: return a.value + b.value c1 = Counter(value=10) c2 = Counter(value=20) print(sum_values(c1, c2)) # 30 -- both still valid ``` ### Mutable borrows with `mut T` A mutable borrow lets a function modify the value in place: ```aura fragment def bump(counter: mut Counter): counter.value += 1 mut counter = Counter(value=41) bump(counter) print(counter.value) # 42 -- the change persisted ``` The caller must declare the binding as `mut` because the function will modify it. If the binding is not mutable, the compiler rejects the call: ```aura fragment counter = Counter(value=41) # not mutable bump(counter) # COMPILE ERROR ``` ``` error: argument for parameter `counter` in function `bump` must be a mutable place ``` ### The exclusivity rule You cannot have mutable access and another overlapping access to the same value at the same time. This prevents data races and aliasing bugs: ```aura fragment def bad(a: mut Counter, b: Counter): a.value += b.value mut c = Counter(value=1) bad(c, c) # COMPILE ERROR: overlapping access ``` **Why does this rule exist?** Imagine `bad` increments `a.value` while reading `b.value` -- but `a` and `b` are the same object. The final result would depend on the order of operations inside the function, creating a subtle bug. Aura prevents this entirely. Think of it like a library book: many people can read it at the same time (shared borrows), or one person can take it home to annotate it (mutable borrow), but you cannot do both at once. ## Method Receivers Methods on classes use the same borrowing system through **receivers**. The receiver determines what the method can do with the instance: ### `self` -- read the instance ```aura check-pass class Account: balance: float64 def display(self) -> str: return f"Balance: {self.balance}" ``` Bare `self` is shared access. The method can read fields but cannot modify them, and the caller retains ownership. ```aura fragment account = Account(balance=100.0) print(account.display()) # "Balance: 100.0" print(account.balance) # still accessible ``` ### `mut self` -- modify the instance ```aura check-pass class Account: balance: float64 def deposit(mut self, amount: float64): self.balance += amount def display(self) -> str: return f"Balance: {self.balance}" ``` The method can read and write fields. The instance must be declared `mut`: ```aura fragment mut account = Account(balance=100.0) account.deposit(50.0) print(account.display()) # "Balance: 150.0" ``` If you forget `mut`: ```aura fragment account = Account(balance=100.0) account.deposit(50.0) # COMPILE ERROR: must be a mutable place ``` ### `own self` -- consume the instance ```aura check-pass class Connection: host: str def into_host(own self) -> str: return self.host ``` An `own self` receiver takes ownership. A non-copy instance is consumed after the call: ```aura fragment conn = Connection(host="example.com") host = conn.into_host() print(host) # "example.com" print(conn.host) # COMPILE ERROR: use of moved value `conn` ``` Use `own self` when the method needs to disassemble the instance or transfer ownership of its fields. ### No receiver -- associated methods Methods without a receiver are called on the class itself, not on an instance: ```aura check-pass class Counter: value: int32 def zero() -> Counter: return Counter(value=0) ``` ```aura fragment c = Counter.zero() ``` ### Choosing the right receiver | Receiver | When to use | Example | |----------|-------------|---------| | `self` | Read-only shared access, the default | getters, display, serialization | | `mut self` | Modify the instance in place | setters, increment, append | | `own self` | Consume the instance to extract data | `into_*` conversions, one-shot use | | no receiver | Factory methods and utilities that do not need an instance | `Counter.zero()` | If you are not sure, start with bare `self`. Add `own` only when the method must consume the instance, or `mut` when it must mutate in place. ## Field Access And Move Semantics When you own a value, reading a non-copy field **moves** that field out of the instance: ```aura check-fail:AU3001 class User: name: str age: int32 def main(): user = User(name="Ada", age=36) greeting = user.name # moves `name` out of `user` print(greeting) # "Ada" print(user.age) # 36 -- copy field, still fine print(user.name) # COMPILE ERROR: use of moved field `name` from `user` ``` ``` error: use of moved field `name` from `user` ``` **Why?** The `str` in `user.name` is a move type. Reading it transfers ownership to `greeting`. The `user` instance no longer has a valid `name` field. The `age` field is `int32` (a copy type), so it is unaffected. ### Reading fields from borrowed values When you borrow a value, you cannot move non-copy fields out of it because you do not own it: ```aura fragment def get_name(user: User) -> str: return user.name # COMPILE ERROR ``` ``` error: cannot move non-copy field `name` out of borrowed value `user` ``` The function only borrowed `user` -- it has no right to take the `name` away. The fix depends on what you need: **Option 1: clone the field** ```aura fragment def get_name(user: User) -> str: return user.name.clone() # explicit copy, user keeps its name ``` **Option 2: take ownership of the whole value** ```aura fragment def get_name(user: own User) -> str: return user.name # consumes user, moves name out ``` **Option 3: return a copy-type field instead** ```aura fragment def get_age(user: User) -> int32: return user.age # int32 is copy, no move needed ``` ## Copy Classes By default, user-defined classes are move types. You can make a class copyable with `copy class`, but only if every field is itself a copy type: ```aura check-pass copy class Point: x: int32 y: int32 p1 = Point(x=1, y=2) p2 = p1 # copies, both valid print(p1.x) # 1 print(p2.x) # 1 ``` If any field is a move type, the compiler rejects the `copy` annotation: ```aura check-fail:AU2002 copy class Bad: name: str # COMPILE ERROR value: int32 ``` ``` error: field `name` on `copy class Bad` must be a copy type, found `str` ``` **When to use `copy class`:** Use it for small, value-like types where copying is cheap and expected -- coordinates, colors, dimensions, ranges. Do not use it for types that hold resources or large data. ## Borrowing In Loops Loops use the same readable default. Bare `list` and `set` iteration borrows the collection, so it remains usable: ```aura check-pass mut names: list[str] = ["Ada", "Grace", "Margaret"] for name in names: print(name) print(names.len()) # 3 -- still usable ``` Write `own` when you intend to move each element out and consume the list: ```aura check-pass def main(): names: list[str] = ["Ada", "Grace", "Margaret"] for name in own names: print(name) # names is moved ``` **Note:** Even `list[int32]` is itself a move type, but its bare loop still borrows. Only `own` consumes it: ```aura check-pass mut xs: list[int32] = [1, 2, 3] for x in xs: print(x) for x in own xs: print(x) # another use of xs would now be an error ``` ### Bare shared iteration Bare iteration is the shared form: ```aura check-pass mut names: list[str] = ["Ada", "Grace", "Margaret"] for name in names: print(name) print(names.len()) # 3 -- names is still valid for name in names: # can iterate again print(name) ``` For copy element types, the loop variable receives a copy of each element. For non-copy element types, the loop variable is a temporary borrow. ### Mutable borrow iteration with `mut` To modify elements during iteration, use `for ... in mut`: ```aura check-pass class Score: value: int32 def double(mut self): self.value = self.value * 2 mut scores: list[Score] = [Score(value=1), Score(value=2), Score(value=3)] for score in mut scores: score.double() for score in scores: print(score.value) # prints: 2, 4, 6 ``` This requires the collection binding to be `mut`. ### Which iteration form to use | Form | Effect | Use when | |------|--------|----------| | `for x in collection` | Shared borrow, collection stays valid | Ordinary read-only iteration | | `for x in own collection` | Consumes the collection | You are done with the collection after the loop | | `for x in mut collection` | Mutable borrow, can modify elements | You want to update elements in place | **Default recommendation:** Use bare `for x in collection` for reads, `own` to consume, and `mut` to update. ### Comprehensions use the bare form A comprehension is the eager expression counterpart of nested bare loops: ```aura check-pass names = ["Ada", "Grace"] lengths = [name.len() for name in names] copies = [name.clone() for name in names] ``` The result collection is newly owned, while a list or set clause shares and freezes its source. `name.len()` only reads the shared `str`. Storing the non-copy `str` itself requires the explicit `.clone()` shown in `copies`; the compiler never inserts that clone. Comprehension clauses have no `mut` or `own` modifier. Use a statement loop for mutable or consuming collection traversal. Queue preserves its bare-loop exception: each received item arrives owned and may move directly into the eager result. Every target disappears after the closing delimiter. ## Borrowing In Match Pattern matching follows the same ownership rules. Bare `match` shares the value, so the caller keeps ownership: ```aura check-pass result: Result[str, str] = Result.Ok("success") match result: case Ok(msg): print(msg) case Err(e): print(e) print(result) # still valid ``` To consume the value and receive owned payloads, use `match own`: ```aura check-pass def main(): result: Result[str, str] = Result.Ok("success") match own result: case Ok(msg): print(msg) # msg is owned case Err(e): print(e) # result is moved ``` To match and mutate the payload, use `match mut`: ```aura check-pass mut result: Result[str, str] = Result.Ok("hello") match mut result: case Ok(msg): # msg is mut str -- can call mutating methods pass case Err(e): pass ``` ## Borrowing And Concurrency Queues transfer ownership of sent values. When you put a value into a queue, it moves: ```aura check-pass jobs = Queue[str]() jobs.put("hello") # "hello" moves into the queue # the sent string is now owned by whichever task receives it ``` Queue construction and sending require the payload type to satisfy Aura's compiler-derived `Transfer` rule. Copy values, `str`, and aggregates whose stored components are all `Transfer` may cross. `random.Rng`, `TaskGroup`, shared or mutable access, and live file, process, or network resources may not. Keep a live resource on the task that owns it and exchange owned descriptions, bytes, snapshot results, or queue/task handles instead. Queue handles are cheap copy references. Passing a queue to `TaskGroup.start(...)` shares the same underlying queue; you do not need `.clone()` for the common case: ```aura check-pass def send_message(jobs: Queue[str]): jobs.put("from task") jobs.close() jobs = Queue[str]() with TaskGroup() as group: task = group.start(send_message, jobs) match jobs.get(): case QueueReceive.Item(value): print(value) # "from task" case QueueReceive.Closed: pass case QueueReceive.TimedOut: pass case QueueReceive.Cancelled: pass task.result() ``` Every task argument and result must also be structurally `Transfer`. This rule is checked after generic specialization. A task target may borrow from its task-owned capture through a bare parameter, but the captured value itself crosses by ownership. Task result observation has a separate repeatability rule. A copy result, a `Queue[...]` result, or a recursively repeatable `Task[...]` result may be observed repeatedly. For any other transferable result, `result()`, `result_or_none()`, and `result_or()` consume the task handle on the first attempt, even if that attempt times out, is cancelled, fails, or returns a fallback. `wait_any` and `wait_all` consume the complete task list for such results; `wait_any` deliberately abandons the unchosen observation rights. ## Common Patterns And Fixes ### Pattern: "I need to use a value after passing it to a function" **Problem:** ```aura fragment def archive(doc: own Document): print(doc.title) doc = Document(title="Report", pages=10) archive(doc) print(doc.title) # COMPILE ERROR: use of moved value ``` **Fix 1 -- remove `own` to use the bare shared-borrow default:** ```aura fragment def archive(doc: Document): print(doc.title) ``` The bare `doc: Document` declaration is the shared spelling. **Fix 2 -- keep the owned parameter and clone before passing:** ```aura fragment archive(doc.clone()) print(doc.title) # doc still valid ``` ### Pattern: "I need to read a str field without consuming the owner" **Problem:** ```aura fragment def get_title(doc: Document) -> str: return doc.title # COMPILE ERROR: cannot move out of shared access ``` **Fix -- clone the field:** ```aura fragment def get_title(doc: Document) -> str: return doc.title.clone() ``` ### Pattern: "I need to consume collection elements" **Problem:** ```aura fragment for item in items: inspect(item) print(items.len()) # still available ``` **Use `own` when the consumer needs owned items:** ```aura fragment for item in own items: process(item) # items is now moved ``` ### Pattern: "I need to modify elements in a collection" **Problem:** ```aura fragment for score in scores: score.double() # COMPILE ERROR: not mutable ``` **Fix -- mutable borrow iterate:** ```aura fragment for score in mut scores: score.double() ``` ### Pattern: "The compiler says my binding must be mutable" **Problem:** ```aura fragment counter = Counter(value=0) counter.bump() # COMPILE ERROR: must be a mutable place ``` **Fix -- declare with `mut`:** ```aura fragment mut counter = Counter(value=0) counter.bump() ``` ## Mental Model For Python Developers Here is how to translate your Python intuition: | Python concept | Aura equivalent | |----------------|-------------------| | `x = y` (always a reference) | `x = y` copies if copy type, moves if move type | | `x = copy.deepcopy(y)` | `x = y.copy()` for collections; `x = y.clone()` for other clone-safe move types that expose it | | `def f(x): ...` reads x | `def f(x: T): ...` for shared access | | `def f(x): x.mutate()` | `def f(x: mut T): ...` | | `del x` (deferred to GC) | Automatic when owner goes out of scope | | `for x in list: ...` (list survives) | `for x in list: ...` (shared; list survives) | | No direct equivalent | `for x in own list: ...` (list consumed) | The key shift is: in Python, assignment creates aliases. In Aura, assignment transfers ownership. Once you internalize this, the rest of the system follows naturally. ## Summary 1. Every value has one owner. When the owner goes out of scope, the value is freed. 2. Copy types (numbers, `bool`, `Duration`) are duplicated on assignment. Move types (`str`, `list`, `random.Rng`, classes) transfer ownership. 3. Use collection `.copy()` or the `.clone()` method exposed by another clone-safe move type when you need an independent owned value; `random.Rng` and values containing it support neither operation. 4. Bare parameters grant logical shared access for every type. Use `mut T` to lend mutable access and `own T` to transfer ownership. 5. `mut` access is exclusive -- no other overlapping access can exist at the same time. 6. Method receivers follow the same rules: `self` reads, `mut self` modifies, and `own self` consumes. 7. Bare collection iteration is shared. Use `for x in own collection` to consume and `for x in mut collection` to modify elements. 8. Use `match value` to pattern-match without consuming. 9. Queues transfer ownership of sent values and admit only structurally `Transfer` payloads. Queue handles are copy values. 10. Task captures and results must be structurally `Transfer`. A `Task[T]` handle is copyable only for a repeatable `T`; otherwise the first result attempt consumes its unique observation right. The compiler enforces all of these rules. When you see an error about moved values or borrowing, come back to this chapter -- the fix is almost always one of the patterns listed above. ## Source: tutorials/07-strings-and-numbers.md # Strings And Numbers Aura supports enough numeric and string behavior for real programs. This chapter covers arithmetic, string operations, parsing, formatting, and the numeric type system. ## Arithmetic The standard arithmetic operators work on matching numeric types: ```aura check-pass a: int32 = 6 b: int32 = 10 print(a + b) # 16 print(a - b) # -4 print(a * b) # 60 print(b // a) # 1 (floor division) print(b % a) # 4 print(-b // a) # -2 print(-b % a) # 2 ``` Integer `/` is intentionally rejected: it is too easy to misread as either truncating integer division or floating true division. Use `//` for a floor quotient. Both `//` and `%` follow the divisor-sign rule, including when either operand is negative: ```aura check-pass print(7 // -3) # -3 print(7 % -3) # -2 print(-7 // 3) # -3 print(-7 % 3) # 2 ``` The identity `a == (a // b) * b + (a % b)` holds for nonzero integer `b`. Integer `//` and `%` by zero fail at runtime. Floating-point `/` remains true division: ```aura check-pass print(7.0 / 2.0) # 3.5 ``` When the inputs are integers and true division is intended, convert both with `.to_float()`: ```aura check-pass numerator: int64 = 7 denominator: int64 = 2 print(numerator.to_float() / denominator.to_float()) # 3.5 ``` Every integer type has `.to_float() -> float64`. It rounds to the nearest representable IEEE-754 value using ties-to-even, so large integers may change: ```aura check-pass large: int64 = 9007199254740993 print(large.to_float()) # 9007199254740992.0 ``` Floating values also support `//` and `%`. They use the CPython-compatible floor/divmod correction, so the remainder follows the divisor's sign even where a naive host remainder would not. Floating `/`, `//`, and `%` by zero fail at runtime. ```aura check-pass print(-10.5 // 3.0) # -4.0 print(-10.5 % 3.0) # 1.5 ``` The matching compound assignments are `+=`, `-=`, `*=`, `**=`, `/=`, `%=`, and `//=`. Integer `/=` is rejected for the same reason as integer `/`; floating `/=` remains true division. `//` can also use the `FloorDiv` operator trait when no builtin numeric or Duration rule applies. Unary minus works on integers and floats: ```aura check-pass offset: int32 = -5 temperature: float64 = -3.5 ``` See [examples/numbers/unary_minus.au](../examples/numbers/unary_minus.au). Aura does not do implicit numeric widening. Mixed expressions like `int32 + int64` are rejected -- use explicit casts instead (see below). ## Integer Literal Bases Integer literals can use decimal, hexadecimal, binary, or octal notation. Underscores can group digits without changing the value: ```aura check-pass requests = 1_000_000 red: uint32 = 0xFF permissions: uint16 = 0o755 flags: uint8 = 0b1010_0110 ``` The prefixes are case-insensitive. An underscore must sit between two digits that are valid in the selected base. The sign remains a unary operator, so `-0x7F` means unary minus applied to `0x7F`. Contextual typing and integer range checks are identical for every literal spelling. ## Bitwise Operators And Shifts Every integer type supports `&`, `|`, `^`, `~`, `<<`, and `>>`. Both operands of a binary operation have the same exact integer type. This includes a shift count: ```aura check-pass value: uint32 = 0b1010_0000 mask: uint32 = 0b1111_0000 four: uint32 = 4 print(value & mask) # 160 print(value | 0b0000_1111) # 175 print(value ^ mask) # 80 print(~value) # 4294967135 print(value >> four) # 10 print(value << four) # 2560 ``` A shift count must be in `0..width`. Signed right shift extends the sign bit; unsigned right shift fills with zero. Ordinary left shift is checked and reports `AU4002` when the mathematical result does not fit. The compound forms are `&=`, `|=`, `^=`, `<<=`, and `>>=`. ## Power, Rounding, And Divmod `**` is right-associative and more tightly bound than unary minus on its left: ```aura check-pass print(2 ** 3 ** 2) # 512 print(-2 ** 2) # -4 print((-2) ** 2) # 4 print(2.0 ** -2.0) # 0.25 ``` Integer power preserves the exact integer type, rejects negative exponents, and checks overflow. Floating power preserves the exact floating type and reports defined domain and overflow failures. `round` uses nearest-integer ties-to-even for floating inputs and returns `int64`. An integer input is returned unchanged with its exact type: ```aura check-pass print(round(2.5)) # 2 print(round(3.5)) # 4 ``` `divmod(left, right)` evaluates both values once and returns the floor quotient and divisor-signed remainder together: ```aura check-pass quotient, remainder = divmod(-17, 5) print(quotient) # -4 print(remainder) # 3 ``` The two arguments have one exact integer or floating type. A zero divisor reports `AU4004`. ## Explicit Integer Arithmetic Modes Ordinary integer `+`, `-`, and `*` are checked and report `AU4002` if the mathematical result does not fit the integer type. Every integer type also provides explicit wrapping and saturating alternatives: ```aura check-pass top: int32 = 2147483647 print(top.wrapping_add(1)) # -2147483648 print(top.saturating_add(1)) # 2147483647 print(top.wrapping_sub(-1)) # -2147483648 print(top.saturating_mul(2)) # 2147483647 ``` Shift operations have the same explicit arithmetic modes. The count has the receiver's exact type and must remain below its bit width: ```aura check-pass high: uint8 = 0b1000_0000 one: uint8 = 1 print(high.wrapping_shl(one)) # 0 print(high.saturating_shl(one)) # 255 print(high.wrapping_shr(one)) # 64 print(high.saturating_shr(one)) # 64 ``` The two right-shift methods match ordinary `>>` after count validation. The same six method names are available on integer `Array[T]`. Their right operand is either another same-shape `Array[T]` or one scalar of exactly `T`. ## Numeric Arrays `Array[T]` provides fixed-shape, contiguous, row-major numeric storage for exactly `int32`, `int64`, `float32`, and `float64`: ```aura check-pass def square(value: float64) -> float64: return value * value matrix = Array[float64].from_list([1.0, 2.0, 3.0, 4.0], [2, 2]) squares = matrix.map[float64](square) first_row = squares[0:1] print(squares[1, 0]) # 9.0 print(first_row.sum()) # 5.0 print(squares.mean()) # 7.5 ``` Array/Array arithmetic requires the same dtype and exact shape. Scalar arithmetic requires exactly `T`; scalar operands work on either side of `+`, `-`, and `*`. `/` is available only for floating Arrays. There is no implicit dtype promotion or array-shape broadcasting. `sum`, `min`, and `max` return `T`; `mean` always returns `float64`. Floating `sum`, `min`, and `max` proceed left-to-right in row-major order with dtype rounding, floating `mean` accumulates as `float64`, and floating reductions propagate NaN. See [examples/numbers/numeric_arrays.au](../examples/numbers/numeric_arrays.au) and [Numeric Arrays](../docs/manual/numeric-arrays.md). ## Floating-Point Math Integer literals default to `int64`, whose shorter alias is `int`. Floating-point literals default to `float64`. Both adopt a compatible expected numeric type when the surrounding context requires it: ```aura check-pass count: int32 = 12 ratio: float32 = 3.25 whole: float64 = 2 ``` An integer literal adopts a `float32` or `float64` context only when its value is exactly representable in that type. This also makes mixed-literal arithmetic read naturally: `7.5 // 2` is floating floor division and `-7.5 % 2` is floating remainder. A bound integer variable is never widened this way. For an inexact value, use an explicit floating spelling when literal rounding is intentional, or call `.to_float()` when converting an integer value intentionally. Aura provides builtin numeric helpers: ```aura check-pass print(abs(-7)) # 7 print(min(9, 2)) # 2 print(max(4, 12)) # 12 print(sqrt(81.0)) # 9.0 ``` `float64` also has a `.sqrt()` method: ```aura check-pass value: float64 = 81.0 print(value.sqrt()) # 9.0 ``` Printed `float32` and `float64` values use the shortest decimal spelling that round-trips to the same source type. Whole-number floats keep a trailing `.0`, signed zero stays `-0.0`, and large or tiny values use concise scientific notation. For example, `9007199254740992.0`, `1e300`, and `1e-300` print without being routed through lower `float32` precision. See [examples/numbers/numeric_builtins.au](../examples/numbers/numeric_builtins.au), [examples/numbers/float_sqrt.au](../examples/numbers/float_sqrt.au), and [examples/numbers/bit_packing.au](../examples/numbers/bit_packing.au). ## `.to_string()` Primitive numeric and boolean values support `.to_string()`: ```aura check-pass count: int32 = 42 ok: bool = true print(count.to_string()) # "42" print(ok.to_string()) # "true" ``` ## Explicit Numeric Casts Use `expr as Type` to cast between numeric types: ```aura check-pass whole = 7.9 as int32 # 7 (truncates toward zero) narrowed = 1.25 as float32 widened = 3 as float64 ``` Integer casts are range-checked and never wrap. A provably invalid literal such as `300 as int8` is rejected while checking. A cast from a value computed at runtime is checked when it executes and traps cleanly if the value does not fit. Integer-to-float casts follow the same split: literals can be rejected while checking, and dynamic values are exactness-checked at runtime. Aura rejects a cast that would lose integer precision. That strict cast is intentionally different from `.to_float()`. For the `9007199254740993` value above, `large.to_float()` returns the rounded `9007199254740992.0`, while `large as float64` fails because the conversion is not exact. See [examples/numbers/numeric_casts.au](../examples/numbers/numeric_casts.au). The combined arithmetic example is [examples/basics/numbers.au](../examples/basics/numbers.au). ## The Full Numeric Type System | Signed | Unsigned | Float | |--------|----------|-------| | `int8` | `uint8` | `float32` | | `int16` | `uint16` | `float64` | | `int32` | `uint32` | | | `int64` | `uint64` | | | `int128` | `uint128` | | | `intsize` | `uintsize` | | Use `int` (the `int64` alias) and `float64` by default. Other explicit widths are useful when you need control over memory layout, value ranges, or a fixed API contract. APIs declared with `int32` remain `int32`; literal defaulting does not widen them. Full-range `uint128` arithmetic is supported: ```aura check-pass value: uint128 = 340282366920938463463374607431768211455 print(value) ``` See [examples/numbers/uint128_values.au](../examples/numbers/uint128_values.au). Annotated integer widths are enforced at runtime. If a value exceeds its annotated type's range, Aura reports an error and preserves the declared type. The bootstrap compiler also supports `float32` in typed contexts like class fields and function parameters: ```aura check-pass class Measurement: value: float32 def double(x: float32) -> float32: return x + x ``` See [examples/numbers/float32_values.au](../examples/numbers/float32_values.au). ## str Basics Ordinary strings use matching single or double quotes. Both forms produce the same `str`, support the same escapes, and concatenate with `+`: ```aura check-pass greeting = 'hello' + ", aura" apostrophe = 'Aura\'s strings' quotation = 'the compiler said "ready"' ``` The supported escapes are `\n`, `\t`, `\"`, `\'`, `\\`, `\0`, `\xHH`, and `\u{H...}`. A one-character literal remains a `str`. Aura has no character type. Use three matching quotes for exact multiline text. The compiler keeps the first newline, last newline, indentation, spaces, and physical tabs: ```aura check-pass prompt = """Summarize the request. Return JSON with a label and reason. """ ``` Use lowercase `r` for a single-line value where backslashes are data: ```aura check-pass model_dir = r"C:\models\agent" number_pattern = r'\d+\.\d+' ``` A raw string cannot end in an odd run of backslashes. Raw triple strings and byte strings are unavailable. ## F-Strings Interpolated strings use the double-quoted `f"..."` form and produce an owned `str`; `f'...'` is not supported: ```aura check-pass name: str = "Aura" answer: int32 = 42 print(f"Hello, {name} {answer}") print(f"{name:·^16.8s} {answer:>8,d}") print(f"success rate: {0.875:+.1%}") print(f"delta: {-1.25:09.3f}") ``` Interpolations accept any expression, including indexed lookups: ```aura fragment print(f"value: {counts['key']}") ``` A static format specification follows a top-level colon. It supports a one-scalar fill, `<`, `^`, and `>` alignment, numeric signs, minimum width, comma grouping, precision, and `d`, `f`, `e`, `x`, `X`, `b`, `o`, `%`, and `s` type codes. Width counts Unicode scalars. String precision truncates by Unicode scalar count. Numeric precision rounds ties to even. Specifications are checked against the interpolation's static type before execution. A numeric width beginning with `0` pads after the sign. Decimal grouping always uses an explicit `d`, `f`, or `%` code. See [examples/strings/f_strings.au](../examples/strings/f_strings.au). ## Borrowed str Parameters When a function takes a string it only reads, use `str`: ```aura check-pass def greet(name: str) -> str: return "Hello, " + name ``` See [examples/strings/borrow_str.au](../examples/strings/borrow_str.au). ## str Methods Aura provides a rich set of string methods: ```aura check-pass text = " aura repo " print(text.len()) # 15 print(text.contains("repo")) # true print(text.starts_with(" au")) # true print(text.ends_with(" ")) # true trimmed = text.trim() # "aura repo" parts = trimmed.split(" ") # ["aura", "repo"] print(trimmed.replace("repo", "lang")) # "aura lang" print(trimmed.to_lower()) # "aura repo" print(trimmed.to_upper()) # "AURA REPO" ``` `len()` counts Unicode scalar values, while `byte_len()` reports the number of bytes in the UTF-8 encoding. Both members return `int64`: ```aura check-pass text = 'A🎉' print(text.len()) # 2; O(n) print(text.byte_len()) # 5; O(1) ``` Integer indexing on `str` remains unavailable, but one-colon slicing returns a fresh owned str: ```aura check-pass text = "A🎉Z" print(text[1:2]) # 🎉 print(text[:2]) # A🎉 print(text[-2:]) # 🎉Z print(text[:]) # A🎉Z ``` Endpoints count Unicode scalar values, matching `len()`. They do not count UTF-8 bytes or grapheme clusters. Locating scalar boundaries scans the text, so String slicing is O(n). Written endpoints use `int64`; negatives normalize once. Both effective endpoints must lie in `0..=len`, and start must not exceed end. Aura does not clamp invalid bounds like Python: invalid or reversed ranges trap with `AU4003`. The result is an owned copy, not a view. Slice steps and slice assignment are unavailable. Character iteration, `ord()`, and `chr()` are also not implemented. Strict UTF-8 conversion is available through `text.to_bytes()` and `str.from_bytes(payload)`; hexadecimal, base64, typed conversion errors, and SHA-256 are taught in [22-bytes.md](22-bytes.md). An explicit `encoding` argument remains reserved but unimplemented. `strip_prefix(...)` and `strip_suffix(...)` return `Option[str]`, so they compose with `match`: ```aura fragment match trimmed.strip_prefix("aura "): case Some(rest): print(rest) # "repo" case None: print("no match") ``` `join(...)` uses the receiver as the separator: ```aura check-pass parts = ["aura", "lang", "tests"] print("-".join(parts)) # "aura-lang-tests" ``` `clone()` creates an independent copy of a string (see [06-ownership-and-borrowing.md](06-ownership-and-borrowing.md) for why this matters): ```aura check-pass text: str = "aura" copy = text.clone() print(text) # still valid print(copy) ``` See [examples/strings/string_methods.au](../examples/strings/string_methods.au) and [examples/strings/string_clone.au](../examples/strings/string_clone.au). ## Parsing And Formatting Aura provides parsing builtins that return `Result`: - `parse_int32(text: str) -> Result[int32, str]` - `parse_int64(text: str) -> Result[int64, str]` - `parse_float64(text: str) -> Result[float64, str]` Use `match` to handle success and failure: ```aura check-pass match parse_int32("42"): case Result.Ok(value): print(value) case Result.Err(message): print(message) ``` Combined with `.to_string()` and `str.join(...)`, these cover the maintained formatting surface. See [examples/strings/string_parsing_and_formatting.au](../examples/strings/string_parsing_and_formatting.au). ## str Equality Strings support `==` and `!=`: ```aura fragment if greeting == "hello, aura": print(greeting) ``` See [examples/strings/greeting.au](../examples/strings/greeting.au). ## Booleans And Comparisons The comparison operators produce `bool`: - `==`, `!=`, `<`, `<=`, `>`, `>=` - `and`, `or`, `not` ```aura fragment if score >= 90 and not failed: print("passed") ``` ## Duration Values Duration literals are used with the concurrency surface (see [13-concurrency.md](13-concurrency.md)): ```aura check-pass short_wait: Duration = 5ms normal_wait: Duration = 1s long_wait: Duration = 2m ``` The stored value is an exact signed i128 count of nanoseconds. Literals are non-negative integral counts with `ms`, `s`, or `m`; there is no `ns` suffix, fractional literal, or unary minus for Duration. Use the signed associated constructors when the count is computed: ```aura check-pass attempt: int64 = 3 base = Duration.ms(125) backoff = attempt * base split = 1ms // attempt print(backoff) # 375ms print(split) # 0.333333ms print(Duration.seconds(2) + 500ms) # 2500ms print(Duration.minutes(-1) < 0ms) # true print(Duration.ms(1500).to_seconds()) # 1.5 ``` Duration supports checked `+` and `-` with another Duration, `* int64` in either operand order, `// int64`, and all comparisons. `to_ms()` and `to_seconds()` return the nearest representable IEEE-754 binary64 value using ties-to-even and may round. Printing uses exact decimal milliseconds with at most six fractional digits and trimmed zeros. Negative values are useful in calculations but are rejected as sleeps, timeouts, deadlines, and restart backoffs. See [examples/concurrency/duration_arithmetic.au](../examples/concurrency/duration_arithmetic.au). ## Source: tutorials/08-tooling.md # Tooling Aura ships with compiler and editor tooling inside the monorepo. ## CLI The `aura` CLI is the primary interface for working with Aura programs: ```bash cargo run -p aura -- check myfile.au # type-check without running cargo run -p aura -- run myfile.au # execute through the MIR runtime cargo run -p aura -- build -o out myfile.au # compile to a native binary ``` For inspecting compiler internals: ```bash cargo run -p aura -- ast myfile.au # print the syntax tree cargo run -p aura -- ast-json myfile.au # syntax tree as JSON cargo run -p aura -- mir myfile.au # print the lowered MIR cargo run -p aura -- analyze myfile.au # diagnostics, symbols, hover info cargo run -p aura -- complete --line 5 --character 11 --trigger . myfile.au ``` See [01-running-programs.md](01-running-programs.md) for a full walkthrough of each command. The CLI is also documented in [crates/aura/README.md](../crates/aura/README.md). ## Examples The categorized example library under `examples/` is part of the development workflow, not just sample code. Compiler tests exercise the examples, so they stay valid as the language evolves. Browse them alongside these tutorials to see runnable code for every feature. ## VS Code The repo includes: - a VS Code extension under `tools/vscode-aura` - an Aura language server under `tools/aura-language-server` ### Editor Features - **Syntax highlighting** for `.au` files - **Completions** with member completion after `.` - **Hover** information showing types and signatures - **Go-to-definition** including cross-file definitions for imported symbols - **Diagnostics** from the compiler's type checker - **Document symbols** for navigation The editor uses compiler-backed analysis through `aura analyze` and `aura complete`. This means the editor and CLI share the same type-checking engine. The local JS analysis layer is kept only as a fallback when the compiler cannot analyze the current buffer. ### Installation For development: 1. Run `npm install` from the repo root 2. Run `npm run build:extension` 3. Open the repo in VS Code 4. Press `F5` to launch an Extension Development Host 5. Open any `.au` file For a packaged install, see [tools/vscode-aura/INSTALL.md](../tools/vscode-aura/INSTALL.md). ## Keeping Tutorials Current This tutorial set tracks the compiler, not the proposal. When a feature is added, changed, or removed: 1. update the relevant tutorial chapter 2. update or add an example program 3. update `14-current-language-surface.md` if the supported surface changed ## Source: tutorials/09-enums-and-match.md # Enums And Match Enums let you define a type that can be one of several variants. Combined with `match`, they give you exhaustive pattern matching -- the compiler guarantees you handle every case. ## Declaring An Enum ```aura check-pass enum TrafficLight: Red Yellow Green ``` Each variant belongs to the enum's namespace: `TrafficLight.Red`, `TrafficLight.Yellow`, etc. ## Variants With Payloads Variants can carry a single value: ```aura check-pass enum ParseResult: Success(int32) Failure(str) ok = ParseResult.Success(42) bad = ParseResult.Failure("invalid input") ``` Variant payloads are owned constructor positions. `Failure(str)` therefore acts like `Failure(own str)`, and the same is true of builtins such as `Option.Some(own T)` and `Result.Err(own E)`. ## Generic Enums Enums can be generic: ```aura check-pass enum Wrapper[T]: Item(T) Empty ``` You can provide explicit type arguments when the compiler needs help: ```aura check-pass wrapped = Result[int32, str].Ok(7) ``` See [examples/enums/explicit_type_args.au](../examples/enums/explicit_type_args.au). ## Exhaustive `match` Aura's `match` requires you to handle every variant. If you miss one, the compiler reports an error: ```aura fragment def value_or_zero(result: own ParseResult) -> int32: match result: case ParseResult.Success(value): return value case ParseResult.Failure(message): print(message) return 0 ``` ### Wildcard Arms Use `case _:` to match any remaining variants: ```aura fragment match light: case TrafficLight.Red: print("stop") case _: print("not red") ``` ### Payload Bindings When a case matches a payload variant, the payload becomes a local binding: ```aura fragment case ParseResult.Success(value): return value # value is an int32 here ``` ### Unqualified Variants When the scrutinee type is already known, you can omit the enum name: ```aura check-pass result: Result[str, str] = Result.Ok("ok") match result: case Ok(value): # same as Result.Ok(value) print(value) case Err(message): # same as Result.Err(message) print(message) ``` This is especially convenient with built-in enums like `Result` and `Option`. ## Borrowed Matching Bare `match` inspects without consuming the value. Write `match own` when an arm must receive owned payloads. This distinction matters for non-copy types (see [06-ownership-and-borrowing.md](06-ownership-and-borrowing.md)): ```aura check-pass result: Result[str, str] = Result.Ok("ok") match result: case Ok(value): print(value.clone()) # value is a borrowed str case Err(message): print(message) # result is still valid here ``` Use `match mut` when you need to modify the matched value: ```aura check-pass mut result: Result[str, str] = Result.Ok("hello") match mut result: case Ok(msg): pass # msg is mut str case Err(e): pass ``` The scrutinee may be a field such as `holder.state`. Reassigning that field, `holder`, or an ancestor field makes its payload bindings stale, while changing a separate sibling field is allowed. See [examples/enums/match_borrow_mut_fields.au](../examples/enums/match_borrow_mut_fields.au). See [examples/enums/match_borrow.au](../examples/enums/match_borrow.au). ## Literal Match Patterns You can also match on literal values of `bool`, integer, and `str`: ```aura check-pass def describe_number(value: int32) -> str: match value: case 0: return "zero" case 1: return "one" case _: return "many" ``` Boolean matches are exhaustive when they cover both `true` and `false`: ```aura check-pass def describe_flag(flag: bool) -> str: match flag: case true: return "yes" case false: return "no" ``` Integer and `str` matches always need a final wildcard arm because the domain is open-ended. See [examples/control_flow/match_literals.au](../examples/control_flow/match_literals.au). Nested patterns, expression-form `match`, floating-point literal patterns, keyword payload arguments, and multi-payload variants are also supported: ```aura check-pass enum Inner: Pair(int32, int32) enum Outer: Point(x: int32, y: int32) Wrapped(Inner) Empty def describe(value: Outer) -> int32: return match value: case Outer.Point(x, y): x + y case Outer.Wrapped(Inner.Pair(a, b)): a * b case Outer.Empty: 0 ``` See [examples/enums/rich_match.au](../examples/enums/rich_match.au). ## Guards And Or-Patterns A guard adds an exact Boolean condition after structural matching. An or-pattern lets one arm accept several structural alternatives: ```aura fragment match code: case 200 | 201 if code == 201: print("created") case 200 | 201: print("success") case _: print("other") ``` Alternatives are tested left to right and must bind the same names with the same types and capabilities. A false guard continues to the next arm. Guarded arms do not make a match exhaustive, so keep an unguarded fallback when the remaining domain is open. A lowercase name at the top level binds the complete scrutinee. The guarded form makes that name available to the condition, and the unguarded form is the final catch-all: ```aura fragment return match value: case whole if whole >= 0: whole case whole: 0 - whole ``` In `match own`, a guard can inspect a non-copy candidate but cannot move it. Extraction happens only after a true guard. In `match mut`, mutations made by a guard remain visible when the guard is false or propagates a failure. See [examples/enums/match_guards_and_or_patterns.au](../examples/enums/match_guards_and_or_patterns.au). Expression-form `match` is not limited to `return`. It also works in binding and argument positions, and an arm value may itself be a nested block-form expression: ```aura fragment value = match outer: case Outer.A: 10 case Outer.B: 20 emit(match outer: case Outer.A: match inner: case Inner.X: 1 case Inner.Y: 2 case Outer.B: 3) ``` See [examples/enums/match_expression_positions.au](../examples/enums/match_expression_positions.au). Built-in generic enums `Result[T, E]`, `Option[T]`, and `SendError[T]` are covered in the next chapter. See [examples/enums/result_match.au](../examples/enums/result_match.au) and [examples/enums/wildcard_match.au](../examples/enums/wildcard_match.au). ## Source: tutorials/10-results-and-options.md # Results And Options Aura represents typed success, failure, presence, and absence with `Result[T, E]`, `Option[T]`, and the queue-specific `SendError[T]`. These enums form the foundation of recoverable error handling in Aura. ## `Result[T, E]` Use `Result[T, E]` when an operation can succeed with a value of type `T` or fail with an error of type `E`: ```aura check-pass def divide(a: int32, b: int32) -> Result[int32, str]: if b == 0: return Result.Err("division by zero") return Result.Ok(a // b) ``` Handle the result with `match`: ```aura fragment match divide(10, 3): case Ok(value): print(f"result: {value}") case Err(message): print(f"error: {message}") ``` This is Aura's primary error-handling pattern. Instead of exceptions (like Python's `try/except`), Aura makes errors part of the return type so the compiler ensures you handle them. ## `Option[T]` Use `Option[T]` when a value may or may not be present: ```aura check-pass def find_user(id: int32) -> Option[str]: if id == 1: return Option.Some("Ada") return Option.None ``` Handle it with `match`: ```aura fragment match find_user(1): case Some(name): print(f"found: {name}") case None: print("not found") ``` You will see `Option[T]` throughout Aura's standard library. `list.get()`, `dict.get()`, `dict.remove()`, and `str.strip_prefix()` all return `Option` values. Those APIs do not all obtain `T` the same way. `dict.remove` transfers a stored value, while collection `get` clones one and therefore requires clone-safe `T`; a stored value containing `random.Rng` must be removed or otherwise transferred. ## `None` vs `Option.None` These look similar but are different: - **`None`** is the unit type and value. It means "no meaningful return value." A function with no `-> ...` returns `None`. - **`Option.None`** is the empty variant of `Option[T]`. It means "no value present in this optional slot." ```aura check-pass done: None = None # the unit value missing: Option[int32] = Option.None # an empty optional ``` In practice, the distinction is clear from context. When you see `Option.None` in a `match` arm, it always refers to the enum variant. `Option.Some(...)` can infer `T` from its payload even without an annotation: ```aura check-pass count = Option.Some(5) ``` `Option.None` still needs an expected `Option[T]` type because there is no payload to infer from: ```aura check-pass missing: Option[int32] = Option.None ``` ## `SendError[T]` `SendError[T]` is the error type returned when a queue send fails. It wraps the value that could not be sent, so you can recover it. `put` can report a closed queue, cancellation, or timeout; `try_put` reports `Full` when a bounded queue has no capacity: ```aura check-pass ch = Queue[int32]() ch.close() match ch.put(4): case Ok(done): print("sent") case Err(SendError.Closed(value)): print(f"queue closed, could not send {value}") case Err(SendError.Cancelled(value)): print(f"send cancelled, could not send {value}") case Err(SendError.TimedOut(value)): print(f"send timed out, could not send {value}") case Err(SendError.Full(value)): print(f"queue full, could not send {value}") ``` See [examples/concurrency/send_result.au](../examples/concurrency/send_result.au) for a full example. ## Composing Results A common pattern is chaining operations that each return `Result`. Use `match` to unwrap each step: ```aura check-pass def process(input: str) -> Result[int32, str]: match own parse_int32(input): case Ok(value): if value < 0: return Result.Err("negative value") return Result.Ok(value * 2) case Err(message): return Result.Err(message) ``` For simpler cases, Aura provides `try expr` to reduce the nesting. See [12-error-propagation.md](12-error-propagation.md). ## Retrying A Result Worker `control.retry` handles the common policy where every `Err` is retryable: ```aura check-pass import control def attempt() -> Result[int32, str]: return Result.Err("not ready") result = control.retry( attempt, max_attempts=3, initial_backoff=10ms ) ``` The first attempt runs immediately. Later attempts wait for the initial backoff and then twice the previous delay. A zero delay skips sleeping. The last permitted `Err` is returned exactly, with no final sleep or unused multiply. Worker traps, backoff overflow, and task cancellation propagate outside the returned `Err` value. This helper does not classify errors or add jitter. Write an explicit policy loop when only selected failures should retry. See [examples/agents/retry_with_backoff.au](../examples/agents/retry_with_backoff.au) for the helper and [examples/agents/retrying_network_worker.au](../examples/agents/retrying_network_worker.au) for a status-aware network policy. ## Current Limits The bootstrap compiler supports: - `Result[T, E]`, `Option[T]`, and `SendError[T]` in type positions - constructing values with `Result.Ok(...)`, `Result.Err(...)`, `Option.Some(...)`, `Option.None`, and every `SendError` variant: `Closed(...)`, `Cancelled(...)`, `TimedOut(...)`, and `Full(...)` - exhaustive `match` over all of these - unqualified variants (`Ok`, `Err`, `Some`, `None`) when the scrutinee type is known - `try` propagation with either the exact error type or a visible applicable `impl From[SourceError] for TargetError` See [examples/enums/result_option.au](../examples/enums/result_option.au). ## Source: tutorials/11-resource-management.md # Resource Management When you open a file, a network connection, or a task group, you need to ensure it gets cleaned up even if something goes wrong. Aura's `with` statement provides deterministic scoped cleanup -- the resource is always closed when the block exits, whether by normal completion or early `return`. If you are coming from Python, this works like Python's `with` statement and context managers. ## `with` Binds A Scoped Resource ```aura check-pass import fs import io def load_text(path: str) -> Result[str, io.Error]: with file = try fs.open(path): return file.read_all() # file.close() is called automatically here, even on early return ``` The bound resource: - is available inside the block as a mutable local binding - always runs `close(mut self)` when the block exits - cleanup runs on normal fallthrough, on early `return`, and after `try` propagation See [examples/resources/with_resource.au](../examples/resources/with_resource.au). ## The Resource Protocol In the current compiler, a `with` resource may be: - a user-defined class with: ```aura fragment def close(mut self): ``` - a builtin `fs.File` - a builtin `net.TcpStream` - a builtin `net.TcpListener` - a `TaskGroup` For user-defined classes, `close(...)` must take `mut self`, no extra parameters, and return `None`. ## `with ... as ...` For Task Groups `TaskGroup()` is the one non-class value that supports `with`: ```aura fragment with TaskGroup() as group: group.start(worker, out) group.start(worker, out) # leaving the block waits for children and cancels only unbounded waits # for which no live task can provide a wakeup ``` Task groups tie child tasks to a lexical scope. When the `with` block ends, Aura waits for child tasks to finish. A child blocked in a queue wait is cancelled only when no live task can wake it; temporary queue backpressure does not become cancellation merely because the host is busy. A true deadlock with no reachable sender, receiver, or queue closer is cancelled so shutdown does not hang forever. You can also cancel early with `group.cancel()`. Queue iteration with `for value in queue:` inside the same `with TaskGroup()` scope observes that cancellation and exits cleanly. See [examples/concurrency/task_group_queue_sum.au](../examples/concurrency/task_group_queue_sum.au) and [examples/concurrency/task_group_cancel.au](../examples/concurrency/task_group_cancel.au). ## Current Limits - builtin resources use the fixed file/TCP/task-group surface; there is no broader enter/exit protocol yet - user-defined resources still require `close(mut self)` with no extra parameters - no borrowed resource bindings ## Source: tutorials/12-error-propagation.md # Error Propagation When functions return `Result[T, E]`, chaining multiple fallible operations with `match` can get deeply nested. Aura provides `try expr` to flatten this pattern. ## `try expr` `try expr` evaluates the expression, which must produce a `Result[T, E]`. If the result is `Ok(value)`, `try` unwraps it and the expression evaluates to the inner value. If the result is `Err(e)`, Aura returns that error from the current function immediately. ```aura check-pass def divide(a: int32, b: int32) -> Result[int32, str]: if b == 0: return Result.Err("division by zero") return Result.Ok(a // b) def add_one_after_divide(a: int32, b: int32) -> Result[int32, str]: value = try divide(a, b) return Result.Ok(value + 1) ``` In `add_one_after_divide`, `try divide(a, b)` either: - unwraps the `Ok` payload into `value` and continues, or - returns `Result.Err("division by zero")` from `add_one_after_divide` immediately Without `try`, the same function would need a nested `match`: ```aura fragment def add_one_after_divide(a: int32, b: int32) -> Result[int32, str]: match divide(a, b): case Ok(value): return Result.Ok(value + 1) case Err(message): return Result.Err(message) ``` ## Chaining Multiple Operations `try` shines when chaining several fallible calls: ```aura fragment def compute(input: str) -> Result[int32, str]: parsed = try parse_int32(input) doubled = try divide(parsed * 2, 3) return Result.Ok(doubled + 1) ``` Each `try` either succeeds and continues to the next line, or short-circuits the entire function with the error. This reads top-to-bottom like normal code. ## Using `try` Inside Expressions `try` can appear inside larger expressions: ```aura check-pass def add_parsed(a: str, b: str) -> Result[int32, str]: return Result.Ok(try parse_int32(a) + try parse_int32(b)) ``` ## Using `try` Inside `with` Blocks `try` works inside `with` blocks. The resource cleanup still runs when `try` triggers an early return: ```aura fragment def process_file(handle: own FileHandle) -> Result[str, str]: with file = handle: value = try validate(file.read()) return Result.Ok(value) # file.close() runs even if try propagates an error ``` ## Rules - `try` is only valid inside a function body - the enclosing function must return `Result[T, E]` - the `try` expression must produce `Result[U, SourceError]`; the enclosing error type may be identical or provide a visible applicable `impl From[SourceError] for TargetError` - `try` unwraps `Ok(value)` to the inner type `U` Exact error types propagate directly. When they differ, Aura calls the selected `From.from` implementation before returning `Result.Err(...)`. See the Manual's [`From` And `try`](../docs/manual/generics-and-traits.md#from-and-try) section for the trait contract and conversion rules. See: - [examples/error_handling/try_result.au](../examples/error_handling/try_result.au) - [10-results-and-options.md](./10-results-and-options.md) ## Source: tutorials/13-concurrency.md # Concurrency Aura's maintained concurrency surface is built around pinned-worker scheduler-backed lightweight tasks, structured task groups, typed queues, and explicit wait helpers. Queue waits, task waits, `sleep(...)`, socket waits, and the maintained HTTP helpers all use the same pinned-worker runtime. The maintained user-facing model is: - `Queue[T]()` - `TaskGroup()` - `TaskGroup.start(...) -> Task[T]` - `TaskGroup.start_soon(...) -> None` - `Task[T].result(timeout=...) -> TaskResult[T]` - `Task[T].result_or_none(timeout=...) -> Option[T]` - `Task[T].result_or(default, timeout=...) -> T` - `Queue[T].get_or_none(timeout=...) -> Option[T]` - `Queue[T].get_or(default, timeout=...) -> T` - `select(queue_or_task_or_duration, ...) -> SelectOutcome[Q, T]` - `wait_any(...)` and `wait_all(...)` Every task belongs to a `TaskGroup`. ## Queues A queue is a typed pipe for sending values between tasks: ```aura check-pass jobs = Queue[int32]() ``` Queues may also be bounded: ```aura check-pass jobs = Queue[int32](capacity=16) ``` With a bounded queue, `put(...)` waits until capacity is available. The queue never grows beyond its configured bound. ### Receiving Values For ordinary code, use the convenience forms: ```aura fragment print(jobs.get_or_none(timeout=100ms)) print(jobs.get_or(0, timeout=100ms)) ``` Without a timeout, `get_or_none()` and `get_or(default)` are immediate non-blocking checks. They return `Option.None` or the fallback value when no item is ready yet. Use `get(timeout=...)` when you need to distinguish all wait states. It returns `QueueReceive[T]`: ```aura fragment match jobs.get(): case QueueReceive.Item(value): print(value) case QueueReceive.Closed: print("closed") case QueueReceive.TimedOut: print("timed out") case QueueReceive.Cancelled: print("cancelled") ``` See [examples/concurrency/queue_timeout.au](../examples/concurrency/queue_timeout.au) and [examples/concurrency/queue_get_timeout_named.au](../examples/concurrency/queue_get_timeout_named.au). ### Sending Values `put(value)` and `put(value, timeout=...)` return `Result[None, SendError[T]]`: ```aura fragment match jobs.put(4, timeout=5ms): case Result.Ok(_): print("sent") case Result.Err(error): match error: case SendError.Closed(value): print(value) case SendError.Cancelled(value): print(value) case SendError.TimedOut(value): print(value) case SendError.Full(value): print(value) ``` `try_put(value)` is the non-blocking send form. It uses the same `SendError[T]` type. See [examples/concurrency/queue_put_timeout.au](../examples/concurrency/queue_put_timeout.au), [examples/concurrency/bounded_queue.au](../examples/concurrency/bounded_queue.au), and [examples/concurrency/send_result.au](../examples/concurrency/send_result.au). ### Iterating Over A Queue You can iterate over a queue until it is closed and empty: ```aura check-pass jobs = Queue[int32]() jobs.put(1) jobs.put(2) jobs.close() for job in jobs: print(job) ``` See [examples/concurrency/queue_iteration.au](../examples/concurrency/queue_iteration.au). Queue handles are copy references. Passing the same queue into multiple tasks shares the underlying queue without requiring `.clone()`. The payload is checked separately. `Queue[T](...)`, `put(...)`, and `try_put(...)` require `T` to be structurally `Transfer`: every stored field, collection element, tuple element, or enum payload must ultimately be safe to move to another task. `random.Rng`, `TaskGroup`, capability views, and live file, process, or network resources are not `Transfer`. Queue receive and handle-only operations do not duplicate or recheck a payload. Queue iteration accepts only the bare form shown above. `for item in own queue:` and `for item in mut queue:` are rejected because receiving already delivers an owned item and the Queue handle itself is a copy value. ## Task Groups Task groups tie child tasks to a lexical scope: ```aura fragment with TaskGroup() as group: first = group.start(worker, jobs) second = group.start(worker, jobs) print(first.result_or(-1, timeout=50ms)) print(second.result_or(-1, timeout=50ms)) ``` When the `with` block ends, Aura waits for child tasks to finish. A no-deadline wait is cancelled only when no live task can wake it; elapsed time and host load do not make a reachable queue wait deadlocked. This lets the scope shut down cleanly when the remaining waits form a true deadlock while keeping ordinary producer/consumer backpressure scoped to the parent block. ### Starting Tasks Use `start(...)` when you need a handle: ```aura fragment with TaskGroup() as group: task = group.start(producer, jobs) ``` Use `start_soon(...)` when you only need the side effect of starting the task: ```aura fragment with TaskGroup() as group: group.start_soon(producer, jobs) ``` Every started task belongs to its group. Scope exit waits for it, and an unread task failure surfaces when the group closes, including for `start_soon(...)`. See [examples/concurrency/task_group_start.au](../examples/concurrency/task_group_start.au) and [examples/concurrency/task_group_start_soon.au](../examples/concurrency/task_group_start_soon.au). All four start methods apply the same boundary before a task is scheduled: every captured argument and the target's result must be structurally `Transfer`. The compiler derives that property from the fully specialized type; source code cannot declare or implement a `Transfer` trait. Copy data, `str`, structurally transferable collections, tuples, classes, enums, and Queue/Task handle identities can cross. Shared or mutable access, `random.Rng`, `TaskGroup`, and live host resources cannot. This static boundary remains the share-nothing rule during multicore task execution. Queue and Task handle state is synchronized for cross-worker use; every other capture and result crosses as owned `Transfer` data. A bare target parameter still grants shared access, but it borrows from the child's owned capture. It never borrows the caller's value. An `own` parameter may consume that capture. A `mut` target remains invalid because there is no caller-visible writeback. Generic task targets must be concrete at the boundary. Inference and defaults may provide the types, or the callable slot may use the narrow forms `function[Types]` and `Type.associated_method[Types]`. Aura rejects an unresolved type parameter at the task boundary. ### Per-task Stack Overrides `TaskGroup.start(...)` and `start_soon(...)` use Aura's guarded 512 KiB default task stack. A child with a measured task-local stack requirement can request a custom capacity without changing its target arguments: ```aura fragment with group = TaskGroup(): task = group.start_with_stack(1024 * 1024, deep_worker, input) group.start_soon_with_stack(2 * 1024 * 1024, deep_sink, jobs) ``` Both size arguments have exact type `int64`. The accepted range is 262,144 through 67,108,864 bytes inclusive (256 KiB through 64 MiB). Aura rejects out-of-range requests and never clamps them. Accepted requests are rounded up to the host page size and protected by the platform stack allocator's guard pages. Use the ordinary start methods unless a real workload demonstrates the need for a custom capacity. The lower 256 KiB bound is also available for an explicitly measured shallow task, but it is not the generally safe default. Aura's complete compiled HTTP example faulted when 256 KiB was used as the global task default during integration and succeeds with the 512 KiB default. A separate runtime-only round trip succeeds with forced 256 KiB protocol callers because the deep host frames execute on service workers; that narrower check does not include the compiled program's MIR/direct execution frames. Associated methods without `self` work too: ```aura check-pass class Worker: def run(value: int32) -> int32: return value + 1 with TaskGroup() as group: task = group.start(Worker.run, 4) ``` See [examples/concurrency/task_group_associated_method.au](../examples/concurrency/task_group_associated_method.au). ### Task Results `Task[T]` is always a `Transfer` handle, but it is copyable only when `T` is repeatable. Repeatable results are copy values, `Queue[...]` handles, and recursively repeatable `Task[...]` handles. A task returning `str`, `list[...]`, or another non-copy transferable value therefore has a move-only task handle. For a non-repeatable result, each of `result`, `result_or_none`, and `result_or` consumes the task handle on its first attempt. Timeout, cancellation, task failure, `Option.None`, and a fallback do not restore the observation right. Use a repeatable result or a separate Queue protocol when a program needs retries or fan-out. Results that are not structurally `Transfer`, including `random.Rng` and live host resources, are rejected at the task-start boundary with `AU3008`. `AU3009` instead reports an attempted clone or collection copy that would duplicate a valid single-consumer result right. A later use after direct observation is the ordinary moved-value diagnostic `AU3001`. For ordinary code, use: ```aura fragment print(task.result_or_none(timeout=100ms)) print(task.result_or(-1, timeout=100ms)) ``` These convenience forms map task failures to `Option.None` or the caller-provided fallback, alongside timeout and cancellation. Without a timeout, `result_or_none()` and `result_or(default)` are immediate non-blocking checks. They return `Option.None` or the fallback value when the task is not ready yet. Use `Task.result(timeout=...)` when you need to distinguish all wait states. It returns `TaskResult[T]`: ```aura fragment match task.result(): case TaskResult.Ready(value): print(value) case TaskResult.Error(message): print(message) case TaskResult.TimedOut: print("timed out") case TaskResult.Cancelled: print("cancelled") ``` ## Waiting On Multiple Tasks Use the builtin `select(...)` when one wait mixes queues, tasks, and a relative deadline. It is an ordinary variadic call, not the removed statement form: ```aura fragment outcome = select(messages, worker_task, 20ms) match own outcome: case SelectOutcome.Queue(index, received): print(index) match own received: case QueueReceive.Item(message): print(message) case _: pass case SelectOutcome.Task(index, result): print(index) match own result: case TaskResult.Ready(value): print(value) case _: pass case SelectOutcome.Deadline(index): print(index) case SelectOutcome.Cancelled: print("cancelled") ``` All Queue sources share one payload type and all Task sources share one result type. Missing source categories appear as `None` in `SelectOutcome[Q, T]`. Source expressions are evaluated once from left to right. Cancellation wins; otherwise the lowest original argument index wins when several sources are ready together. A selected Queue removes one item, while losing queues remain unchanged. Every non-repeatable Task observation right is consumed at call entry, even when a Queue or deadline wins. Use `wait_any(...)` and `wait_all(...)` for an existing homogeneous `list[Task[T]]`. `wait_any(tasks, timeout=...)` returns `WaitAny[T]`: ```aura fragment match wait_any(task_list, timeout=20ms): case WaitAny.Ready(index, value): print(index) print(value) case WaitAny.Error(index, message): print(index) print(message) case WaitAny.TimedOut: print("timedout") case WaitAny.Cancelled: print("cancelled") ``` `wait_any([])` returns `WaitAny.TimedOut` immediately. `wait_all(tasks, timeout=...)` returns `WaitAll[T]`: ```aura fragment match wait_all(task_list, timeout=20ms): case WaitAll.Ready(results): for result in results: print(result) case WaitAll.Error(index, message): print(index) print(message) case WaitAll.TimedOut: print("timedout") case WaitAll.Cancelled: print("cancelled") ``` For repeatable `T`, the task handles and observations remain reusable. For a non-repeatable `T`, both helpers consume the complete `list[Task[T]]` on the first attempt, including timeout, cancellation, and task failure. `wait_any` deliberately abandons the observation rights of the tasks it did not choose. Queue receive APIs always transfer one owned payload, but Queue construction and sends admit only `Transfer` payloads. See [examples/concurrency/task_group_wait_helpers.au](../examples/concurrency/task_group_wait_helpers.au). ## Cooperative Cancellation Call `group.cancel()` to signal all tasks in the group to stop. Inside long-running task code, call `cancelled()` to observe the request: ```aura check-pass def worker(out: Queue[int32]): mut i: int32 = 0 while i < 100: if cancelled(): return out.put(i) i += 1 ``` `sleep(...)` also wakes early when the group is cancelled, so task code after the sleep can call `cancelled()` and decide how to exit. If the current `with TaskGroup()` scope is iterating a `Queue[T]` from that scope with `for value in queue:`, `group.cancel()` also wakes that queue iteration so it can finish cleanly. Cancellation is cooperative. Aura does not forcibly kill tasks. Aura 0.3 runs task bodies on cooperative pinned scheduler workers on both maintained backends. The default worker count is the available parallelism reported by the host; the provisional `AURA_WORKERS=` environment override selects an explicit count. A task receives its stable worker assignment when it is spawned. Its coroutine stack never migrates, the runtime does not steal work, and `yield_now()` yields only to runnable work on that worker. The compiler inserts a cooperative scheduling check on every loop backedge, including a normal body tail and `continue`, so a tight loop allows ready timers, Queue operations, or socket work assigned to the same worker to proceed. `break` and `return` leave the loop without taking that check. One long loop body or straight-line computation can still delay same-worker siblings, and the check does not inspect cancellation. Each ordinary lightweight task requests a guarded 512 KiB coroutine stack; the explicit stack-start methods accept requests through 64 MiB. Descriptor registrations persist across waits, deadlines use a timer heap, and Queue, task-completion, and blocking-pool events notify the responsible worker directly. With nothing ready locally, a worker blocks until work, an event, or a deadline. It does not wake on a periodic tick. Queue and Task handles are the maintained cross-worker channels. Every other task capture and result stays owned and share-nothing through structural `Transfer`. Cancellation and diagnostic context remain isolated per task. If a child traps, its diagnostic preserves typed Aura call frames and a youngest-first ancestry chain naming the task entry and parent spawn site. Both maintained backends render the same human call/task notes, and tooling receives the same records without parsing those notes. Scheduling, independent completion, and printed-output order are unspecified, and Aura exposes no worker-index or affinity-introspection API. Pinned workers enable multicore task execution; work stealing and preemption are unavailable, and parallel speedup depends on the workload. Deep HTTP, TLS, and maintained Unix WebSocket library steps run on a distinct bounded protocol service with deep native worker stacks. Protocol state returns to the lightweight task after each bounded, nonblocking step and before cancellation or reactor waiting resumes. On the clean Mac14,9 Phase 5.10 measurement, three runs of 10,000 parked sleepers peaked at 207,798,272, 206,946,304, and 206,831,616 bytes whole-process RSS, all below the maintained 512 MiB gate. Standalone 1,000-timer controls remained stable with a 6 ms maximum arm span and 1 ms worst p99 overshoot. The runtime accepts larger task counts; 10,000 sleepers is the maintained memory-capacity bound. Three clean runs of the 100,000-sleeper plus 1,000-timer workload peaked at 1,170,735,104, 1,921,531,904, and 2,001,305,600 bytes, so two runs exceeded 1.5 GiB while their timers remained stable at a 3 ms maximum arm span and 2 ms worst p99 overshoot. Mac14,9 uses 16 KiB pages: 101,000 stackful tasks therefore have a 1,654,784,000-byte one-page floor before scheduler, program, and process metadata. The earlier Phase 5.9 below-gate sample depended on nondeterministic memory compression. The same current contractual report passes the four-worker scaling gate at a `1.039673x` paired median wall-time ratio with `396.73%` median four-task process CPU. The protocol service starts lazily and lives until process exit; Aura 0.3 does not expose a shutdown or join operation for it. File reads, resolver work, and listener binding use the generic blocking-I/O pool. TLS asset bytes are read there before PEM parsing and rustls construction run on protocol workers. The generic blocking-I/O pool is configured separately. An exact positive `AURA_BLOCKING_WORKERS` value is not clamped; without it, host parallelism is used with fallback `4` and a derived `2..=8` clamp. A positive `AURA_BLOCKING_QUEUE_CAPACITY` bounds accepted pending jobs only, while omission preserves an unbounded queue. Full-queue admission is FIFO and parks the Aura task through the scheduler. Cancellation or timeout before queue insertion prevents submission; accepted work cannot be retracted and its late result is discarded. Bounding the queue does not guarantee unrelated progress while every blocking worker remains stuck. Blocking queue/task/network waits are cancellation-aware and surface cancellation through `QueueReceive`, `TaskResult`, `WaitAny`, `WaitAll`, or `io.Error`, depending on the API. See [examples/concurrency/task_group_cancel.au](../examples/concurrency/task_group_cancel.au). ## Automatic Loop Safepoints Loop safepoints make progress automatic at backedges; they do not make Aura preemptive. A single long iteration can still delay siblings until it reaches the body tail, and long straight-line CPU work has no automatic checkpoint. The safepoint also does not inspect cancellation. Keep calling `cancelled()` when a task must stop on request. MIR execution amortizes yielding with 8 units of function-local loop fuel. Native concurrent programs use 4,096 units, while a program proven to have no possible sibling task removes the runtime checks. Do not use these intervals to predict output order: runnable-task selection and concurrent interleaving remain unspecified. ## `yield_now` Automatic safepoints are enough to keep a tight loop from starving the scheduler indefinitely. Calling `yield_now()` between chosen bounded chunks provides an explicit scheduling point sooner than the amortized native check when the application wants one: ```aura check-pass def count(label: str): mut step: int32 = 1 while step <= 3: print(f"{label}: {step}") step += 1 yield_now() ``` The call returns `None` when the current task is scheduled again. It does not sleep or guarantee that a different task runs, and runnable-task ordering is not part of the language contract. It also does not inspect cancellation; call `cancelled()` separately when cancellation matters. See [examples/concurrency/yield_now.au](../examples/concurrency/yield_now.au). ## `sleep` A simple delay: ```aura check-pass sleep(100ms) ``` Computed delays use the same signed Duration arithmetic as other expressions. For example, a runtime attempt count can scale a base delay with `attempt * 1ms`. A sleep or timeout must be non-negative and fit the host deadline; invalid values fail. Only omission creates an unlimited wait. See [examples/concurrency/sleep_builtin.au](../examples/concurrency/sleep_builtin.au). For constructors, arithmetic, comparison, conversion, and sub-millisecond rendering, see [examples/concurrency/duration_arithmetic.au](../examples/concurrency/duration_arithmetic.au). ### Backoff Without Hidden Final Delays Retry policy belongs in application code. The maintained [retrying network worker](../examples/agents/retrying_network_worker.au) retries only HTTP `503`, doubles a `Duration` backoff after each retry, and adds jitter from `random.Rng(42)` so its trace is reproducible. The worker checks both the response status and the final-attempt guard before drawing randomness, printing a retry, or calling `sleep(...)`. Exhausting three attempts therefore returns the last `503` immediately: there is no invisible fourth attempt and no final delay. A terminal non-retryable status such as `429` is returned immediately too. The example places the loopback server and worker in one `TaskGroup`, gives network and task waits explicit five-second deadlines, and scopes listeners, exchanges, and responses with `with`. Its maintained CLI regression pins the same seven-request trace through the MIR and forced-direct backends. ## Full Example ```aura check-pass def producer(out: Queue[int32]) -> int32: out.put(2) out.put(4) out.close() return 6 def main() -> int32: jobs = Queue[int32]() with TaskGroup() as group: task = group.start(producer, jobs) while true: match jobs.get_or_none(timeout=50ms): case Option.Some(value): print(value) case Option.None: break print(task.result_or(-1, timeout=50ms)) return 0 ``` See: - [examples/concurrency/task_group_start.au](../examples/concurrency/task_group_start.au) - [examples/concurrency/task_group_start_soon.au](../examples/concurrency/task_group_start_soon.au) - [examples/concurrency/task_group_associated_method.au](../examples/concurrency/task_group_associated_method.au) - [examples/concurrency/task_group_queue_sum.au](../examples/concurrency/task_group_queue_sum.au) - [examples/concurrency/task_group_cancel.au](../examples/concurrency/task_group_cancel.au) - [examples/concurrency/task_group_wait_helpers.au](../examples/concurrency/task_group_wait_helpers.au) - [examples/concurrency/bounded_queue.au](../examples/concurrency/bounded_queue.au) - [examples/concurrency/queue_timeout.au](../examples/concurrency/queue_timeout.au) - [examples/concurrency/queue_put_timeout.au](../examples/concurrency/queue_put_timeout.au) - [examples/concurrency/send_result.au](../examples/concurrency/send_result.au) - [examples/agents/retrying_network_worker.au](../examples/agents/retrying_network_worker.au) ## Current Limits The runtime is intentionally simple: - queue waits, task waits, `sleep(...)`, socket waits, and HTTP waits all use the pinned-worker runtime scheduler - the scheduler keeps descriptor registrations persistent, orders deadlines in a timer heap, receives direct Queue/task-completion/blocking-pool notifications, and blocks without a periodic idle tick - cancellation is cooperative; preemptive cancellation is unavailable - loop backedges include compiler-inserted cooperative scheduling checks, but a single long body can still delay siblings - tasks are scheduler-backed lightweight coroutines; each task does not require its own OS thread - task arguments are owned captures; bare shared and `own` target parameters are supported, while `mut` target parameters are rejected ## Source: tutorials/14-current-language-surface.md # Current Language Surface This chapter is a compact reference for the language subset that the bootstrap compiler supports today. It is intentionally implementation-facing. Use the earlier chapters to learn the language progressively, then use this chapter to check what is actually available right now. ## Top-Level Items Aura currently supports these top-level declarations: - `import module`, `import module as alias`, and `from module import name` - immutable complete-value module constants such as `limit: int64 = 10` - `public class` - `public enum` - `public def` - `public trait` - `public copy class` - `class` - `copy class` - `enum` - `def` - `trait` - `impl Trait for Type` - authorized `extern "C" def` and `extern "C" opaque class` declarations It also supports top-level executable statements for script-style files. ## Entry Styles You can write either: - a top-level script - an explicit `main` Do not mix top-level executable statements with `main` in the same file. Floating-point literals default to `float64`, but they can adopt an expected `float32` type from an annotation, parameter, return type, or class field. Unsuffixed integer literals default to `int64`, and `int` is an alias for `int64`. Expected integer types still take precedence, so fixed `int32` APIs and annotations remain `int32`. An integer literal can also adopt an expected `float32` or `float64` type when its value is exactly representable there; this never converts an already-bound integer variable. Integer literals support the full `uint128` range when that integer type is expected. ## Types Builtin scalar and utility type names currently accepted by the compiler: - `bool` - `int` (an alias for `int64`) - `int8`, `int16`, `int32`, `int64`, `int128`, `intsize` - `uint8`, `uint16`, `uint32`, `uint64`, `uint128`, `uintsize` - `float32`, `float64` - `str` - `str` in shared type positions - `None` - `Duration` - `Range` - `io.Error` - `fs.File` - `net.TcpListener` - `net.TcpStream` - `net.UdpSocket` - `net.UdpDatagram` - `net.HttpListener` - `net.HttpExchange` - `net.HttpResponse` - `net.WebSocketListener` - `net.WebSocket` - `net.UnixListener` - `net.UnixStream` - `net.TlsListener` - `net.TlsStream` - `process.Child` - `process.Pipe` - `process.Completed` - `process.Supervisor` - `process.ExitStatus` - `process.Wait` - `process.Stdio` - `process.Error` - `process.RestartPolicy` - `process.SupervisorEvent` - `process.SupervisorWait` - `random.Rng` - `bytes.Error` - `json.Value` - `json.Error` Builtin generic or runtime-facing types currently accepted: - `Option[T]` - `Result[T, E]` - `SendError[T]` - `Queue[T]` - `QueueReceive[T]` - `list[T]` - `dict[K, V]` - `set[T]` - `Array[T]`, where `T` is exactly `int32`, `int64`, `float32`, or `float64` - `Task[T]` - `TaskResult[T]` - `TaskGroup` - `SelectOutcome[Q, T]` - `WaitAny[T]` - `WaitAll[T]` Structural tuple types such as `(str, int64)` and singleton `(bool,)` are also accepted. A tuple is copyable exactly when every element is copyable. Capture-free named function values use `def(T1, mut T2, own T3) -> R`. They are copy values, satisfy `Transfer`, and may be stored in bindings, parameters, fields, and collections or used as `TaskGroup` targets. Bare function-type parameters are shared; written or inferred `mut`/`own` modes are part of the contract. Instance, associated, and trait method values remain outside the implemented surface. Contextually typed expression lambdas use `lambda parameters: expression`. The expected `def(...) -> ...` type supplies parameter types and constrains the result; `lambda: expression` may infer `def() -> R` without context. Captures are by value: Copy values are snapshotted and owned non-Copy values move at creation. A read-only closure is repeatable, a closure that consumes a non-Copy capture is single-use, and a closure is Transfer exactly when every capture is Transfer. Captured environments are read-only in Phase 6.3 and cannot be erased through arbitrary stored or parameter `def` types. These built-in type names are reserved and cannot be reused for user-defined classes, enums, or traits. ## Packages And Workspaces Aura supports this local package-system surface: - `Aura.toml` package manifests with `[package]` - package source roots under `src/` - local path dependencies under `[dependencies]` - git dependencies under `[dependencies]` - workspace roots with `[workspace] members = [...]` - package-aware `check`, `run`, `build`, `analyze`, and `complete` - a local `Aura.lock` written at the package root or workspace root - FFI v0 authorization through `[package] allow_ffi = true`, with every reachable FFI-enabled dependency named exactly in the root package's `[ffi] dependencies` report Current manifest shape: ```toml [package] name = "app" version = "0.1.0" edition = "2026" [dependencies] util = { path = "../util" } jsonx = { git = "https://github.com/example/jsonx.git", branch = "main" } ``` Current workspace shape: ```toml [workspace] members = ["app", "util"] ``` Current package-system limits: - dependency imports may come from local path dependencies or git dependencies - import roots for dependencies are package-name-prefixed, such as `import util.math` - version-only registry dependencies like `util = "0.1.0"` are rejected with a clear diagnostic - git dependencies support `rev`, `tag`, or `branch`, and default to `branch = "main"` when no selector is provided - git dependencies are materialized from a local cache and pinned by exact revision in `Aura.lock` - `aura deps update` refreshes all branch/tag/default-main git dependencies for the current package or workspace - `aura deps update util` refreshes just the named git dependency - there are still no registry or publish/install flows yet An authorized package may declare bodyless `extern "C"` functions over the fixed-width scalar set, temporary str/byte pointer-length views, and opaque handles. Extern functions are direct-call-only and resolve process-global symbols synchronously. Empty views pass `(NULL, 0)`; `mut list[uint8]` uses same-length scratch copy-in/out. Opaque handles are non-Copy, non-cloneable, non-Transfer values and require an explicit consuming native close/free call. Callbacks, variadics, raw pointer arithmetic, returned views, nullable handles, and explicit library loading are not implemented. See [26-ffi.md](26-ffi.md). ## Ownership And Borrowing Aura uses an ownership model with no garbage collector. See [06-ownership-and-borrowing.md](06-ownership-and-borrowing.md) for the full tutorial. Copy types (all numeric types, `bool`, `Duration`, and `Queue[T]`) are duplicated on assignment. `Task[T]` is copyable only when `T` is copyable, a `Queue[...]` handle, or a recursively repeatable `Task[...]` handle. Move types (`str`, `list[T]`, `dict[K, V]`, `set[T]`, `Array[T]`, `random.Rng`, `TaskGroup`, opaque FFI handles, ordinary user-defined classes, and `Task[T]` for a non-repeatable `T`) transfer ownership on assignment. `copy class` declarations are allowed when all fields are copy types. Capability forms. Bare means shared access everywhere, `mut` means mutable access, and `own` means ownership transfer. There is one spelling per capability: - `value: T` -- shared parameter, for every type including copy types - `value: mut T` -- exclusive, mutable parameter - `value: own T` -- consuming parameter - `self` -- shared receiver - `mut self` -- mutable receiver - `own self` -- consuming receiver - `for x in collection:` -- shared collection iteration - `for x in mut collection:` -- mutable iteration with writeback - `for x in own collection:` -- consuming iteration - `match value:` -- shared pattern matching - `match mut value:` -- mutable pattern matching with writeback - `match own value:` -- consuming pattern matching Mutable arguments must be mutable places. Overlapping `mut` arguments with other shared access to the same value are rejected. Non-copy fields cannot be moved out of a shared value. `.clone()` produces an explicit independent copy when the move type exposes clone and its stored values are clone-safe. `random.Rng`, and an ordinary value that contains one, has no public clone route. ## Statements The current compiler supports these statement forms: - assignment and compound assignment through `+=`, `-=`, `*=`, `**=`, `/=`, `%=`, `//=`, `&=`, `|=`, `^=`, `<<=`, and `>>=` - recursive tuple unpack assignment such as `name, count = record` - `return` - `if` / `elif` / `else` - `while` - `for value in range(n):` - `for value in jobs:` - recursive tuple-target iteration such as `for name, count in records:` - `match` - `with` - `break` - `continue` - `pass` - `assert condition` and `assert condition, message` - expression statements Assertion conditions must be exactly `bool`, and optional messages must be `str`. A true assertion does not evaluate its message. A false assertion traps with `AU4001` at the `assert` keyword, using `assertion failed` or the exact supplied message. Assertions are not stripped in any build mode. ## Expressions The current compiler supports these expression forms: - names - parenthesized tuple values such as `(name, count)` and singleton `(value,)` - decimal, hexadecimal, binary, and octal integer literals with digit separators; float, ordinary string, triple-quoted string, raw string, f-string, boolean, `None`, and duration literals - ordinary strings accept matching single or double quotes with shared escapes - f-strings remain double-quoted as `f"..."`, while interpolations may contain either ordinary quote form - arithmetic, comparison, and boolean operators - integer `&`, `|`, `^`, `~`, `<<`, and `>>` preserve exact widths and use exact same-type operands; left shift is checked - `**` is right-associative, preserves exact numeric types, and checks integer overflow and exponent domain - `//` is builtin floor division for matching integer or floating types - builtin integer `/` and `/=` are rejected; floating `/` and `/=` remain true division - builtin `%` follows the divisor's sign for matching integer or floating types - Duration supports checked `+`, `-`, `* int64` in either order, `// int64`, and full comparison - same-type tuples support recursive `==` and `!=` when every element is equatable; both operands are read and retained, while tuple ordering remains rejected - unary prefix operators `-` and `not` - operator-trait dispatch for `+`, binary `-`, `*`, `/`, `//`, `%`, unary `-`, and `not` - `//` uses `FloorDiv.floor_div` when no builtin numeric or Duration rule applies - explicit numeric casts with `expr as Type` - integer casts are range-checked and integer-to-float casts reject silent precision loss - integer `.to_float() -> float64`, which uses nearest-even conversion and may round - `round(value)`, which preserves integer types or rounds a float to `int64` with ties-to-even, and `divmod(left, right)`, which returns the floor quotient and divisor-signed remainder together - shortest-roundtrip `float32`/`float64` rendering through `print`, preserving integral `.0` and signed zero - list literals such as `[1, 2, 3]` - dictionary literals such as `{"aura": 1}` - set literals such as `{1, 2, 3}` - eager owned list, set, and dictionary comprehensions such as `[value * 2 for value in values if value > 0]`; nested clauses are outer-major, targets do not leak, and every clause uses the bare-loop contract (including Queue's receive-owned item carve-out) - member access with `.` - indexing with `expr[index]` - numeric Array indexing with comma-separated `int64` coordinates such as `matrix[row, column]`, including indexed assignment - owned list/str slicing with `expr[start:end]`, `expr[:end]`, `expr[start:]`, and `expr[:]` - owned first-axis Array slicing with the same one-colon forms; the result is a fresh Array, and views remain unavailable - function and method calls - explicit type arguments on call targets such as `Box[int32](...)` and `Result[int32, str].Ok(...)` - enum and built-in enum variant construction - `try expr` - contextually typed expression lambdas such as `lambda value: value + 1` - expression-form `match` in bindings, returns, and call arguments - conditional expressions written `value if condition else alternative`; the condition must be exactly `bool`, is evaluated once, and selects exactly one lazily evaluated arm. Both arms must have one static result type. This form has the lowest expression precedence and associates to the right. - `value in container` and `value not in container` over `list[T]` and `set[T]` elements, `dict[K, V]` keys, and `str` substrings; both operands are read and neither is moved - comparison chains such as `low <= value < high`, where equality, ordering, and membership share one precedence level, every operand is evaluated at most once, and a false link short-circuits the rest - the compiler-known `for` iterable forms `enumerate(seq)`, yielding `(int64, element)`, and `zip(first, second)`, which stops at the shorter sequence; both take `list[T]` or `set[T]` operands over the bare-loop shared default and are legal only as a `for` iterable - the builtin functions `print`, `range`, `cancelled`, `yield_now`, `sleep`, `select`, `wait_any`, `wait_all`, `abs`, `min`, `max`, `sqrt`, `round`, `divmod`, `parse_int32`, `parse_int64`, `parse_float64`, `len`, and `str`; these names are reserved and cannot be redefined - parenthesized expressions and tuple values - delimiter-based newline continuation while `(`, `[`, or `{` remains open - continuation indentation is visual and does not create a block - ordinary comma-separated forms still reject trailing commas; singleton tuples require one comma - backslashes and physical newlines inside ordinary/f-strings do not continue source Parenthesized generator expressions are not implemented. They report `AU2005` with guidance to use an eager owned list comprehension or an explicit loop. Comprehension clauses do not accept `mut` or `own`; use a statement loop for mutable or consuming source traversal. Indexed expressions remain ordinary values after parsing. Copy-typed element reads like `values[idx]` work directly. Clone-safe non-copy list elements use `get(index)` for an explicit cloned read. Elements carrying `random.Rng` state use `pop(index)` to transfer ownership. Negative list indexes normalize as `len + index` for direct access and every maintained list index method. Dictionary indexing and interpolations such as `f"{counts['key']}"` remain supported when the dict value type is copy; clone-safe non-copy values use `get(key)` for an explicit cloned optional read, while `remove(key)` transfers any stored value. One-colon list and str slices return fresh owned copies. Written endpoints use the `int64` position domain, negatives normalize once, both effective endpoints must be in `0..=len`, and start must not exceed end. Invalid bounds trap with `AU4003`; Aura never clamps them. String positions count Unicode scalar values and require an O(n) scan. Integer str indexing, step syntax, slice assignment, and views remain unavailable. Numeric `Array[T]` values have rank at least one, may contain zero-sized dimensions, and use contiguous row-major storage. The constructors are `zeros(shape)`, `full(shape, value)`, and `from_list(values, shape)`; `from_list` copies its shared list input. Members include `shape`, `len`, `clone`, `get`, mutable `set`, mutable `fill`, `map[U]`, `sum`, `min`, `max`, and `mean`. Array/Array arithmetic uses exact shapes and dtypes; same-dtype scalar arithmetic supports either operand order for `+`, `-`, and `*`, while `/` is float-only. Integer Arrays expose wrapping and saturating add, subtract, and multiply methods. There is no array-shape broadcasting, mixed-dtype promotion, equality, reshape, transpose, view, step, slice assignment, or accelerator placement. Empty `min`, `max`, and `mean` trap with `AU4007`; coordinate and slice bounds use `AU4003`; shape-product overflow and allocation failure use `AU4005`. ## Methods Class methods currently support these receiver forms: - `self` for shared access - `own self` - `mut self` - no receiver for associated methods Bare `self` has shared semantics. `self: Type` is not a receiver declaration and is rejected with guidance naming the valid forms. Ordinary functions, instance methods, and associated methods support: - positional calls - named arguments - mixed calls where positional arguments come first and named arguments come after - default parameter values on ordinary functions and class methods - ordinary bare, `own`, and `mut` parameters - builtin named arguments for `print(value=...)`, `range(...)`, `wait_any(...)`, and `wait_all(...)` Bare parameters grant logical shared access for every type, and that choice is stable after specialization. Task starts move/copy arguments into task-owned capture storage, then allow bare shared or `own` target parameters; `mut` targets are rejected. Calls also reject overlapping borrowed arguments whenever a `mut` parameter participates, including a `mut self` receiver overlapping another borrowed argument in the same method call. Empty list literals currently require an expected `list[T]` type such as `values: list[int32] = []`, or you can use `list[int32]()` explicitly. Empty dictionary literals require an expected `dict[K, V]` type such as `counts: dict[str, int32] = {}`. Empty sets use a typed constructor such as `set[int32]()`. Top-level declarations may also be generic: - `class Box[T]: ...` - `class Box[T: Trait]: ...` - `enum Wrapper[T]: ...` - `enum Wrapper[T: Trait]: ...` - `def identity[T](value: own T) -> T: ...` - `trait Child: Parent: ...` - `trait Child[T]: Parent[T]: ...` Generic functions and methods may use inline trait bounds: - `def speak[T: Greeter](value: T): ...` - `def use_both[T: A + B](value: T) -> int32: ...` - `def apply[T: Mapper[int32]](mapper: T, value: int32) -> int32: ...` Built-in enum constructor notes: - `Option.Some(...)` can infer `T` from its payload without a separate annotation - `Option.None` still requires an expected `Option[T]` type ## Builtins Current builtin functions: - `print` - `range` - `cancelled` - `yield_now` - `sleep` - `wait_any` - `wait_all` - `abs` - `min` - `max` - `sqrt` - `parse_int32` - `parse_int64` - `parse_float64` Current builtin module namespaces: - `io` - `fs` - `net` - `process` - `random` - `sys` - `path` - `bytes` - `json` - `toml` - `log` - `metrics` - `trace` - `control` Current builtin `range(...)` notes: - supports `range(stop)` and `range(start, stop)` - supports the matching named-argument forms - currently requires bounds that fit the bootstrap compiler's signed index space Current dynamic JSON surface: - `json.parse(...) -> Result[json.Value, json.Error]` - `json.dumps(..., indent=Option.None) -> str` - exact inspecting accessors `is_null`, `as_bool`, `as_int`, and `as_float` - consuming accessors `into_string`, `into_array`, and `into_object` - recursive Null, Boolean, Int, Float, str, Array, and Object variants - deterministic sorted-key compact or pretty output - typed parse failures plus fixed depth and byte limits Current bytes, text-codec, and hash surface: - `list[uint8]` as the bytes representation - `str.to_bytes()` and `str.from_bytes(...)` for strict UTF-8 - lowercase `bytes.hex_encode(...)` and strict mixed-case `bytes.hex_decode(...)` - canonical standard-alphabet `bytes.base64_encode(...)` and `bytes.base64_decode(...)` - raw 32-byte `bytes.sha256(...)` and `bytes.sha256_string(...)` - typed `bytes.Error` malformed-input variants with retained `int32` offsets and lengths; required metadata above `2147483647` traps with `AU4005` and is never truncated or wrapped - a fixed 2,147,483,647-byte safety ceiling for each fresh codec destination, independent of public str and `list` length domains; crossing it, destination-size arithmetic overflow, or allocation failure traps with `AU4005` Current builtin I/O, networking, and process surface: - `io.write(...)` - `io.flush()` - `io.read_line()` - `fs.exists(...)` - `fs.read_to_string(...)` - `fs.read_bytes(...)` - `fs.write_string(...)` - `fs.write_bytes(...)` - `fs.append_string(...)` - `fs.append_bytes(...)` - `fs.create_dir(...)` - `fs.read_dir(...)` - `fs.remove_file(...)` - `fs.open(...)` - `fs.create(...)` - `fs.append(...)` - `fs.File.read_all()` - `fs.File.read_bytes()` - `fs.File.write_all(...)` - `fs.File.write_bytes(...)` - `fs.File.flush()` - `fs.File.close()` - one-shot and `fs.File` whole-file reads are capped at 256 MiB of remaining content in both `aura run` and built binaries; Aura 0.3 has no chunked file-read API - process capture/pipe reads and TCP, Unix, and TLS whole or bounded reads are capped at 64 MiB; TLS certificate, private-key, and CA-file loading uses the same independent 64 MiB ceiling - incoming HTTP parsing is capped at 16 MiB of wire data per message - `net.connect(...)` - `net.connect_timeout(...)` - `net.listen(...)` - `net.udp_bind(...)` - `net.http_listen(...)` - `net.http_request_text(...)` - `net.http_request_text_timeout(...)` - `net.http_request_bytes(...)` - `net.http_request_bytes_timeout(...)` - `net.websocket_listen(...)` - `net.websocket_connect(...)` - `net.websocket_connect_timeout(...)` - `net.unix_listen(...)` - `net.unix_connect(...)` - `net.unix_connect_timeout(...)` - `net.tls_listen(...)` - `net.tls_connect(...)` - `net.tls_connect_timeout(...)` - `process.start(...)` - `process.run(...)` - `process.supervisor()` - both accept `group=true` to place the child in its own process group and make lifecycle cleanup group-aware on maintained Unix hosts - `process.inherit()` - `process.null()` - `process.pipe()` - `net.TcpListener.accept(timeout=...)` - `net.TcpListener.local_addr()` - `net.TcpListener.close()` - `net.TcpStream.read_all(timeout=...)` - `net.TcpStream.read_line(timeout=...)` - `net.TcpStream.read_bytes(...)` - `net.TcpStream.read_exact(...)` - `net.TcpStream.write_all(...)` - `net.TcpStream.write_bytes(...)` - `net.TcpStream.flush()` - `net.TcpStream.local_addr()` - `net.TcpStream.peer_addr()` - `net.TcpStream.shutdown_read()` - `net.TcpStream.shutdown_write()` - `net.TcpStream.shutdown_both()` - `net.TcpStream.close()` - `net.UdpSocket.send_text(...)` - `net.UdpSocket.send_bytes(...)` - `net.UdpSocket.recv(...)` - `net.UdpSocket.recv_from(...)` - `net.UdpSocket.local_addr()` - `net.UdpSocket.peer_addr()` - `net.UdpSocket.close()` - `net.UdpDatagram.address()` - `net.UdpDatagram.bytes()` - `net.UdpDatagram.text()` - `net.HttpListener.accept(timeout=...)` - `net.HttpListener.local_addr()` - `net.HttpListener.close()` - `net.HttpExchange.method()` - `net.HttpExchange.path()` - `net.HttpExchange.headers()` - `net.HttpExchange.body_text()` - `net.HttpExchange.body_bytes()` - `net.HttpExchange.respond_text(...)` - `net.HttpExchange.respond_bytes(...)` - `net.HttpResponse.status()` - `net.HttpResponse.reason()` - `net.HttpResponse.headers()` - `net.HttpResponse.text()` - `net.HttpResponse.bytes()` - `net.WebSocketListener.accept(timeout=...)` - `net.WebSocketListener.local_addr()` - `net.WebSocket.send_text(...)` - `net.WebSocket.send_bytes(...)` - `net.WebSocket.recv_text(...)` - `net.WebSocket.recv_bytes(...)` - `net.WebSocket.close()` - `net.UnixListener.accept(timeout=...)` - `net.UnixListener.close()` - `net.UnixStream.read_line(timeout=...)` - `net.UnixStream.read_exact(...)` - `net.UnixStream.write_all(...)` - `net.UnixStream.close()` - `net.TlsListener.accept(timeout=...)` - `net.TlsListener.local_addr()` - `net.TlsListener.close()` - `net.TlsStream.read_line(timeout=...)` - `net.TlsStream.read_exact(...)` - `net.TlsStream.write_all(...)` - `net.TlsStream.close()` - `process.Child.stdin()` - `process.Child.stdout()` - `process.Child.stderr()` - `process.Child.wait(timeout=...)` - `process.Child.wait_or_none(timeout=...)` - `process.Child.wait_ok(timeout=...)` - `process.Child.kill()` - `process.Child.terminate()` - `process.Child.close()` - `process.Pipe.read_all()` - `process.Pipe.read_line(timeout=...)` - `process.Pipe.read_bytes(...)` - `process.Pipe.write_all(...)` - `process.Pipe.write_bytes(...)` - `process.Pipe.flush()` - `process.Pipe.close()` - `process.Completed.status()` - `process.Completed.success()` - `process.Completed.stdout()` for UTF-8 text - `process.Completed.stderr()` for UTF-8 text - `process.Completed.stdout_bytes()` - `process.Completed.stderr_bytes()` - `process.Completed.check()` - `process.Supervisor.start(...)` - `process.Supervisor.wait(timeout=...)` - `process.Supervisor.wait_or_none(timeout=...)` - `process.Supervisor.stop()` - `process.Supervisor.is_empty()` - `process.Supervisor.close()` Current builtin member methods include: - `float64.sqrt()` - scalar and boolean `.to_string()` - `str.len() -> int64` (Unicode scalar values, O(n)) - `str.byte_len() -> int64` (UTF-8 bytes, O(1)) - `str.to_bytes()` (fresh `list[uint8]`) - `str.from_bytes(...)` (associated strict UTF-8 conversion) - `str.contains(...)` - `str.starts_with(...)` - `str.ends_with(...)` - `str.split(...)` - `str.join(...)` - `str.replace(...)` - `str.to_lower()` - `str.to_upper()` - `str.strip_prefix(...)` - `str.strip_suffix(...)` - `str.trim()` - `str.clone()` - `list.len() -> int64` - `list.is_empty()` - `list.copy()` - `list.append(...)` - `list.pop(index=-1)` - `list.get(...)` - `list.insert(...)` - `list.set(...)` - `list.remove(...)` - `list.index(...)` - `list.count(...)` - `list.swap(...)` - `list.extend(...)` - `list.clear()` - `list.reverse()` - `list.sort()` - `list.sort(reverse=...)` - `list.sort(key=..., reverse=...)` - `list.map(f)` - `list.filter(f)` - `list.reserve(...)` - `list.with_capacity(...)` - `dict.len() -> int64` - `dict.is_empty()` - `dict.copy()` - `dict.get(...)` - `dict.remove(...)` - `dict.keys()` - `dict.values()` - `dict.items()` - `dict.clear()` - `dict.update(...)` - `dict.reserve(...)` - `dict.with_capacity(...)` - `set.len() -> int64` - `set.is_empty()` - `set.copy()` - `set.add(...)` - `set.remove(...)` - `set.discard(...)` - `set.clear()` - `set.reserve(...)` - `set.with_capacity(...)` - `Queue.put(...)` - `Queue.try_put(...)` - `Queue.get(...)` - `Queue.get_or_none(...)` - `Queue.get_or(...)` - `Queue.close()` - `Task.result(timeout=...)` - `Task.result_or_none(timeout=...)` - `Task.result_or(timeout=...)` - `TaskGroup.start(...)` - `TaskGroup.start_soon(...)` - `TaskGroup.start_with_stack(...)` - `TaskGroup.start_soon_with_stack(...)` - `TaskGroup.cancel()` - `random.Rng.next_int(...)` - `random.Rng.next_float()` - `random.Rng.shuffle(...)` ## Randomness Import `random` for two deliberately separate surfaces. A mutable `random.Rng(seed)` is a deterministic, move-only xoshiro256** stream with half-open `next_int`, `[0.0, 1.0)` `next_float`, and in-place generic list shuffle. Seed mapping and sequences are stable throughout Aura 0.3.x and identical through MIR and direct execution. `random.secure_int(lo, hi)` and `random.secure_bytes(n)` use only the host operating system's secure source. They have no seed and never fall back to the deterministic generator. `secure_bytes(0)` returns an empty list without an entropy request. Its count is `int64`, with a fixed per-request resource and safety ceiling of `2147483647` independent of the public `list` length domain. Invalid bounds or a negative count traps with `AU4003`; a count above the ceiling traps with `AU4005` before allocation or entropy, and entropy or allocation failure also traps with `AU4005`. There is no `random.Error` or secure floating function. See [20-randomness.md](20-randomness.md). Clone-producing generic bodies infer clone-safety obligations for unresolved type parameters. Requirements propagate through generic calls, imports, trait/default/associated dispatch, operators, and `From`, then reject an unsafe concrete `random.Rng` specialization with `AU3007`. Queue handles remain clone barriers because copying a handle does not observe its payload. An allowed Task-handle copy also does not observe its payload, but `Task[T]` is not copyable when `T` carries a single-consumer result right. ## Pattern Matching The current compiler supports: - `Enum.Variant` - `Enum.Variant(name)` - multi-payload enum variants including named payload fields - unqualified variants such as `Ok(value)` and `None` when the scrutinee type is known - literal patterns over `bool`, integer, and `str` - floating-point literal patterns - top-level complete-value bindings such as `case value:` and `case value if condition:` - `match value:` - `match mut value:` - `case _:` - exhaustive statement-form `match` - expression-form `match` in return, binding, and argument positions - nested enum patterns Boolean literal matches are exhaustive when they cover both `true` and `false`. Integer and `str` literal matches still require a final wildcard arm. Expression-form arms may also evaluate nested block-form expressions. ## Concurrency The current bootstrap concurrency surface includes: - typed queues - `for` iteration over queues until close - task groups - `TaskGroup.start(...)` - `TaskGroup.start_soon(...)` - `TaskGroup.start_with_stack(bytes, ...)` - `TaskGroup.start_soon_with_stack(bytes, ...)` - `Task.result(timeout=...)` - typed `select(queue_or_task_or_duration, ...)` - `wait_any(...)` - `wait_all(...)` - cooperative cancellation - signed i128-nanosecond Duration values with `ms`, `s`, and `m` literals, integer constructors, checked arithmetic, conversions, and comparisons Aura 0.3 executes task bodies on cooperative pinned scheduler workers on both maintained backends. The default count is the available parallelism reported by the host; the provisional `AURA_WORKERS=` override selects an explicit count. Each child receives a stable assignment at spawn time. Coroutine stacks never migrate, work is not stolen, and `yield_now()` yields only to runnable work on the local worker. Every loop backedge has a compiler-inserted scheduling check, including the ordinary body tail and `continue`; `break` and `return` bypass it. Tight loops therefore allow ready timers, queues, or sockets assigned to the same worker to proceed, although a single long loop body can still delay same-worker siblings. The check does not inspect cancellation. Ordinary tasks request a guarded 512 KiB coroutine stack. The two explicit stack-start methods accept an exact `int64` byte request from 256 KiB through 64 MiB inclusive, reject out-of-range values without clamping, and page-round accepted requests. The 256 KiB lower bound is for measured shallow tasks, not the generally safe default. The complete compiled Aura HTTP example requires the 512 KiB default; an isolated runtime round trip can use 256 KiB protocol callers because it excludes the compiled program's language-execution frames and keeps deep host protocol frames on service workers. Scheduler waits use persistent descriptor registrations, a timer heap, and direct Queue, task-completion, and blocking-pool notifications; an idle scheduler blocks until an event or deadline without a periodic tick. Task starts require every captured argument and the target result to be structurally `Transfer` after generic specialization. Copy values, `str`, recursively transferable collections, tuples, classes, enums, and Queue/Task handle identities pass. Shared or mutable access, `random.Rng`, `TaskGroup`, and live filesystem, process, pipe, supervisor, listener, socket, stream, HTTP-exchange, WebSocket, and TLS resources do not. `Transfer` is compiler-derived and has no builtin user trait or escape hatch; an ordinary same-named trait cannot confer the property. A Copy value read through access becomes an owned snapshot and may cross; non-copy access cannot. Queue and Task handle state is synchronized across workers. All other task captures and results remain owned `Transfer` data, preserving a share-nothing boundary. Cancellation and diagnostic context stay per task, while task scheduling, independent completion, and output order remain unspecified. Aura exposes no worker-introspection API or work stealing; parallel speedup depends on the workload. Task results are repeatable only for copy `T`, `Queue[...]`, or recursively repeatable `Task[...]`. `Task[T]` is always transferable but is copyable only for those repeatable results. For every other transferable `T`, `result`, `result_or_none`, and `result_or` consume the handle on their first attempt, including timeout, cancellation, failure, and fallback outcomes. `wait_any` and `wait_all` consume the complete task list for such a `T`; `wait_any` abandons unchosen observation rights. Boundary failures are `AU3008`, attempted duplication of a single-consumer right is `AU3009`, and using a directly observed handle again is moved-value `AU3001`. `select(...)` accepts one or more positional Queue, Task, and relative-Duration sources and returns `SelectOutcome[Q, T]`. All Queue payloads share `Q`, all Task results share `T`, and a missing category uses `None`. Source expressions run once from left to right. Current-task cancellation wins; otherwise the lowest original argument index wins among ready sources. Every non-repeatable Task right is consumed at entry and a losing right is abandoned. Selection uses the ordinary builtin call. Deep HTTP, TLS, and maintained Unix WebSocket operations use a distinct bounded protocol-step service with deep native worker stacks. In the clean Mac14,9 Phase 5.10 report, three 10,000-sleeper runs peaked at 207,798,272, 206,946,304, and 206,831,616 bytes whole-process RSS, preserving the maintained 512 MiB bound. Standalone 1,000-timer controls passed with a 6 ms maximum arm span and 1 ms worst p99 overshoot. The runtime accepts larger task counts; 10,000 sleepers is the maintained memory-capacity bound. Three clean runs of 100,000 sleepers plus 1,000 timers peaked at 1,170,735,104, 1,921,531,904, and 2,001,305,600 bytes; two exceeded the proposed limit while timer behavior remained stable at a 3 ms maximum arm span and 2 ms worst p99 overshoot. Mac14,9 uses 16 KiB pages, giving those 101,000 stackful tasks a 1,654,784,000-byte one-page floor before other runtime and process memory. The earlier Phase 5.9 below-gate sample depended on nondeterministic memory compression. The current four-worker workload passes at a `1.039673x` paired median wall-time ratio with `396.73%` median four-task process CPU. The protocol service is lazily initialized and remains alive until process exit; it has no public shutdown or join surface. File reads, resolver work, and listener binding use the generic blocking-I/O pool. Only subsequent PEM parsing and rustls construction use protocol workers for TLS assets. The generic pool accepts two process settings: `AURA_BLOCKING_WORKERS=` selects an exact, unclamped worker count, while the absent default derives `2..=8` workers from host parallelism with fallback `4`; `AURA_BLOCKING_QUEUE_CAPACITY=` bounds accepted pending jobs only, while omission preserves the unbounded queue. Full-queue admission is FIFO and scheduler-aware. Expiry or cancellation before queue insertion prevents submission. Accepted work still runs once and has any abandoned result discarded. A bound limits accepted pending backlog, not admission waiters, and cannot guarantee unrelated blocking-I/O work while every worker remains stuck. Current collection notes: - `str.len()`, `str.byte_len()`, `list.len()`, `dict.len()`, and `set.len()` return `int64`; `len(value)` delegates to the corresponding `len()` and therefore satisfies `len(value) == value.len()` - `range(...)` bounds, yielded values, list indexes, slice endpoints, enumeration positions, and Array coordinates use `int64`; fixed-width narrower integer values widen losslessly only at those positions - bare list iteration is shared; `for value in own values:` consumes; `for value in mut values:` supports writeback - `for value in mut values:` requires the iterable place itself to be mutable - `list.sort()` and `list.sort(key=callback)` are stable in-place mutations; keyed sorting evaluates one shared key per element from left to right before mutating, so a key trap leaves the source unchanged - built-in list ordering covers all integer types, `float32`, `float64`, and `Duration`; `str` has no built-in `Ord[str]`, so preserve insertion order, use `sort(key=callback)` with an orderable key/index, or define a nominal application type with the required `Ord` behavior - `list.map(f)` and `list.filter(f)` are eager shared traversals that retain the source and return fresh owned lists; `filter` requires clone-safe `T` - list algorithm callbacks have exact bare/shared element parameters; `mut` and `own` callback capabilities are rejected without adaptation - indexed reads from `list[T]` work directly only when `T` is copy; clone-safe non-copy element reads use `get(index)` for an explicit cloned read, while `pop(index)` transfers any stored element - module-level functions cannot redefine a builtin function name such as `len`, `str`, `abs`, or `print`; that rejection is `AU2007` - negative list indexes normalize once as `len + index` for direct reads/writes, `get`, `set`, `pop`, and `swap` - `get` returns `None` when the normalized index is invalid; direct access and mutating methods trap - list and str slices accept all four omitted-endpoint forms, return fresh owned copies, and never clamp invalid or reversed bounds; str slicing counts Unicode scalars in O(n), while list slicing requires clone-safe, repeatably observable elements - `insert(-1, value)` inserts before the last element; `insert(values.len(), value)` appends, and positions outside the range are clamped to the nearest boundary - `list[T]` supports equality and inequality when both sides have the same `list[T]` type and `T` defines equality; `remove`, `index`, `count`, membership, set insertion, and dictionary-key operations enforce the same obligation with `AU2008` - `list.set(index, value)`, `list.pop(index)`, and `list.swap(first, second)` trap on out-of-bounds indices; `list.remove(value)` traps with `AU4008` when no equal value exists - empty dictionary literals need an expected `dict[K, V]` type, or use `dict[K, V]()` explicitly - `dict[K, V]` supports literal construction, indexed writes for every `V`, direct indexed reads under the value ownership rule, and the methods `len`, `is_empty`, `copy`, `get`, `remove`, `keys`, `values`, `items`, `clear`, `update`, `reserve`, and `with_capacity` - `dict.items()` returns `list[(K, V)]` in insertion order - `set[T]` supports literal construction with `{...}`, membership with `in`, and the methods `len`, `is_empty`, `copy`, `add`, `remove`, `discard`, `clear`, `reserve`, and `with_capacity` - bare set iteration is shared; `for value in own set:` consumes - `for value in mut set:` is not currently supported - `Queue[T]` supports `Queue[T](capacity=...)` for bounded-capacity queues on the pinned-worker runtime scheduler; construction, `put`, and `try_put` require a structurally `Transfer` payload type - `Queue.put(...)` returns `Result[None, SendError[T]]`, where `SendError[T]` currently includes `Closed(value)`, `Cancelled(value)`, `TimedOut(value)`, and `Full(value)` - `Queue.get(timeout=...)` returns `QueueReceive[T]`, distinguishing `Item(value)`, `Closed`, `TimedOut`, and `Cancelled` - `Queue.get_or_none(timeout=...)` returns `Option[T]` for the common case where closed, timed out, and cancelled waits all map to “no value”; without a timeout it performs an immediate non-blocking check - `Queue.get_or(default, timeout=...)` returns either the queued value or a caller-provided fallback; without a timeout it returns the fallback immediately when no item is ready - Queue iteration receives owned items and accepts only bare `for value in queue:`; the explicit `own` and `mut` modifiers are rejected - `Task.result(timeout=...)` returns `TaskResult[T]`, distinguishing `Ready(value)`, `Error(message)`, `TimedOut`, and `Cancelled`; for non-repeatable `T`, the call consumes the task handle on every outcome - `wait_any(...)` returns `WaitAny[T]`, distinguishing `Ready(index, value)`, `Error(index, message)`, `TimedOut`, and `Cancelled`; `wait_any([])` returns `TimedOut` immediately, and a non-repeatable `T` makes the call consume the entire task list and abandon unchosen rights - `wait_all(...)` returns `WaitAll[T]`, distinguishing `Ready(results)`, `Error(index, message)`, `TimedOut`, and `Cancelled`; a non-repeatable `T` makes the call consume the entire task list - `Task.result_or_none(timeout=...)` returns `Option[T]` for the common case where task failure, timeout, and cancellation all map to “no result yet”; without a timeout it performs an immediate non-blocking check, and for non-repeatable `T` even a `None` outcome consumes the handle - `Task.result_or(default, timeout=...)` returns either the task result or a caller-provided fallback when the task fails, times out, or is cancelled; without a timeout it returns the fallback immediately when the task is not ready, and for non-repeatable `T` every outcome consumes the handle ## Tooling The current CLI commands are: - `check` - `run` - `build` - `ast` - `ast-json` - `mir` - `analyze` - `complete` - `lsp` - `new` - `fmt` - `test` - `deps update` - `upgrade` - `help` - `version` Current backend/tooling notes: - `build` accepts `--backend auto|direct` - `auto` is the default - `direct` covers the full currently implemented Aura language surface - compiler-backed editor state is invalidated across open documents when imported files change - `file://` URI handling preserves both Windows drive-letter paths and UNC workspaces The current VS Code tooling is compiler-backed for: - diagnostics - document symbols - hover - go-to-definition - completions ## Current Boundaries The current compiler does not support: - non-numeric casts - direct recursive fields without `indirect` - method values, statement-bodied closures, shared parameter captures, or mutable captured state Current module/import limitations: - imports resolve local `.au` files relative to the current package root - directly checking or analyzing a nested package file infers the nearest package root that satisfies its imports - `import a.b` exposes module namespaces for calls like `a.b.func(...)`, `a.b.Type(...)`, and `a.b.Enum.Variant` - type annotations may use namespace-imported types such as `a.b.Type` - both maintained execution paths stop with a friendly recursion-depth diagnostic after 256 nested Aura calls - MIR and direct-native runtime failures preserve matching typed Aura call frames and child-task ancestry; JSON tooling receives them as always-present `call_frames` and `task_ancestry` arrays - package manifests, local path dependencies, and git dependencies are implemented Current expression/ergonomics limitations: - empty list literals still require an expected `list[T]` type such as `values: list[int32] = []` - strings use quoted literals; `str(...)` is not a constructor - enum variants may be called by bare built-in name when an expected type is available, for example `ok: Result[int32, str] = Ok(7)` - `TaskGroup.start(...)`, `TaskGroup.start_soon(...)`, and their explicit-stack variants support capture-free function values, Transfer closure values, and the existing direct named-function and associated-method-without-`self` targets, using task-owned captures; every capture and target result must be structurally `Transfer` after specialization - `TaskGroup()` scope exit waits for started tasks and surfaces every unread task failure - `group.cancel()` wakes queue iteration over `Queue[T]` in the same `with TaskGroup()` scope so `for value in queue:` can exit cleanly - concurrency uses only the maintained `Queue[T]()`, `Task.result()`, `TaskGroup()`, its four start methods, `yield_now()`, `wait_any(...)`, and `wait_all(...)` surface - `control.retry(worker, max_attempts=3, initial_backoff=0ms)` runs a `def() -> Result[T, E]` worker immediately, retries every `Err` with doubling delays, skips zero sleeps, and returns the exact final error without a post-final wait or multiply; it validates a positive attempt budget and a non-negative, host-representable backoff before the worker runs, and traps and cancellation propagate - queue waits, `sleep(...)`, socket waits, and the maintained HTTP helpers all use the pinned-worker evented runtime scheduler - Aura tasks are pinned-worker scheduler-backed lightweight tasks, and ordinary file I/O offloads through that runtime, keeping the task free from a blocking host thread - Unix domain sockets require a Unix host at runtime - subprocess APIs are shell-free and use explicit argv vectors; process groups and restart supervision are implemented, while PTY support is not - every function return is owned: copy results are ordinary copies, while a non-copy result must be constructed, cloned, moved from owned input, or obtained through an owner operation ## Source: tutorials/15-generics.md # Generics Generics let one `Box[T]` definition work across the types that satisfy its requirements. Separate `BoxInt`, `BoxString`, and `BoxFloat` classes are not needed. ## Generic Classes ```aura check-pass class Box[T]: value: T def get(own self) -> T: return self.value ``` Use it with any type: ```aura fragment int_box: Box[int32] = Box(value=7) print(int_box.get()) text_box: Box[str] = Box(value="hello") print(text_box.get()) ``` You can also provide the type argument explicitly at the constructor: ```aura fragment boxed = Box[int32](value=7) ``` The compiler infers type arguments from the surrounding expected type or the provided field values, so explicit arguments are optional when the type is clear. ## Bounded Type Parameters Sometimes a generic class should only accept types that implement a specific trait (see [16-traits.md](16-traits.md)): ```aura fragment class Wrapper[T: Greeter]: value: T ``` This restricts `T` to types that implement `Greeter`. Attempting to construct a `Wrapper` with a type that does not implement `Greeter` produces a compile error. ## Generic Enums Enums can also be generic. Unit variants and payload variants both work: ```aura check-pass enum Wrapper[T]: Item(T) Empty ``` ```aura fragment wrapped: Wrapper[str] = Wrapper.Item("ok") match wrapped: case Wrapper.Item(value): print(value) # value is str case Wrapper.Empty: print("empty") ``` Payload matching uses the instantiated payload type, so `value` is a `str` in the example above. Generic enums may also use bounded type parameters: ```aura fragment enum MaybeNamed[T: Greeter]: Some(T) Empty ``` ## Generic Functions ```aura check-pass def identity[T](value: own T) -> T: return value ``` The compiler infers type arguments from call arguments and the expected return type: ```aura fragment print(identity(7)) # infers T = int64 text: str = identity("aura") # infers T = str print(text) ``` Method calls on generic class instances work inside generic functions: ```aura fragment def extract[T](box: own Box[T]) -> T: return box.get() ``` The `own` spelling matters for unresolved generics. A bare `value: T` is fixed as a shared borrow when this declaration is checked and stays shared even if a call later uses a copy type. Use `own T` when the body returns, stores, or otherwise consumes the value. ## Inferred Clone-Safety A generic body may clone values without rejecting the declaration merely because `T` is unresolved: ```aura check-pass def duplicate[T](values: list[T]) -> list[T]: return values.copy() def forward[T](values: list[T]) -> list[T]: return duplicate(values) ``` Aura infers that `T` must be clone-safe. A call with `int32` or `str` works. A call with `random.Rng`, including through a class, enum, or collection wrapper, is rejected with `AU3007`. `forward` receives the same requirement through its generic-to-generic call. The inferred contract also survives a module import; callers do not gain a clone route by moving the helper to another file. Clone safety and task transport are separate rules. A `Queue[T]` handle has copyable identity, but constructing or sending through the queue requires concrete `T: Transfer`, so `Queue[random.Rng]()` is rejected with `AU3008`. `Task[T]` is always a transferable handle, but is copyable only when `T` is repeatable; `random.Rng` is neither `Transfer` nor repeatable, so a task may not return it. Aura does not infer a deferred `Transfer` obligation for an unresolved type parameter. A generic task target must be fully specialized by inference, defaults, or the narrow explicit target form `function[Types]` (and the equivalent associated-method form) before `TaskGroup.start(...)` can validate its captured arguments and result. ## Current Limits The implemented generic surface supports: - generic `class`, `enum`, and `def` declarations - generic `trait` declarations - trait bounds on type parameters - explicit type arguments on constructors like `Box[int32](...)` - inference for generic function calls and constructors - method calls on generic instances inside generic functions - generic enum unit variants with explicit type arguments such as `Maybe[int32].Nothing` - generic trait impl headers like `impl Mapper[T] for Box[T]:` - inferred clone-safety obligations with generic-to-generic and imported propagation See [examples/generics/box_and_wrapper.au](../examples/generics/box_and_wrapper.au), [examples/generics/generic_method_calls.au](../examples/generics/generic_method_calls.au), [examples/generics/generic_constructor_specialization.au](../examples/generics/generic_constructor_specialization.au), [examples/generics/bounded_types.au](../examples/generics/bounded_types.au), and [examples/generics/clone_safety_obligations.au](../examples/generics/clone_safety_obligations.au). ## Source: tutorials/16-traits.md # Traits Traits define shared behavior that different types can implement. If you know Python's abstract base classes or Go's interfaces, traits serve a similar purpose -- they let you write code that works with any type that provides the required methods. ## Declaring A Trait A trait lists method signatures. Methods may omit a body or provide a default implementation: ```aura check-pass trait Greeter: def greet(self) -> str ``` ```aura check-pass trait Named: def name(self) -> str def label(self) -> str: return "name=" + self.name() ``` Empty marker traits use `pass`: ```aura check-pass trait Marker: pass ``` Generic traits use the same `Name[T]` syntax as classes: ```aura check-pass trait Mapper[T]: def map(self, value: own T) -> T ``` Trait methods and impl methods may also use `Self` in parameter and return positions: ```aura check-pass trait Combine: def combine(self, other: Self) -> Self ``` Traits may also inherit from other traits: ```aura check-pass trait Named: def name(self) -> str trait Labelled: Named: def label(self) -> str: return "name=" + self.name() ``` When a type implements `Labelled`, it must also implement `Named`. Generic bounds such as `T: Labelled` inherit the methods and obligations of the supertraits. ## Implementing A Trait Use `impl Trait for Type:` to provide the trait's methods for a concrete type: ```aura fragment class User: name: str impl Greeter for User: def greet(self) -> str: return "hello " + self.name ``` You can also implement traits for specialized generic instances: ```aura fragment class Box[T]: value: T impl Greeter for Box[str]: def greet(self) -> str: return self.value.clone() ``` Open generic impl headers work too: ```aura fragment impl[T] Showable for Box[T]: def show(self) -> str: return "box" ``` And generic traits can be implemented for generic classes: ```aura fragment impl Mapper[T] for Box[T]: def map(self, value: own T) -> T: return value ``` ## Clone-Safety Is Part Of The Trait Contract When a generic trait default method performs a clone-producing operation, Aura infers a clone-safety obligation as part of that method's contract: ```aura check-pass trait Duplicator[T]: def duplicate(self, values: list[T]) -> list[T]: return values.copy() ``` The requirement follows `T` and `Self` through every implementation, concrete call, associated call, and bounded generic call. A safe specialization works; one containing `random.Rng` is rejected with `AU3007`. A signature-only trait method has no inferred obligation. An explicit `impl` may satisfy the trait contract but may not strengthen it by adding hidden generic clone-producing behavior. Aura 0.3 has no written clone-safety bound, so put that behavior in a default trait body when it is part of the intended contract. ## Trait Bounds On Generic Functions Generic functions can require that a type parameter implements a trait using inline bounds: ```aura fragment def speak[T: Greeter](value: T): print(value.greet()) ``` At the call site, Aura checks that the concrete type implements the required trait: ```aura fragment speak(value=User(name="aura")) # User implements Greeter, so this works ``` Multiple bounds use `+`: ```aura fragment def use_both[T: A + B](value: T) -> int32: return value.a() + value.b() ``` ## Trait Bounds On Classes And Enums Class and enum type parameters can also carry trait bounds: ```aura fragment class Wrapper[T: Greeter]: value: T ``` See [15-generics.md](15-generics.md) for more on generic type parameters. ## Specialized Generic Trait Bounds Bounds can be specialized, which is useful when the trait itself is generic: ```aura fragment def apply[T: Mapper[int32]](mapper: T, value: int32) -> int32: return mapper.map(value=value) ``` This says: `T` must implement `Mapper` specifically for `int32`. Specialized dispatch works across multiple implementing types in the same program: ```aura check-pass trait Describe: def describe(self) -> str class Dog: name: str class Cat: label: str impl Describe for Dog: def describe(self) -> str: return "dog" impl Describe for Cat: def describe(self) -> str: return "cat" def show[T: Describe](animal: T) -> None: print(animal.describe()) ``` See [examples/traits/generic_dispatch_multiple_types.au](../examples/traits/generic_dispatch_multiple_types.au), [examples/traits/generic_trait_bounds.au](../examples/traits/generic_trait_bounds.au), and [examples/traits/specialized_trait_dispatch.au](../examples/traits/specialized_trait_dispatch.au). See [examples/traits/supertraits.au](../examples/traits/supertraits.au) for a runnable supertrait example. See [examples/traits/self_parameters.au](../examples/traits/self_parameters.au) for a runnable `Self`-parameter example. ## Associated Methods Traits can declare methods without a receiver. They are called through the implementing type name: ```aura check-pass trait Factory: def make() -> int32 class Widget: value: int32 impl Factory for Widget: def make() -> int32: return 7 print(Widget.make()) # 7 ``` See [examples/traits/trait_associated_factory.au](../examples/traits/trait_associated_factory.au). ## Operator Traits Aura supports operator overloading through traits. When you implement the right trait, standard operators like `+` and `-` work with your types: | Operator | Trait | Method | |----------|-------|--------| | `a + b` | `Add[Rhs, Out]` | `add(self, rhs: Rhs) -> Out` | | `a - b` | `Sub[Rhs, Out]` | `sub(self, rhs: Rhs) -> Out` | | `a * b` | `Mul[Rhs, Out]` | `mul(self, rhs: Rhs) -> Out` | | `a / b` | `Div[Rhs, Out]` | `div(self, rhs: Rhs) -> Out` | | `a // b` | `FloorDiv[Rhs, Out]` | `floor_div(self, rhs: Rhs) -> Out` | | `a % b` | `Mod[Rhs, Out]` | `mod(self, rhs: Rhs) -> Out` | | `a < b` | `Ord[Rhs]` | `lt(self, rhs: Rhs) -> bool` | | `a <= b` | `Ord[Rhs]` | `le(self, rhs: Rhs) -> bool` | | `a > b` | `Ord[Rhs]` | `gt(self, rhs: Rhs) -> bool` | | `a >= b` | `Ord[Rhs]` | `ge(self, rhs: Rhs) -> bool` | | `-a` | `Neg[Out]` | `neg(self) -> Out` | | `not a` | `Not[Out]` | `not(self) -> Out` | Builtin numeric floor division and `Duration // int64` take precedence. When neither rule applies, `//` and `//=` resolve through `FloorDiv.floor_div`. Equal integer operands with `/` are rejected before trait dispatch, while `/` on an applicable non-numeric user type still resolves through `Div.div`. Example: ```aura fragment class Point: x: int32 y: int32 impl Add[Point, Point] for Point: def add(self, rhs: Point) -> Point: return Point(x=self.x + rhs.x, y=self.y + rhs.y) impl Neg[Point] for Point: def neg(self) -> Point: return Point(x=0 - self.x, y=0 - self.y) ``` With these impls, you can use `+` and `-` with `Point` values, including through generic bounds: ```aura fragment def add_all[T: Add[T, T]](left: T, right: T) -> T: return left + right ``` See [examples/traits/operator_traits.au](../examples/traits/operator_traits.au). Operator dispatch enforces the selected trait method's inferred clone-safety contract. The `From.from` method selected by `try` does the same. Ordering traits work the same way for `<`, `<=`, `>`, and `>=`: ```aura check-pass trait Ord[Rhs]: def lt(self, rhs: Rhs) -> bool def le(self, rhs: Rhs) -> bool def gt(self, rhs: Rhs) -> bool def ge(self, rhs: Rhs) -> bool ``` This lets you write generic ordered code such as: ```aura fragment def choose_smaller[T: Ord[T]](left: own T, right: own T) -> T: if left < right: return left return right ``` See [examples/traits/ordering_traits.au](../examples/traits/ordering_traits.au). ## Traits On Builtin Types A trait can also target a builtin type, not only your own classes and enums: ```aura check-pass trait Describe: def describe(self) -> str impl Describe for list[int32]: def describe(self) -> str: return f"list of {self.len()}" impl Describe for str: def describe(self) -> str: return f"text of {self.len()}" ``` The one restriction is that the method name must not already be a builtin member of that target. A method named `len` would be rejected with `AU2006`, because the builtin `len` always wins at every call site and the trait body would silently never run: ```text error[AU2006]: trait method `len` collides with builtin method `list.len` = help: rename the trait method; builtin methods cannot be shadowed by trait implementations ``` This holds for every builtin target: the runtime handles such as `Queue[T]`, `Task[T]`, `TaskGroup`, `random.Rng`, and `fs.File`, and the builtin value types such as `str`, `list[T]`, `dict[K, V]`, `set[T]`, `Duration`, and the scalar types. See [examples/traits/builtin_target_traits.au](../examples/traits/builtin_target_traits.au). ## Current Limits The implemented trait surface supports: - trait declarations (signature-only methods, default methods, marker traits with `pass`) - `impl Trait for Type:` blocks - specialized impls like `impl Trait for GenericType[ConcreteType]:` - generic trait declarations and generic impl headers - supertrait declarations such as `trait Child: Parent:` - bounded generic functions, methods, classes, and enums - specialized bounds like `T: Mapper[int32]` - multiple bounds with `T: A + B` - direct trait-method calls on concrete types - trait implementations for builtin targets, for method names that do not collide with a builtin member of that target - `Self` in trait and impl method parameter and return positions - associated methods without `self` - operator traits for `+`, `-`, `*`, `/`, `//`, `%`, `<`, `<=`, `>`, `>=`, unary `-`, and `not` - inferred clone-safety contracts from trait defaults, with explicit impls forbidden from strengthening them See [examples/traits/clone_safety_contract.au](../examples/traits/clone_safety_contract.au) for a runnable default-method contract. ## Source: tutorials/17-modules-and-visibility.md # Modules And Visibility Aura supports local file modules with `import`, `from ... import ...`, and `public` visibility boundaries. Modules let you organize code across files and control what is exposed to other parts of your project. ## Importing A Module Use Python-style import syntax to bring in a module by its file path: ```aura fragment import helpers.math ``` This resolves to `helpers/math.au` relative to the current source root. Call public functions through the module path: ```aura fragment print(helpers.math.double(value=5)) ``` Namespace imports also work for classes and enums: ```aura fragment import pkg.types counter = pkg.types.Counter(value=4) status = pkg.types.Status.Ready ``` Module-qualified type annotations are supported: ```aura fragment counter: pkg.types.Counter = pkg.types.Counter(value=4) ``` ## Importing Names Directly Use `from ... import ...` to bring a name into the local scope: ```aura fragment from helpers.counter import Counter ``` This is the most concise way to use names without repeating module paths. You can import public functions, classes, enums, traits, and module constants. ## Module Constants Declare stable configuration and constructed immutable values beside the functions that use them: ```aura check-pass service_name = "planner" public max_attempts: int64 = 3 retry_budget = max_attempts + 2 def main(): print(service_name) print(retry_budget) ``` Constants initialize eagerly before `main`. Imported dependencies initialize before the importing module, imports are visited in source order, and each module initializes once. Within a module, a constant may use functions and earlier constants. It cannot read itself or a later constant. Module bindings cannot use `mut` and cannot be reassigned. Copy values read as ordinary copies. Non-Copy values stay owned by the defining module and each read grants shared access. Call `.clone()` when the type supports it and the program needs independent owned data. Export a constant with `public` and import it through either form: ```aura fragment import settings from settings import max_attempts as configured_attempts def main(): print(settings.max_attempts) print(configured_attempts) ``` ## Import Aliases Use `as` to choose a concise or collision-free local name for a module: ```aura fragment import helpers.math as integer_math print(integer_math.double(value=5)) ``` Individual from-import entries may also be aliased: ```aura fragment from helpers.counter import Counter as ReadableCounter counter = ReadableCounter(value=2) ``` A from-import may mix direct and aliased entries. The alias changes only the local spelling. Visibility, type identity, trait implementations, and module resolution continue to use the original declaration. ## `public` Visibility Top-level items are private by default. Mark items with `public` to make them available to other modules: ```aura check-pass public def double(value: int32) -> int32: return value * 2 ``` For classes, both the class itself and its fields/methods have independent visibility: ```aura check-pass public class Counter: public value: int32 public def read(self) -> int32: return self.value def internal_reset(mut self): self.value = 0 ``` Across module boundaries: - importing a private top-level item is rejected - reading a private field is rejected - calling a private method is rejected - keyword construction only exposes `public` fields -- you cannot set a private field from another module - trait impls defined in imported modules still participate in generic bounds and method lookup - inferred clone-safety obligations on public generic functions and methods survive both namespace and direct imports Within the same module, all members are accessible regardless of visibility. ## Packages And Dependency Imports When a file lives under a package with `Aura.toml`, the package's `src/` directory is the source root. Local imports work the same way: ```aura fragment import helpers.math # resolves to src/helpers/math.au ``` Dependencies declared in the manifest are imported by package name: ```aura fragment import util.math # resolves to the util dependency's src/math.au ``` See [18-packages-and-workspaces.md](18-packages-and-workspaces.md) for the full package system. ## Maintained Examples - [examples/modules/simple_import.au](../examples/modules/simple_import.au) with helpers under [examples/modules/helpers](../examples/modules/helpers) - [examples/modules/import_aliases.au](../examples/modules/import_aliases.au) demonstrates module and from-import aliases - [examples/modules/constants.au](../examples/modules/constants.au) demonstrates inferred, annotated, public, and dependent constants beside `main` - [examples/modules/namespace_import_types.au](../examples/modules/namespace_import_types.au) with modules under [examples/modules/pkg](../examples/modules/pkg) - [examples/modules/trait_impl_imports.au](../examples/modules/trait_impl_imports.au) with modules under [examples/modules/pkg](../examples/modules/pkg) - [examples/packages/local_path_dependencies/app/src/main.au](../examples/packages/local_path_dependencies/app/src/main.au) with a sibling dependency ## Current Limits - module resolution is local-file based plus package dependencies from local paths or git repositories - registry-style version resolution and publishing are not implemented yet ## Source: tutorials/18-packages-and-workspaces.md # Packages And Workspaces Aura supports a package system built around `Aura.toml` manifest files. Packages let you organize larger projects with multiple source directories, share code through local path and git dependencies, and group related packages into workspaces. ## Single Package A package has an `Aura.toml` manifest and source files under `src/`: ```text my-app/ Aura.toml src/main.au src/helpers/math.au ``` The manifest declares the package identity: ```toml [package] name = "app" version = "0.1.0" edition = "2026" ``` Run the package by pointing `aura` at a file under `src/`: ```bash cargo run -p aura -- run my-app/src/main.au ``` The compiler treats the directory containing `Aura.toml` as the package root and `src/` as the source root. Local imports resolve relative to `src/`: ```aura fragment import helpers.math # resolves to src/helpers/math.au ``` An alias changes the local spelling without changing that resolution path: ```aura fragment import helpers.math as integer_math from helpers.math import double as twice ``` ## Local Path Dependencies Declare dependencies relative to the manifest directory: ```toml [dependencies] util = { path = "../util" } ``` Then import through the package name: ```aura fragment import util.math ``` The dependency package must have its own `Aura.toml` with a matching `name`. Transitive dependencies are resolved through the package graph. See [examples/packages/local_path_dependencies/app/src/main.au](../examples/packages/local_path_dependencies/app/src/main.au). ## Git Dependencies Dependencies can also come from git repositories: ```toml [dependencies] util = { git = "https://github.com/example/util.git" } jsonx = { git = "https://github.com/example/jsonx.git", tag = "v0.3.1" } release_math = { git = "https://github.com/example/math.git", branch = "release" } frozen_math = { git = "https://github.com/example/math.git", rev = "4f2c9d8b7e..." } ``` Git dependencies support three selectors: - `branch = "name"` -- track a branch (default: `"main"` when no selector is provided) - `tag = "v1.0.0"` -- pin to a specific tag - `rev = "abc123..."` -- pin to an exact commit Imports work the same way as path dependencies -- use the package name: ```aura fragment import util.math import jsonx.parser ``` The complete dependency path may be aliased after it resolves: ```aura fragment import util.math as util_math from jsonx.parser import parse as parse_json ``` ## Workspaces Workspace roots group related packages under a single top-level manifest: ```toml [workspace] members = ["app", "util"] ``` ```text my-workspace/ Aura.toml # workspace root app/ Aura.toml # [package] name = "app" src/main.au util/ Aura.toml # [package] name = "util" src/math.au ``` Member packages keep their own `[package]` section and dependency lists. The workspace root only declares membership. See [examples/packages/workspace/Aura.toml](../examples/packages/workspace/Aura.toml) and [examples/packages/workspace/app/src/main.au](../examples/packages/workspace/app/src/main.au). ## Lockfiles Aura writes an `Aura.lock` file to record the resolved dependency graph: - for standalone packages: beside `Aura.toml` - for workspace members: at the workspace root The lockfile records: - local path dependencies with their relative paths - git dependencies with their source URL and the exact pinned revision This ensures reproducible builds. Later runs use the pinned revisions from the lockfile until you explicitly update it. When you want to refresh moving git references, use the CLI update command from inside the package or workspace: ```bash aura deps update aura deps update util ``` `aura deps update` refreshes all branch/tag/default-main git dependencies in the current package graph. `aura deps update util` refreshes only the named git dependency. ## Current Limits The package system is intentionally local-first: - supported dependency forms: `{ path = "..." }` and `{ git = "...", branch/tag/rev = "..." }` - version-only registry dependencies like `util = "0.1.0"` are rejected with a clear diagnostic - no registry, publish, or install flows yet - no version solving This is deliberate -- Aura supports real multi-package development before taking on registry infrastructure. ## Source: tutorials/19-io-and-networking.md # I/O And Networking Aura has a maintained I/O surface through four builtin modules: - `io` - `fs` - `net` - `process` These modules are imported like ordinary namespaces: ```aura check-pass import io import fs import net import process ``` The current runtime model uses scheduler-backed lightweight tasks. Queue waits, timer waits, and the maintained socket/HTTP surface share the same evented runtime scheduler. They park until an event is ready. Hostname resolution and blocking connect syscalls run on the generic blocking-I/O pool, so a slow DNS resolver or connect attempt does not pin the lightweight-task scheduler. ## Standard Input And Output Use `io.write(...)`, `io.flush()`, and `io.read_line()` for explicit terminal I/O: ```aura check-pass import io def main() -> int32: match io.write("name> "): case Result.Ok(_): pass case Result.Err(_): return 1 match io.flush(): case Result.Ok(_): pass case Result.Err(_): return 1 match io.read_line(): case Result.Ok(Option.Some(line)): print(line) return 0 case Result.Ok(Option.None): return 0 case Result.Err(_): return 1 ``` `io.read_line()` returns `Result[Option[str], io.Error]`: - `Result.Ok(Option.Some(text))` when a line was read - `Result.Ok(Option.None)` on end-of-file - `Result.Err(...)` on I/O failure ## File I/O The `fs` module provides one-shot helpers and scoped file handles. Text and binary one-shot helpers: - `fs.exists(path)` - `fs.read_to_string(path)` - `fs.read_bytes(path)` - `fs.write_string(path, text)` - `fs.write_bytes(path, bytes)` - `fs.append_string(path, text)` - `fs.append_bytes(path, bytes)` - `fs.create_dir(path)` - `fs.read_dir(path)` - `fs.remove_file(path)` The one-shot helpers and `fs.File` whole-file reads are capped at 256 MiB of remaining content in both `aura run` and built binaries. Aura 0.3 has no incremental file-read member, so larger files need a host helper or pre-splitting. Scoped file-handle constructors: - `fs.open(path)` - `fs.create(path)` - `fs.append(path)` Those constructors return `Result[fs.File, io.Error]`. `fs.File` works with `with` and exposes: - `read_all()` - `read_bytes()` - `write_all(text)` - `write_bytes(bytes)` - `flush()` - `close()` Text example: ```aura check-pass import fs import io def load_text(path: str) -> Result[str, io.Error]: with file = try fs.open(path): return file.read_all() ``` Binary example: ```aura check-pass import fs import io def copy_bytes(path: str) -> Result[list[uint8], io.Error]: with file = try fs.open(path): bytes = try file.read_bytes() return Result.Ok(bytes) ``` Crossing between raw data and UTF-8 text is explicit. Use `text.to_bytes()` or `str.from_bytes(payload)`. [22-bytes.md](22-bytes.md) covers those conversions plus strict hex/base64 codecs and SHA-256. See: - [examples/io/read_text_file.au](../examples/io/read_text_file.au) - [examples/io/bytes_file_io.au](../examples/io/bytes_file_io.au) ## Processes The `process` module provides shell-free subprocess helpers that fit the current Aura runtime model. Process constructors: - `process.supervisor()` - `process.start(command, cwd=..., env=..., stdin=..., stdout=..., stderr=..., group=false)` - `process.run(command, cwd=..., env=..., stdin=..., stdout=..., stderr=..., timeout=..., group=false)` - `process.inherit()` - `process.null()` - `process.pipe()` `command` is always an explicit `list[str]` argv list. Aura does not provide a shell-string subprocess API. When `group=true`, Aura starts the child in its own process group and applies terminate/kill/close cleanup to that full group. On current maintained hosts, grouped children are supported on Unix. `process.start(...)` returns `Result[process.Child, process.Error]`. `process.Child` works with `with` and exposes: - `stdin()` - `stdout()` - `stderr()` - `wait(timeout=...)` - `wait_or_none(timeout=...)` - `wait_ok(timeout=...)` - `kill()` - `terminate()` - `close()` `process.pipe()` is used to request captured child stdio streams. `process.Pipe` works with `with` and exposes: - `read_all()` - `read_line(timeout=...)` - `read_bytes(max_bytes, timeout=...)` - `write_all(text, timeout=...)` - `write_bytes(bytes, timeout=...)` - `flush()` - `close()` `process.run(...)` returns `Result[process.Completed, process.Error]`. `process.Completed` exposes: - `status()` - `success()` - `stdout()` for UTF-8 text - `stderr()` for UTF-8 text - `stdout_bytes()` for raw bytes - `stderr_bytes()` for raw bytes - `check()` `process.supervisor()` returns `process.Supervisor`. `process.Supervisor` works with `with` and exposes: - `start(name, command, cwd=..., env=..., stdin=..., stdout=..., stderr=..., restart=..., backoff=..., max_restarts=..., group=true)` - `wait(timeout=...)` - `wait_or_none(timeout=...)` - `stop()` - `is_empty()` - `close()` Related process-supervisor enums: - `process.RestartPolicy` - `process.SupervisorEvent` - `process.SupervisorWait` Supervisor children default to `group=true`, so `stop()` and `close()` shut down the leader process and its full child tree. When `restart` is `process.RestartPolicy.OnFailure` or `process.RestartPolicy.Always`, `backoff` must be at least `10ms` to prevent zero-delay restart loops. `Supervisor.start` retains the configuration it may need for a restart, so all of its configuration slots are explicit `own` parameters. This includes the copy-valued restart, backoff, count, and group settings; `own` is harmless for copy values and keeps the retention contract uniform. Clone a clone-safe move value before the call only when the caller also needs an independent copy; otherwise ownership must transfer. `process.Child.close()` is cleanup-oriented: it sends a graceful terminate signal first, waits briefly, and escalates to kill if the child does not exit promptly. For grouped children it waits for the full child process group to disappear before returning. One-shot example: ```aura check-pass import process def run_echo() -> Result[None, process.Error]: completed = try process.run(["/bin/echo", "aura process"], stdout=process.pipe(), stderr=process.pipe(), timeout=1s, group=true) try completed.check() print(completed.stdout().trim()) print(completed.stdout_bytes().len()) return Result.Ok(None) ``` Interactive pipe example: ```aura check-pass import process def roundtrip() -> Result[None, process.Error]: with child = try process.start(["/bin/cat"], stdin=process.pipe(), stdout=process.pipe(), stderr=process.null(), group=true): match child.stdin(): case Option.Some(stdin_pipe): try stdin_pipe.write_all("ping\n", timeout=500ms) try stdin_pipe.flush() stdin_pipe.close() case Option.None: pass match child.stdout(): case Option.Some(stdout_pipe): match try stdout_pipe.read_line(timeout=500ms): case Option.Some(text): print(text.trim()) case Option.None: pass case Option.None: pass print(try child.wait_ok(timeout=2s)) return Result.Ok(None) ``` See: - [examples/io/process_run.au](../examples/io/process_run.au) - [examples/io/process_pipes.au](../examples/io/process_pipes.au) - [examples/io/process_supervisor.au](../examples/io/process_supervisor.au) Supervisor example: ```aura check-pass import process def supervise() -> Result[None, process.Error]: with supervisor = process.supervisor(): try supervisor.start(name="flaky", command=["/usr/bin/false"], restart=process.RestartPolicy.OnFailure, backoff=10ms, max_restarts=1, group=true) print(try supervisor.wait_or_none(timeout=500ms)) print(try supervisor.wait_or_none(timeout=500ms)) print(supervisor.is_empty()) try supervisor.start(name="sleeper", command=["/bin/sleep", "1"], restart=process.RestartPolicy.Never, group=true) print(supervisor.is_empty()) try supervisor.stop() print(supervisor.is_empty()) return Result.Ok(None) ``` ## TCP The `net` module provides TCP clients and listeners on the maintained nonblocking socket runtime: - `net.connect(address)` - `net.connect_timeout(address, timeout)` - `net.listen(address)` `net.TcpListener` methods: - `accept(timeout=...)` - `local_addr()` - `close()` `net.TcpStream` methods: - `read_all(timeout=...)` - `read_line(timeout=...)` - `read_bytes(max_bytes, timeout=...)` - `read_exact(count, timeout=...)` - `write_all(text, timeout=...)` - `write_bytes(bytes, timeout=...)` - `flush()` - `local_addr()` - `peer_addr()` - `shutdown_read()` - `shutdown_write()` - `shutdown_both()` - `close()` Both listener and stream resources work with `with`. Text example: ```aura check-pass import io import net def serve(addresses: Queue[str]) -> Result[None, io.Error]: with server = try net.listen("127.0.0.1:0"): addresses.put(try server.local_addr()) with stream = try server.accept(timeout=1s): match try stream.read_line(timeout=1s): case Option.Some(text): try stream.write_all("echo:" + text, timeout=1s) try stream.flush() case Option.None: pass return Result.Ok(None) ``` Listeners and other live network resources are not `Transfer`, so a worker task creates and owns its listener. The copy queue handle crosses the task boundary, and the worker sends the listener's owned `str` address back after binding. See: - [examples/io/tcp_echo.au](../examples/io/tcp_echo.au) - [examples/io/tcp_bytes.au](../examples/io/tcp_bytes.au) ## UDP Aura also supports UDP sockets on the same poll-driven runtime: - `net.udp_bind(address)` `net.UdpSocket` methods: - `send_text(address, text, timeout=...)` - `send_bytes(address, bytes, timeout=...)` - `recv(max_bytes, timeout=...)` - `recv_from(max_bytes, timeout=...)` - `local_addr()` - `peer_addr()` - `close()` `recv_from(...)` returns `Option[net.UdpDatagram]`. `net.UdpDatagram` exposes: - `address()` - `bytes()` - `text()` See [examples/io/udp_echo.au](../examples/io/udp_echo.au). ## HTTP The maintained HTTP convenience surface includes: - `net.http_listen(address)` - `net.http_request_text(method, url, body, headers)` - `net.http_request_text_timeout(method, url, body, headers, timeout)` - `net.http_request_bytes(method, url, bytes, headers)` - `net.http_request_bytes_timeout(method, url, bytes, headers, timeout)` `net.HttpListener` methods: - `accept(timeout=...)` - `local_addr()` - `close()` `net.HttpExchange` methods: - `method()` - `path()` - `headers()` - `body_text()` - `body_bytes()` - `respond_text(status, text, headers)` - `respond_bytes(status, bytes, headers)` `net.HttpResponse` methods: - `status()` - `reason()` - `headers()` - `text()` - `bytes()` See [examples/io/http_roundtrip.au](../examples/io/http_roundtrip.au). ### Application-Level Retries Aura's HTTP helpers perform one request. The generic `control.retry` helper can repeat a capture-free `def() -> Result[T, E]` worker when every `Err` is retryable, using a fixed attempt budget and exponential `Duration` backoff. HTTP status classification, jitter, and richer policy remain an application concern. The maintained [retrying network worker](../examples/agents/retrying_network_worker.au) retries only `503`, returns other statuses such as `429` unchanged, and returns the last `503` when its attempt budget is exhausted. The example uses `random.Rng(42)` for deterministic jitter, doubles a `Duration` backoff after each retry, and checks the final-attempt guard before the RNG draw, retry log, and `sleep(...)`. It makes seven real requests against an ephemeral loopback listener and applies explicit five-second deadlines to listener acceptance, HTTP requests, and task results. `TaskGroup` and `with` scopes ensure the worker, server, listener, exchanges, and responses are all closed deterministically. The CLI conformance test executes the exact trace on both the MIR and forced-direct backends. ## WebSockets The maintained WebSocket surface includes: - `net.websocket_listen(address)` - `net.websocket_connect(url)` - `net.websocket_connect_timeout(url, timeout)` `net.WebSocketListener` methods: - `accept(timeout=...)` - `local_addr()` - `close()` `net.WebSocket` methods: - `send_text(text, timeout=...)` - `send_bytes(bytes, timeout=...)` - `recv_text(timeout=...)` - `recv_bytes(timeout=...)` - `close()` See [examples/io/websocket_roundtrip.au](../examples/io/websocket_roundtrip.au). ## Unix Sockets And TLS Aura also supports Unix domain stream sockets and TLS streams on the maintained nonblocking socket runtime. Unix-socket constructors: - `net.unix_listen(path)` - `net.unix_connect(path)` - `net.unix_connect_timeout(path, timeout)` Unix-socket resource methods: - `net.UnixListener.accept(timeout=...)` - `net.UnixListener.close()` - `net.UnixStream.read_line(timeout=...)` - `net.UnixStream.read_exact(count, timeout=...)` - `net.UnixStream.write_all(text, timeout=...)` - `net.UnixStream.close()` TLS constructors: - `net.tls_listen(address, cert_pem_path, key_pem_path)` - `net.tls_connect(address, server_name, ca_pem_path)` - `net.tls_connect_timeout(address, server_name, ca_pem_path, timeout)` TLS resource methods: - `net.TlsListener.accept(timeout=...)` - `net.TlsListener.local_addr()` - `net.TlsListener.close()` - `net.TlsStream.read_line(timeout=...)` - `net.TlsStream.read_exact(count, timeout=...)` - `net.TlsStream.write_all(text, timeout=...)` - `net.TlsStream.close()` Unix domain sockets require a Unix host at runtime. See [examples/io/unix_tls_roundtrip.au](../examples/io/unix_tls_roundtrip.au), which embeds a self-signed certificate so it stays runnable without extra setup. ## Timeouts And Cancellation Most maintained socket operations accept optional `timeout=...` arguments. Timeouts are expressed with Aura `Duration` values such as `100ms`, `1s`, or `2m`. Computed timeouts may use `Duration.ms(n)` or arithmetic such as `attempt * 1ms`. Explicit values must be non-negative and fit the host deadline; invalid values return `io.Error.InvalidInput`. Only an omitted timeout is unlimited. For connect operations, one timeout budget covers blocking-pool admission, hostname resolution, every resolved-address attempt, and the remaining protocol handshake. Aura does not restart the full timeout for each address returned by DNS. Cancellation or expiry before pool acceptance prevents submission. After acceptance it stops the Aura task's wait, but the host resolver or connect syscall may finish later and its result is discarded safely. `AURA_BLOCKING_WORKERS=` selects an exact worker count without clamping; the absent default uses host parallelism with fallback `4` and a derived `2..=8` clamp. `AURA_BLOCKING_QUEUE_CAPACITY=` optionally bounds accepted pending jobs only, and omission preserves an unbounded queue. Full-queue admission is FIFO and scheduler-aware. The queue bound limits accepted pending backlog, not admission waiters, and cannot interrupt accepted work or guarantee unrelated blocking-I/O progress while every worker remains stuck. `process.run(...)` follows the same rule through `process.Error.Io(io.Error.InvalidInput)`. Omitting its timeout uses an internal absence marker; explicit negative Duration values never act as that marker. The socket runtime also threads task-group cancellation into maintained socket waits. If a task group is cancelled while a child is waiting on a maintained network operation, that operation returns `io.Error.Cancelled` promptly. ## Current Model This surface uses one explicit scheduler-backed I/O model: - queue waits, `sleep(...)`, socket waits, and the maintained HTTP helpers all run through the pinned-worker runtime scheduler - socket-backed networking and HTTP convenience helpers use nonblocking descriptors with timeout and cancellation support - hostname resolution, listener binding, UDP destination resolution, and blocking TCP/Unix connect syscalls offload through the configurable generic blocking-I/O pool - process waits and captured child stdio pipes use the same scheduler-backed wait path - Aura tasks are scheduler-backed lightweight coroutines; each task does not require its own OS thread - ordinary file operations offload through the pinned-worker scheduler-backed runtime, keeping the lightweight task free from a blocking host thread Current process notes: - subprocess APIs are shell-free and take explicit argv vectors only - grouped children are supported through `group=true` on `process.start(...)` and `process.run(...)` - there is not yet a PTY surface - there are not yet pipeline helpers This keeps lightweight tasks schedulable while host operations wait. ## Source: tutorials/20-randomness.md # Deterministic And Secure Randomness Aura makes you choose which promise you need. A seeded `random.Rng` gives a repeatable sequence for tests, simulations, generated fixtures, and retry jitter. The module-level `random.secure_int` and `random.secure_bytes` functions ask the operating system for unpredictable values. ## A Repeatable Stream Import the module and keep the generator in a mutable binding: ```aura check-pass import random mut rng = random.Rng(42) print(rng.next_int(0, 10)) print(rng.next_int(-5, 6)) ``` This prints `2` and `2`. Reconstructing a generator with seed `42` starts the same stream again. A different pattern of calls consumes the stream differently, so reproducibility depends on both the seed and call order. `next_int(lo, hi)` uses a half-open interval: `lo` can be returned and `hi` cannot. The bounds are `int64`, may be negative, and must satisfy `lo < hi`. Aura uses rejection sampling internally, giving every integer in the interval equal probability and avoiding remainder bias. `next_float()` returns a `float64` in `[0.0, 1.0)`. It can return zero and can never return one. ## Shuffling In Place `shuffle` mutably borrows a list and rearranges its existing elements: ```aura check-pass import random mut rng = random.Rng(42) mut values: list[int64] = [0, 1, 2, 3, 4, 5] rng.shuffle(values) print(values) ``` The result is `[3, 5, 4, 1, 2, 0]`. The list stays owned by the caller. The method neither clones nor moves its elements, so it works with move-only element types too. Empty and one-element vectors are unchanged and do not advance the stream. ## The Generator Is A Move Value An `Rng` contains evolving state. It is deliberately non-copy and has no public clone route: ```aura check-pass import random def take_rng(rng: own random.Rng): pass mut rng = random.Rng(7) take_rng(rng) # rng.next_float() would be rejected because rng moved. ``` The three generator methods need a mutable receiver. If a helper should advance the caller's stream without taking ownership, give it a mutable borrow: ```aura check-pass import random def roll(rng: mut random.Rng) -> int64: return rng.next_int(1, 7) ``` This makes state flow visible at the same call boundary as any other mutation. Wrapping the generator does not make it cloneable. Aura rejects collection copies and cloned collection reads that would duplicate an `Rng`, even when it is nested inside a class or enum. Moving or removing a generator from a collection within one owning task remains valid. An `Rng` is not `Transfer`, so it cannot be a task result or Queue payload: those boundaries fail with `AU3008`. Queue handles remain copy values for admitted payload types, while a Task handle is copyable only when its result is repeatable. Generic code is not rejected merely because its element type is unresolved. If a body copies `list[T]` or performs another clone-producing operation, Aura infers that `T` must be clone-safe. The requirement propagates through other generic calls and imports, then a concrete `random.Rng` specialization fails with `AU3007`. A trait default body can establish the same contract; an explicit implementation cannot add a stronger hidden requirement. Operator traits and `From` conversions enforce the selected method's contract too. ## Use OS Randomness For Secrets The deterministic generator is predictable and must not create secrets. Use the secure functions for tokens, nonces, salts, keys, and session identifiers: ```aura check-pass import random die_roll = random.secure_int(1, 7) token_bytes = random.secure_bytes(32) print(token_bytes.len()) ``` Secure calls have no seed and no reproducible sequence. They use only the operating system's cryptographically secure source; Aura never falls back to `random.Rng`, a clock, or a process identifier. `secure_bytes(0)` returns an empty list without requesting entropy. The `secure_bytes` count is `int64`. Each call accepts at most `2147483647` bytes as a fixed resource and safety ceiling independent of the public `list` length domain. A larger count traps with `AU4005` before Aura requests either allocation or entropy. Invalid or unavailable requests trap because these functions return plain values: an empty/reversed integer interval or negative byte count is `AU4003`, while a secure byte count above the ceiling, OS entropy failure, or allocation failure is `AU4005`. Aura 0.3 has no `random.Error` type. ## Compatibility And The Full Contract Aura 0.3 fixes xoshiro256** plus its SplitMix64 seed expansion, integer mapping, floating mapping, and shuffle order for the complete 0.3.x series. That promise makes seeded tests portable across the MIR and direct backends. It does not make xoshiro secure. Run the maintained example: ```bash cargo run -p aura -- run examples/randomness/deterministic_rng.au ``` For the constants, state transition, seed-42 conformance vectors, ownership rules, and secure failure boundary, read the normative [Randomness Module](../docs/manual/randomness.md) chapter. ## Source: tutorials/21-json.md # Working With JSON Values Aura's JSON surface gives untrusted JSON its own recursive value and typed parse-error enums. It keeps parsing failures recoverable while making serialization deterministic enough for service messages, fixtures, and cache keys. The observable gap-fill policy is Accepted under ADR-0021, and the API and examples described here are implemented. ## Parse Into A Typed Tree `json.parse` returns `Result[json.Value, json.Error]`: ```aura check-pass import json result = json.parse("{\"name\":\"aura\",\"workers\":3}") match result: case Result.Ok(value): print(json.dumps(value)) case Result.Err(error): print(error) ``` The successful value is not an untyped host object. It is one of seven enum variants: `Null`, `Bool`, `Int`, `Float`, `String`, `Array`, or `Object`. Ordinary exhaustive `match` can distinguish them. Parse errors are values too. `Syntax` contains a message and location; `NumberOutOfRange` identifies a number Aura cannot preserve as `int64` or a finite `float64`; `NestingTooDeep` and `InputTooLarge` report their limits. Lines and columns start at one, and a column counts Unicode scalar values. UTF-8 byte offsets are not used for this field. ```aura fragment match json.parse("{\"ready\":"): case Result.Ok(value): print(value) case Result.Err(json.Error.Syntax(message, line, column)): print(f"{line}:{column} {message}") case Result.Err(error): print(error) ``` ## Numbers Keep Their JSON Meaning Parsing classifies the exact source number before binary64 rounding. Any mathematical integer in the `int64` range becomes `Value.Int`, even when its source uses a decimal point or exponent: - `1`, `1.0`, and `1e0` become `Int(1)` - `1.5e1` becomes `Int(15)` - `-0.0` becomes `Int(0)` - `1.5` becomes `Float(1.5)` - `1e400` returns `NumberOutOfRange` This keeps a rounded float from masquerading as an exact integer. It also means source spelling alone does not select the variant. The scalar accessors are intentionally exact: ```aura check-pass import json integer = json.Value.Int(7) match json.as_int(integer): case Option.Some(value): print(value) case Option.None: print("not an integer") print(json.as_float(integer) == Option.None) ``` `as_float` does not convert an Int. Perform any numeric conversion explicitly after extracting the payload. ## Borrow To Inspect, Consume To Extract `json.is_null`, `json.as_bool`, `json.as_int`, and `json.as_float` use the ordinary bare parameter default: shared access, so the JSON value remains available. Owned `String`, `Array`, and Object payloads use the consuming module functions `json.into_string`, `json.into_array`, and `json.into_object`: ```aura check-pass import json def main(): value = json.Value.Array([json.Value.Int(2), json.Value.Int(3)]) match json.into_array(value): case Option.Some(items): print(items.len()) case Option.None: print("not an array") ``` An `into_*` call consumes its argument whether or not the variant matches. That makes ownership transfer explicit and avoids a hidden deep clone of a nested tree. ## Build And Dump Deterministically Construct Values with ordinary qualified enum constructors. One Object can contain different JSON kinds because every dictionary value has the same `json.Value` type: ```aura check-pass import json payload = json.Value.Object({"workers": json.Value.Int(3), "ready": json.Value.Bool(true), "tags": json.Value.Array([json.Value.String("compiler"), json.Value.String("service")])}) print(json.dumps(payload)) print(json.dumps(payload, indent=Option.Some(2))) ``` Compact output sorts object keys, so the first line is: ```text {"ready":true,"tags":["compiler","service"],"workers":3} ``` Pretty output uses LF line endings, two spaces for each nesting level, one space after each colon, and no final newline. Empty arrays and objects remain `[]` and `{}`. Sorting is a dump rule, not a mutation. The Object's underlying dict keeps its insertion order. Parsing duplicate object keys keeps the key's first insertion slot but replaces it with the last value. ## Parse Errors And Dump Traps Are Different Malformed input is normal at a service boundary, so parse returns `json.Error`. Match it and decide whether to reject, log, or retry. `json.dumps` has the roadmap-mandated return type `str`, not `Result`. Failures therefore trap: - invalid indent or depth greater than 128 uses `AU4003` - NaN or infinity in a manually constructed Float uses `AU4001` - output-cap or allocation failure uses `AU4005` Indent must be `None` or `Some(0)` through `Some(16)`. Both parse input and dump output have independent 67,108,864-byte caps. The exact boundary is accepted. Depth counts containers only: a root scalar is depth zero, a root Object or Array is depth one, and depth 128 is accepted. Parse and dump also share a 262,144-value structural budget. Every scalar, array, object, and object member value counts once; object keys do not count. The exact boundary is accepted. Exceeding this budget, like exceeding an output cap or encountering a controlled allocation failure, reports `AU4005`. ## Strict JSON, Not A Schema System The parser accepts one strict JSON value plus surrounding JSON whitespace. It does not accept comments, trailing commas, leading-zero integers, `NaN`, or infinities. `json.Value` is useful when the shape is genuinely dynamic or checked by application code. Derived class/enum schemas and generated codecs remain deferred beyond Phase 6. Aura also has no streaming JSON API or arbitrary-precision number type. `json.is_valid`, `json.stringify_map`, and `json.parse_string_map` provide typed operations for flat `dict[str, str]` data. They are distinct from the dynamic `json.Value` API. ## Full Contract The normative [JSON Module](../docs/manual/json.md) chapter fixes the complete variant shapes, numeric rules, error coordinates, ordering, escaping, formatting, ownership, diagnostics, and limits. ADR-0021 records those observable policies and their rationale. ## Source: tutorials/22-bytes.md # Bytes, Encodings, And Hashes Aura uses `list[uint8]` whenever an API needs raw bytes. That is the same type returned by file, socket, process, and secure-random byte APIs, so data can move between those boundaries without a wrapper conversion. There is deliberately no implicit conversion between `str` and bytes. Text has a character encoding; bytes do not. Aura makes the UTF-8 boundary visible. ## Convert UTF-8 Explicitly Call `to_bytes()` on a str: ```aura check-pass import bytes text = "Aura 🌌" payload = text.to_bytes() print(bytes.hex_encode(payload)) ``` This prints `4175726120f09f8c8c`. The returned list contains the exact UTF-8 bytes. Embedded NULs, non-ASCII text, and a leading U+FEFF are preserved; Aura does not normalize the text or rewrite line endings. Going the other way can fail because an arbitrary byte list need not be valid UTF-8: ```aura check-pass import bytes payload: list[uint8] = [65, 117, 114, 97] match str.from_bytes(payload): case Result.Ok(text): print(text) case Result.Err(bytes.Error.InvalidUtf8(index)): print(f"invalid UTF-8 at byte {index}") case Result.Err(error): print(error) ``` `str.from_bytes` validates strictly. It never replaces bad bytes with a replacement character. `InvalidUtf8(index)` points to the zero-based byte offset where the first invalid sequence begins. The conversion functions share their inputs. `payload` remains available after `from_bytes`, and the original str remains available after `to_bytes`. ## Hexadecimal Is A Text Representation Hex encoding uses two lowercase digits per byte: ```aura check-pass import bytes payload: list[uint8] = [0, 1, 254, 255] text = bytes.hex_encode(payload) print(text) ``` The result is `0001feff`. Decoding accepts uppercase or lowercase ASCII: ```aura fragment match bytes.hex_decode("0001FeFf"): case Result.Ok(payload): print(payload) case Result.Err(bytes.Error.InvalidHexLength(length)): print(f"odd byte length: {length}") case Result.Err(bytes.Error.InvalidHexDigit(index, byte)): print(f"invalid byte {byte} at {index}") case Result.Err(error): print(error) ``` The decoder is strict. It does not accept a `0x` prefix, spaces, separators, signs, or non-ASCII digits. Odd length is checked before digit validity. ## Base64 Uses The Canonical Standard Alphabet Base64 is useful when a text protocol needs to carry arbitrary bytes: ```aura check-pass import bytes payload: list[uint8] = [0, 1, 254, 255] encoded = bytes.base64_encode(payload) print(encoded) match bytes.base64_decode(encoded): case Result.Ok(decoded): print(decoded) case Result.Err(bytes.Error.InvalidBase64(index)): print(f"invalid base64 at byte {index}") case Result.Err(error): print(error) ``` This prints `AAH+/w==` and then `[0, 1, 254, 255]`. Aura uses the RFC 4648 standard alphabet with canonical `=` padding. The decoder rejects URL-safe `-`/`_`, whitespace, missing or extra padding, trailing data, and nonzero discarded bits. It does not quietly repair input. Decoded bytes are not assumed to be UTF-8; call `str.from_bytes` separately when text is required. ## Hash Exact Bytes `bytes.sha256` returns a raw 32-byte SHA-256 digest: ```aura check-pass import bytes payload = "abc".to_bytes() digest = bytes.sha256(payload) print(digest.len()) print(bytes.hex_encode(digest)) ``` The output length is `32`, and the hex line is: `ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad`. For a str, `bytes.sha256_string(text)` hashes exactly the bytes produced by `text.to_bytes()`. It does not add a terminator or normalize text. These two expressions therefore produce equal digest vectors: ```aura fragment bytes.sha256_string("café") bytes.sha256("café".to_bytes()) ``` SHA-256 is a digest, not encryption, a password hash, a signature, a message-authentication code, or random data. Use a protocol-specific cryptographic construction when one of those properties is required. ## Typed Data Errors And Runtime Failures Malformed UTF-8, hex, and base64 are expected data problems, so they return a `bytes.Error` inside `Result` when the exact offset or length fits the retained `int32` payload. Match the variant and report, reject, or retry as the application requires. If required malformed-data metadata exceeds `2147483647`, Aura traps with `AU4005`. It never truncates or wraps the value. Each fresh codec destination has a fixed 2,147,483,647-byte safety ceiling independent of the public str and `list` length domains. Crossing that ceiling, arithmetic overflow while calculating the destination size, or allocation failure traps with `AU4005`. A codec never returns a partial successful value. The optional `encoding` parameter is reserved but not implemented. These are the complete 0.3 conversion calls: - `text.to_bytes()` - `str.from_bytes(payload)` Do not pass `"utf-8"` positionally or as `encoding=...`; ordinary argument checking rejects it. ## Run The Maintained Example From the repository root: ```bash cargo run -p aura -- run examples/bytes/codecs_and_hashing.au ``` For exact signatures, malformed-input precedence, error offsets, size preflights, backend parity, and non-features, read the normative [Bytes, Text Codecs, And SHA-256](../docs/manual/bytes.md) chapter. ## Source: tutorials/23-assertions-and-tests.md # Assertions And Tests Assertions turn a condition that must hold into an immediate, source-located runtime failure. Use the short form when the default message is enough: ```aura fragment assert user_count >= 0 ``` Use a second expression when the failure needs application context: ```aura fragment assert response_code == 200, "worker expected a successful response" ``` The condition must have type `bool`. Aura does not apply Python-style truthiness. The optional message must have type `str`. ## Evaluation Is Deliberately Lazy Aura evaluates the condition exactly once. When it is `true`, execution continues and the message expression is not evaluated. When it is `false`, the message is evaluated exactly once and becomes the failure text. This makes it safe to construct an expensive diagnostic only for the failure path: ```aura check-pass def explain(value: int64) -> str: print("building failure message") return f"unexpected value {value}" value = 4 assert value == 4, explain(value) ``` This program does not print `building failure message`. If evaluating the condition or message fails first, Aura reports that earlier failure and never reaches the assertion result. ## Failed Comparisons Show Their Values For a top-level comparison or positive membership test, Aura reports the two values that produced the failure: ```aura check-pass expected = 42 actual = 41 assert actual == expected ``` The diagnostic includes: ```text left = 41 right = 42 ``` `assert item in collection` uses `item` and `collection` labels. Operands still evaluate exactly once from left to right. A custom message remains lazy and is evaluated after the failed operands have been captured. Each displayed value is limited to 4,096 UTF-8 bytes and receives a visible truncation suffix when needed. This focused view applies to `==`, `!=`, `<`, `<=`, `>`, `>=`, and positive `in` when the operation reads both operands without consuming them. Comparison chains, `not in`, Boolean combinations, and calls returning `bool` retain the ordinary assertion failure message. ## Failure Behavior `assert false` fails with diagnostic code `AU4001` and the exact message `assertion failed`. A custom message is preserved exactly, including an empty or whitespace-only str. The diagnostic points to the `assert` keyword. Assertions are never removed by an optimization or release mode. Aura has no assertion-stripping option, so do not use an assertion as a substitute for recoverable validation of untrusted input. Return a typed `Result` for a failure the caller should handle. Active `with` cleanups still run when an assertion fails. If cleanup also fails, the assertion remains the primary diagnostic. ## Assertions In Test Files `aura test` discovers parameterless module functions named `test_*`. A function returning `None` is one independently reported case: ```aura check-pass def test_account_total(): charges = [20, 21] assert charges[0] + charges[1] == 41 ``` The canonical name is `path::test_account_total`. Functions are discovered in source order. A file with no `test_*` function remains one file-level case and runs through `main()` or top-level statements. Use `-k` to select a literal, case-sensitive substring of the complete case name: ```bash aura test -k account tests/check_account.au ``` A valid filter with no matches succeeds with a zero-case summary. ### Per-Case Lifecycle Optional `setup()` and `teardown()` functions run around every selected case: ```aura check-pass def setup(): print("setup") def teardown(): print("teardown") def test_total(): print("case") assert 20 + 21 == 41 ``` The observable order is `setup`, `case`, `teardown`. Teardown still runs when setup or the case traps. The earlier failure remains primary, and a teardown failure is reported secondarily. Each phase enters the same checked module in isolation, so Aura values and module state do not flow between phases. External effects such as file writes can be used when a test needs observable lifecycle state. ### Parameterized Cases A registration function returns labeled, named test functions: ```aura check-pass def empty_case(): assert "".len() == 0 def unicode_case(): assert "A🎉".len() == 2 def test_lengths() -> list[(str, def() -> None)]: return [("empty", empty_case), ("unicode", unicode_case)] ``` The two case names end in `test_lengths[empty]` and `test_lengths[unicode]`. Registration happens once before `-k` filtering. Labels must be non-empty and unique. Returned functions are capture-free, parameterless, repeatable, and return `None`; captured closures are rejected. Setup and teardown run for each selected expanded case, not for registration. ### JSON Results `aura test --format json` emits one schema-version-1 document. Its summary contains selected, passed, and failed counts. Each ordered test record contains the canonical name, file, outcome, and lifecycle duration in milliseconds. Captured stdout is included when non-empty. A trapped case carries the normal structured diagnostic, including assertion operands; a runner failure carries a reason. A teardown failure accompanying an earlier failure appears as a secondary teardown record. Human and JSON runs exit 0 when all selected cases pass and 1 when any case or discovery step fails. Usage errors exit 2. The maintained example works both as an ordinary program and as a test module: ```bash cargo run -p aura -- run examples/basics/assertions.au cargo run -p aura -- test examples/basics/assertions.au cargo run -p aura -- test --format json -k '[unicode]' examples/basics/assertions.au ``` ## Source: tutorials/24-multiline-expressions.md # Multiline Expressions Aura can keep one logical expression readable across several physical lines. The rule is simple: the line continues while `(`, `[`, or `{` remains open. ## Calls And Signatures ```aura check-pass def combine( left: int64, right: int64 ) -> int64: return left + right answer = combine( 20, 22 ) ``` The closing `)` returns to the surrounding block indentation. The newline after that line ends the logical statement. ## Collections And Grouping ```aura check-pass values = [ 10, 20 ] labels = { "first": values[0], "second": values[1] } total = ( values[0] + values[1] ) ``` Continuation indentation is for readers. It does not create an Aura suite, and the compiler would accept different numbers of leading spaces. Use one extra four-space level so the structure remains obvious. ## Comments And Blank Lines A trailing comment can end one continued physical line: ```aura check-pass values = [ 10, # first input 20 ] ``` The blank line and comment do not close `[`, so the list continues. ## Match Expressions Keep Their Layout `match` still needs an indented `case` block even when it appears inside a call: ```aura fragment print( match status: case Ready: "ready" case _: "waiting" ) ``` The `case` block is a layout island inside the continued call. Every arm keeps the normal match layout. The containing delimiter can close after the last inline arm or on its own following line. ## Delimiters Must Pair Delimiters may nest and mix, but the most recently opened delimiter must close first with the matching kind. A mismatched or unclosed delimiter is a lexical `AU1001` diagnostic. The diagnostic points at the wrong closer or end of file and relates it back to the opening delimiter. Token locations still use their physical line and column. Joining the lines does not change types, ownership, borrow duration, or evaluation order. ## What Does Not Continue Aura does not use a trailing backslash: ```text value = left + \ # invalid Aura right ``` A comma or operator at the end of a line is not enough by itself. Keep a delimiter open. A multi-element list still rejects a trailing comma, so write: ```aura check-pass values = [ 10, 20 ] ``` not a comma after `20`. Ordinary strings and f-strings are still single-line. Break a larger calculation across delimiters outside the string; do not put a physical newline inside `f"..."`. Run the maintained example: ```bash cargo run -p aura -- run examples/basics/multiline_expressions.au ``` It prints `80` and `20`. ## Next Delimiter continuation changes source layout only. Values have the same types, ownership, evaluation order, and backend behavior as the equivalent one-line program. Continue with [Tuples](25-tuples.md). ## Source: tutorials/25-tuples.md # Tuples Tuples bundle a fixed number of values that may have different types. They are useful when a function has two or three natural results but defining a class would add more ceremony than meaning. ## Values And Types A comma inside parentheses makes a tuple: ```aura check-pass pair = ("Aura", 7) only = (true,) ``` `(value)` still means grouping. The comma in `(value,)` is therefore required for a singleton. Aura has no empty tuple, and a tuple with two or more elements does not take a trailing comma. Tuple types mirror tuple values: ```aura check-pass def version() -> (str, int64): return ("Aura", 7) ``` The order and number of element types matter. `(str, int64)` and `(int64, str)` are different types. ## Unpacking A Return Value Use a comma-separated target to give each result a name: ```aura fragment name, number = version() print(name) print(number) ``` Tuple value expressions require parentheses, but the top-level assignment target does not: write `name, number = pair`, not a naked tuple expression. Nested targets use parentheses: ```aura check-pass label, (x, y) = ("point", (3, 4)) ``` The right side is evaluated once, and its complete recursive shape must match the target. ## Copy And Move Behavior A tuple is a copy value only when every element is a copy value: ```aura check-pass point = (3, 4) x, y = point print(point[0]) # point is still usable ``` A tuple containing `str`, `list`, or another move value is itself a move value. Unpacking it moves the whole source and gives owned leaf bindings: ```aura check-pass def main(): record = ("Aura", 7) name, number = record print(name) print(number) # print(record) would be a use-after-move error ``` Aura deliberately reports reuse of the original tuple. Positional partial moves are not exposed. ## Structural Equality `==` and `!=` compare tuples recursively, element by element. Both operands must have exactly the same tuple type, and every element must support equality: ```aura check-pass baseline = ("Aura", (7, true)) same = ("Aura", (7, true)) changed = ("Aura", (8, true)) assert baseline == same assert baseline != changed assert same != changed ``` Tuple equality reads and retains both operands. This also applies to non-copy tuples such as these, which contain `str`: each binding remains usable in a later comparison. Tuple ordering is deliberately separate. `<`, `<=`, `>`, and `>=` are rejected for tuple operands; compare the intended elements explicitly instead. ## Constant Indexes Indexing is available for the small read-only case: ```aura check-pass point = (3, 4) print(point[1]) ``` The index must be a non-negative integer literal, must be in bounds, and must select a copy element. A variable index or a non-copy element is rejected. Unpack the tuple when you need ownership of a non-copy element. ## Unpacking In Loops A `for` target may recursively unpack tuple items: ```aura check-pass for label, count in [("ready", 2), ("done", 3)]: print(f"{label}:{count}") ``` Bare collection iteration keeps the collection and gives non-copy tuple leaves shared access. `own` collection iteration gives owned leaves. Bare Queue iteration receives each tuple already owned. `mut` iteration with a tuple target is not supported because the minimal tuple surface does not reconstruct and write a changed tuple back into the collection. ## Tuple Patterns Tuple patterns use the same fixed shape: ```aura check-pass match ((1, 2), true): case ((left, right), flag): print(left + right) print(flag) ``` `match own` consumes a non-copy tuple as one whole value. Bare `match` keeps it and gives shared access to non-copy leaves. `match mut` with a tuple pattern is not supported. ## What Tuples Are Not Tuples are not small vectors. Beyond structural `==` and `!=`, the current surface has no tuple ordering, iteration, methods, named elements, rest/star unpacking, slicing, dynamic indexing, or implicit conversion to `list`. Run the maintained example: ```bash cargo run -p aura -- run examples/basics/tuples.au ``` It prints: ```text Aura 7 20 ready:2 done:3 3 true ``` For the complete contract, including diagnostics and backend parity, see the normative [Tuples Manual page](../docs/manual/tuples.md). ## Source: tutorials/26-ffi.md # 26. Foreign Function Interface v0 Aura FFI v0 binds small, trusted C APIs without opening the language to general pointer manipulation. FFI declarations are package-only and require an explicit manifest opt-in. The maintained example is `examples/packages/ffi_getpid`: ```toml [package] name = "ffi_getpid" version = "0.1.0" edition = "2026" allow_ffi = true ``` ```aura fragment public extern "C" def getpid() -> int32 def main() -> int32: print(getpid() > 0) return 0 ``` On Unix-family hosts, run it through either maintained backend: ```bash aura run --backend mir examples/packages/ffi_getpid/src/main.au aura run --backend direct examples/packages/ffi_getpid/src/main.au ``` The two runs print `true`. ## Signatures FFI functions are bodyless `extern "C" def` declarations. Fixed-width scalar parameters are bare and pass by value. The accepted widths are signed and unsigned 8/16/32/64-bit integers plus `bool`, `float32`, and `float64`. `int` is the exact `int64` alias, although `int64` communicates the ABI width more directly. Results may use one of those scalars, `None`, or a declared opaque handle. A bare `str` lowers to a temporary const UTF-8 pointer and byte length; it is not NUL-terminated. `list[uint8]` is the matching read-only byte view, while `mut list[uint8]` uses a same-length scratch buffer for copy-in/out without changing the list length. Empty views pass a null pointer and length zero. The native callee must not retain any view pointer after the synchronous call. ```aura fragment public extern "C" def checksum(data: list[uint8]) -> uint64 public extern "C" def normalize(data: mut list[uint8]) -> None ``` ## Opaque Handles Use a declaration-only opaque class for a non-null foreign pointer: ```aura fragment public extern "C" opaque class Handle public extern "C" def acquire() -> Handle public extern "C" def inspect(handle: Handle) -> int32 public extern "C" def close(handle: own Handle) -> None ``` Aura cannot construct, inspect the layout or address of, clone, or transfer an opaque handle. A bare parameter retains it; `own` consumes it. Rendering shows only ``. FFI v0 does not automatically invoke a destructor, so a binding must call the correct consuming native function. ## Package Dependency Reports When an application depends on an FFI-enabled package, the root package must also opt in and name every reachable FFI-enabled dependency, including a transitive one: ```toml [package] name = "app" version = "0.1.0" edition = "2026" allow_ffi = true [dependencies] native_binding = { path = "../native_binding" } [ffi] dependencies = ["native_binding"] ``` The list is exact and auditable. Unknown, duplicate, non-FFI, or missing entries are errors. ## Safety Boundary Aura checks that the declaration uses the supported surface. It cannot verify the real native signature or behavior. A missing process-global symbol, or null handle becomes an `AU4005` runtime failure. A non-canonical C boolean result (a byte other than `0` or `1`) traps with `AU4001`. A native abort, signal, memory fault, unwind, out-of-bounds write, or retained temporary pointer can still terminate or corrupt the process. There are no callbacks, variadics, raw pointer arithmetic, returned views, nullable handles, or explicit library-loading declarations in FFI v0. The normative contract is [Foreign Function Interface (FFI) v0](../docs/manual/ffi.md). ## Source: tutorials/README.md # Aura Tutorials This directory is the beginning of the Aura tutorial track: a book-style set of Markdown chapters that explains the language as it exists in the repository today. These tutorials are intentionally scoped to the implemented Aura surface. They stay in sync with the compiler, examples, and normative Manual. ## Maintenance Rule When the implemented language surface changes, update these in the same pass: 1. the relevant tutorial chapter 2. the relevant example program under `examples/` 3. any CLI or tooling docs that reference the changed behavior 4. `14-current-language-surface.md` if the supported surface changed ## Reading Order 1. [00-overview.md](00-overview.md) 2. [01-running-programs.md](01-running-programs.md) 3. [02-bindings-and-types.md](02-bindings-and-types.md) 4. [03-functions.md](03-functions.md) 5. [04-control-flow.md](04-control-flow.md) 6. [05-classes-and-data.md](05-classes-and-data.md) 7. [06-ownership-and-borrowing.md](06-ownership-and-borrowing.md) 8. [07-strings-and-numbers.md](07-strings-and-numbers.md) 9. [08-tooling.md](08-tooling.md) 10. [09-enums-and-match.md](09-enums-and-match.md) 11. [10-results-and-options.md](10-results-and-options.md) 12. [11-resource-management.md](11-resource-management.md) 13. [12-error-propagation.md](12-error-propagation.md) 14. [13-concurrency.md](13-concurrency.md) 15. [14-current-language-surface.md](14-current-language-surface.md) 16. [15-generics.md](15-generics.md) 17. [16-traits.md](16-traits.md) 18. [17-modules-and-visibility.md](17-modules-and-visibility.md) 19. [18-packages-and-workspaces.md](18-packages-and-workspaces.md) 20. [19-io-and-networking.md](19-io-and-networking.md) 21. [20-randomness.md](20-randomness.md) 22. [21-json.md](21-json.md) 23. [22-bytes.md](22-bytes.md) 24. [23-assertions-and-tests.md](23-assertions-and-tests.md) 25. [24-multiline-expressions.md](24-multiline-expressions.md) 26. [25-tuples.md](25-tuples.md) 27. [26-ffi.md](26-ffi.md) ## Scope Today The current tutorial set covers: - scripts and `main` - bindings, mutability, and type annotations - functions with explicit and omitted `None` return types - capture-free named function values with `def(T1, mut T2, own T3) -> R` types, copy and `Transfer` semantics, indirect calls, storage, and task targets - contextually typed expression lambdas with by-value Copy/non-Copy capture, repeatable reads, consuming single-use calls, and structural Transfer - classes with fields, default values, receiver forms, mutating methods, and `public` field syntax - ownership, declaration-stable parameter defaults, explicit `own`, move semantics, copy types, and the exclusivity rule for mutable borrows - owned `list[T]`, `dict[K, V]`, and `set[T]` collections with literals, storing APIs, bare-shared/`own` iteration, mutable list iteration, stable sorting, eager callback-powered `map`/`filter`, and eager owned list/set/dictionary comprehensions with filters and nested clauses, plus owned list/str slices with omitted endpoints, negative normalization, loud bounds, and Unicode-scalar str positions - owned contiguous numeric `Array[T]` values for the four maintained dtypes, with exact shapes, row-major multidimensional indexing, first-axis copy slices, same-dtype arithmetic, explicit wrapping/saturating integer modes, mapping, and deterministic reductions - enums with exhaustive `match` - user-defined generic classes, enums, and functions - trait declarations, trait impls, and bounded generic calls - local file modules with `import`, `from ... import ...`, and `public` visibility at module boundaries - `Aura.toml` packages with `src/`, local path dependencies, git dependencies, workspaces, and local lockfiles - package-authorized FFI v0 with bodyless `extern "C"` declarations, fixed-width scalars, pointer-length str/byte views, opaque handles, and exact root dependency reports - built-in `Result[T, E]`, `Option[T]`, `SendError[T]`, and bare `None` - `try expr` - conditional expressions such as `value if condition else alternative`, with exact-`bool` conditions and lazy selection of one arm - `in` and `not in` over `list`, `set`, `dict` keys, and `str` substrings - Python-style chained comparisons such as `low <= value < high`, which evaluate each operand once and short-circuit - the `for ... in enumerate(seq):` and `for ... in zip(first, second):` loop forms, where `zip` stops at the shorter sequence - the builtin functions `len(value)`, delegating to the value's own `len()`, and `str(value)`, producing the print rendering; `str.len()`, `str.byte_len()`, `list.len()`, `dict.len()`, and `set.len()` all return `int64`, matching range bounds, list indexes, slice endpoints, enumeration positions, and Array coordinates - `with` using `close(mut self)` and `with TaskGroup() as group:` - builtin `io`, `fs`, `net`, and `process` modules with scheduler-aware file I/O, maintained networking resource types, and shell-free subprocess helpers - `Queue[T]()`, `Task[T].result()`, `TaskGroup()`, its ordinary and explicit-stack start methods, typed `select(...)` over Queue, Task, and relative-Duration sources, `wait_any(...)`, `wait_all(...)`, send-result errors, structural `Transfer` boundaries, single-consumer task results, and cooperative cancellation - arithmetic including decimal/hexadecimal/binary/octal integer literals, fixed-width bitwise and shift operations, checked power, ties-to-even `round`, paired `divmod`, explicit floor division, integer-to-float conversion, and computed signed Duration values; strings, string parsing/formatting, booleans, and comparisons - deterministic seeded randomness, unbiased ranges, mutable-list shuffle, and the separate OS-secure integer/byte boundary - `control.retry` for eager `Result` workers with an attempt budget and exponential `Duration` backoff - recursive `json.Value` trees, typed parse errors, exact accessors, consuming payload extraction, and deterministic compact or pretty dumping - `list[uint8]` bytes, strict UTF-8 conversion, canonical hex/base64 codecs, typed malformed-input errors, and raw SHA-256 - `assert condition` and `assert condition, message`, with lazy messages, source-located `AU4001` failures, and file-level `aura test` behavior - delimiter-based newline continuation inside `()`, `[]`, and `{}`, including multiline signatures, calls, grouping, indexing, and collection literals; ordinary trailing commas, backslash continuation, and multiline f-strings remain unavailable (singleton tuples require their one comma) - fixed structural tuples with parenthesized value/type syntax, recursive assignment/loop unpacking and patterns, whole-source move behavior, and copy-only constant indexing; same-type recursive `==` and `!=` retain both operands, while tuple ordering remains unavailable - `if`, `elif`, `else`, `for`, `while`, `match`, `break`, and `continue` - `print` - CLI inspection commands such as `check`, `ast`, `ast-json`, `analyze`, `complete`, and `mir` - compiler-backed VS Code diagnostics, navigation, and completions Use the normative [Language Specification](../docs/manual/language-specification.md) and [Manual](../docs/manual/index.md) as the exhaustive truth source. `14-current-language-surface.md` is a compact tutorial recap; the earlier chapters should explain the maintained surface progressively. It does not yet attempt to teach features that are still only in the proposal.