# Rove v4 Language Reference This document describes the `v4.0.0-rc.2` (`Bodhi`) source-language contract. Rove source files use the `.rove` extension and are built with the `rove` CLI. Backend availability is a capability decision, not a change to language syntax. ## 1. Bindings and assignment ```rove var attempts: int = 0 // mutable binding let limit: int = 3 // immutable binding const APP_NAME = "Rove" // immutable binding set attempts = attempts + 1 ``` `let` and `const` prevent rebinding. They do not deep-freeze a struct, array, or other value reachable through the binding. `set target = value` is the explicit assignment form; `target = value` remains equivalent for source compatibility. Array and positional struct destructuring declarations bind several names while evaluating the initializer exactly once: ```rove let [left, right] = read_pair() struct Point { x: int, y: int } let Point(x, y) = Point(10, 20) let [first, _] = values // `_` discards one position ``` An array pattern requires an `Array`; insufficient input fails through a checked bounds path. A struct pattern must name a declared struct and provide one position for every field in declaration order. Nested and `..rest` patterns are not part of this first contract. ```rove struct Counter { value: int } let counter = Counter(0) set counter.value = 1 // valid: the binding still refers to the same Counter // set counter = Counter(2) // error: counter is an immutable binding ``` ## 2. Core types The portable scalar types are `int`, `float`, `bool`, `string`, `char`, `uintptr`, and `void`. Generic library/compiler types include `Array`, `Option`, `Result`, `Channel`, and `Task`. Fixed-width scalar spellings are `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, `f32`, and `f64`. They select concrete storage widths on native and Rust backends. A directly assigned integer literal must fit its declared width (`E2024`); implicit narrowing is not permitted. The hosted cross-backend arithmetic contract remains the canonical `int` and `float` contract below until width-specific overflow semantics are frozen for JavaScript and Python. `int` is a signed 64-bit two's-complement value. Addition, subtraction, multiplication, negation, bitwise operations, and left shift wrap modulo 2⁶⁴. Integer division truncates toward zero, remainder keeps the dividend's sign, and shift counts are reduced modulo 64. Division by zero raises an error. Integer literals must be between `-9223372036854775808` and `9223372036854775807`; an out-of-range literal is `E2012`. The minimum value is accepted in its directly negated decimal or hexadecimal spelling. `float` is IEEE-754 binary64. Division by zero produces the corresponding infinity or NaN; `%` uses a truncating remainder with the dividend's sign. Canonical text uses `nan`, `inf`, `-inf`, normalized exponents such as `1e-7`, and renders negative zero as `0`. Finite values use a shortest round-tripping decimal without a redundant `.0`. Nonzero magnitudes from `1e-6` (inclusive) to `1e21` (exclusive) use fixed notation; other magnitudes use scientific notation with an explicit `+` for positive exponents and no exponent padding. An `int` widens to `float` by IEEE-754 binary64 conversion when required by an operator, parameter, field, or return type. The conversion can round integers whose magnitude exceeds 2⁵³; it never changes the original `int` binding's type. `float` does not narrow to `int` implicitly. Canonical booleans are rendered as lowercase `true` and `false`. The full numeric and scalar-text contract is declared by `cpp`, `js`, and `python`. The beta `wasm` ABI remains explicitly `wasm32` and does not claim signed-i64 conformance until its numeric ABI is revised. ```rove let answer: int = 42 let ratio: float = 0.5 let title: string = "Rove" let maybe_name: string? = null let values: Array = [1, 2, 3] ``` Strings are Unicode values. Literals support common escapes, embedded `\0`, four-hex-digit Unicode escapes such as `\u0301`, and interpolation: ```rove let city = "İstanbul" print($"hello, {city} 🌙") ``` Rove does not normalize Unicode automatically; NFC and NFD spellings remain distinct byte sequences. ## 3. Functions ```rove fn add(a: int, b: int) -> int { return a + b } fn greet(name: string) { print("Hello, " + name) } fn square(value: int) -> int = value * value fn classify(value: int) -> string = if value < 0 { "negative" } elif value == 0 { "zero" } else { "positive" } ``` Parameters and return values are typed. A missing return annotation means `void` unless the compiler can safely infer a value type in an inference-enabled context. `= expression` is an expression-bodied function and behaves as one implicit `return`. A value-producing `if` requires an `else`; each arm contains one expression and every arm must have a compatible result type. A parameter may declare a default value with `= expression`. An omitted trailing argument is filled with that default value at the call site, so the default is re-evaluated on every call. Only trailing parameters may be omitted; a required parameter before an omitted one is an arity error. ```rove fn greet(name: string = "world", times: int = 1) { var count: int = 0 while count < times { print("hi", name) set count = count + 1 } } greet() // name = "world", times = 1 greet("rove") // name = "rove", times = 1 greet("rove", 3) // name = "rove", times = 3 ``` A default value whose type does not match its declared parameter type is a compile-time error, as is omitting a parameter that has no default. ## 4. Async tasks ```rove async fn compute() -> int { return 42 } async fn main() { let task: Task = compute() let first: int = await task let second: int = await task print(first + second) } ``` An `async fn ... -> T` call returns `Task`. `await` is valid only inside an async function and unwraps one `Task` to `T`. A task is reusable: awaiting the same task more than once observes the same completion. Errors thrown by the task propagate at `await` and can be caught normally. The exact instant at which a task begins execution is intentionally not part of the language contract. The `Task` ABI is currently implemented by `cpp`, `js`, and `python`. Other targets reject it with a capability diagnostic instead of silently changing its behavior. ## 5. Control flow ```rove if score >= 90 { print("A") } else if score >= 80 { print("B") } else { print("C") } for i in 1..10 { if i == 5 { continue } print(i) } var remaining = 3 while remaining > 0 { set remaining = remaining - 1 } loop { break } ``` `a..b` is an inclusive range. `elif` and `else if` are equivalent spellings. `break` and `continue` are valid only inside loops. Conditions accept only `bool`; a dynamically typed `any` value is checked at runtime rather than using C++/JavaScript/Python truthiness. `match` can also produce a value without a `return` in every arm: ```rove fn http_label(code: int) -> string = match code { 200 => "ok", 404 => "missing", _ => "other" } ``` The final `_` fallback is mandatory and arm values must share a compatible type. Maya's first value-match form accepts literals. The subject is evaluated exactly once, including calls and other side-effecting expressions: ```rove let label = match read_status() { 200 => "ok", _ => "other" } ``` `guard` expresses an early-exit precondition, while `defer` runs an expression when the current scope exits: ```rove fn save(value: string?) { guard value != null else { return } defer print("save finished") print(value) } ``` ## 6. Structs, traits, and implementations ```rove struct Point { x: int, y: int } trait Show { fn show(self) -> string } impl Show for Point { fn show(self) -> string { return $"({self.x}, {self.y})" } } let point = Point(10, 20) print(point.show()) ``` An inherent implementation omits the trait name: `impl Point { ... }`. Instance methods declare `self` explicitly as their first parameter; an inherent implementation may also contain an associated function without `self`. Trait methods are signatures, not default implementations, and therefore have no body. An `impl Trait for Type` must provide every required method with the same async marker, parameter types, and return type. The target must be a declared struct. ## 7. Errors and cleanup ```rove fn parse_port(value: int) -> int { if value < 1 { throw "port must be positive" } return value } try { print(parse_port(0)) } catch error { print("caught:", error) } ``` `throw` converts its value to the canonical Rove string representation for the current exception boundary. `try`/`catch`/`throw` are available on `cpp`, `js`, and `python`; unsupported targets fail during capability validation. For recoverable domain errors that are part of an API, prefer `Result` and pattern matching: ```rove let result: Result = Ok(42) match result { Ok(value) => print(value), Err(error) => print(error), _ => print("unreachable") } ``` Statement `match` keeps `"_"` as a legacy wildcard for source compatibility; use `_` in new code. Comparison patterns must match the subject type, except that an `int` pattern may widen to a `float` subject. ## 8. Pipelines and null safety ```rove fn doubled(value: int) -> int { return value * 2 } let result = 21 |> doubled let display_name = user?.profile?.name ?? "anonymous" ``` `value |> function` passes `value` as the next call's input. `?.` propagates absence and `??` supplies a fallback. ## 9. Modules ```rove import "./helper" import "std/math" import { sqrt, clamp } from "std/math" ``` Local modules use `.rove` files. Standard-library availability is target-specific and can be inspected with `rove targets --json`. Local packages can be locked without a registry: ```text rove add physics --path ../physics rove install ``` The lockfile records canonical relative paths and source-content checksums; recursive dependency cycles are rejected. For WebAssembly browser programs, `std/web` provides opaque `WebElement`, `WebEvent`, and `WebListener` handles plus DOM, event, animation-frame, and Canvas 2D functions. These calls require the generated `nyx_host_v1` adapter and are rejected on non-WASM targets. The namespace keeps its historical name for Bundle ABI v1 compatibility. ## 10. Tests and unsafe boundaries ```rove test "addition" { assert(add(2, 3) == 5, "addition must be exact") } unsafe { let address = addr(answer) print(peek(address)) } ``` Raw memory operations must remain inside an explicit `unsafe` boundary. ## 11. CLI ```text rove check main.rove rove run main.rove --target cpp rove build main.rove --target js rove bundle main.rove --output dist --package --react --vue --svelte rove test main.rove rove self-host verify rove targets --json ``` The canonical stable hosted backends are `cpp` (C++20/native), `js` (ES2022/Node.js), and `python` (Python 3). `wasm`, `rust`, `react`, and `asm` expose narrower, machine-readable capability sets and must reject unsupported semantics.