astra-lang.org

Language tour

Readable to humans.
Checkable by machines.

Astra's syntax borrows Python's directness and Rust's discipline: type inference keeps code terse, static checking keeps it honest, and every program compiles to a native binary.

BasicsVariables, inference, and strings

Types are inferred from initializers; annotations are optional and checked when present. Template literals interpolate with ${}.

let name = "Astra";          // inferred str
let version: float = 0.9;   // explicit annotation
let tests = 449;            // inferred int

print("${name} v${version} — ${tests} tests green");

FunctionsRecursion, control flow, loops

fn fib(n: int) -> int {
    if n < 2 { return n; }
    return fib(n - 1) + fib(n - 2);
}

fn main() -> int {
    for i in 0..10 {
        print("fib(${i}) = ${fib(i)}");
    }
    return 0;
}

while, do/while, switch, break/continue, and for-range loops all compile to native code today.

DataStructs, enums, and pattern matching

struct User {
    name: str,
    age: int,
    active: bool
}

let u = User { name: "Ada", age: 36, active: true };
print(u.name);              // typed field access, native GEP loads

enum Status { Active, Suspended, Deleted }

let label = match status {
    Status::Active    => "ok",
    Status::Suspended => "paused",
    Status::Deleted   => "gone"
};

ErrorsResult<T,E> — no exceptions

Fallible operations return Result. The compiler forces the caller to handle both arms; there is no invisible control flow.

fn divide(a: int, b: int) -> Result<int, str> {
    if b == 0 {
        return Err("division by zero");
    }
    return Ok(a / b);
}

match divide(10, 2) {
    Ok(value)  => print("result: ${value}"),
    Err(cause) => print("failed: ${cause}")
}

FunctionalClosures

Closures capture from the enclosing scope. The compiler lifts them into standalone functions at compile time (lambda lifting), so a closure call is exactly as fast as a plain function call.

fn main() -> int {
    let factor = 3;
    let scale = |x: int| { x * factor };   // captures factor
    print(scale(5));                       // 15 — native call
    return 0;
}

TypesGenerics and gradual typing

Generic functions are monomorphized — specialized per concrete type at compile time, like Rust and C++ — so generics carry zero runtime cost.

fn max<T>(a: T, b: T) -> T {
    if a > b { return a; }
    return b;
}

When a boundary genuinely can't be typed statically — data from an LLM, a dynamic config — dyn marks it explicitly. Checks defer to runtime at that boundary and nowhere else.

fn handle(payload: dyn) -> dyn {
    // statically compatible with every type;
    // the escape hatch is visible in the signature
    return payload;
}

Under the hoodThe compiler pipeline

Astra is implemented in Rust (~15 core modules) as a classic multi-phase compiler:

source.astra
  → lexer        tokens with line + column
  → parser       recursive-descent AST
  → closure lift closures → top-level functions
  → typechecker  type inference + checking
  → monomorphize generic specialization
  → IR           LLVM IR generation
  → llc + link   object code + small C runtime
  → native executable

Diagnostics are emitted as structured JSON at every phase, which is what makes the agent repair loop possible. The direction is locked in five ratified architecture decisions: ARC memory management, gradual typing via dyn, a single LLVM backend targeting native / WASM / GPU, AI primitives as keywords, and Python interop via embedded CPython.