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.