Axle v0.14.1

Hello, Axle

// hello.axle
fn main() : i32 {
    println("Hello, Axle!");
    return 0;
}
./target/release/axle build hello.axle -o hello
./hello
# Hello, Axle!

The commands on this page call the from-source binary by its path (./target/release/axle). If you installed axle from apt or the Windows MSI, it is already on your PATH — drop the prefix and run axle build hello.axle -o hello.

Four things to read off that program:

  • fn main() : i32 — every program starts at main. The : i32 return type is the process exit code: 0 means success, non-zero signals an error to the shell. (The axle new scaffold uses fn main() : void instead — a program that always succeeds.)
  • println(...) — prints a line to standard output. It is part of the prelude, so it needs no use. The explicit form Console::println(...) calls the same builtin; Console is one of the handful of classes that are always in scope. (The axle new scaffold writes the explicit form.) Declaring your own fn println(...) shadows the prelude word — your function is what a bare println(...) calls from then on.
  • :: reaches a namespace member — a static method on a class (Console::println), a module function (math::pi()), an enum variant (Color::Red). . is for instance members. More on that in the tour.
  • No free, no garbage collector. The compiler decides where each value lives and inserts its cleanup for you — see the Memory model.

Tip — axle run hello.axle builds a native binary and executes it in one step, as it does inside a project directory; axle build is the same build with the binary kept.

Inspect what the compiler produced

# LLVM IR (post-optimiser when `-O > 0`)
./target/release/axle build hello.axle -O 2 --emit llvm -o hello.ll

# Native assembly
./target/release/axle build hello.axle -O 2 --emit asm -o hello.s

# Intermediate-representation debug dumps (provisional shape)
./target/release/axle build hello.axle --emit hir -o hello.hir
./target/release/axle build hello.axle --emit ast -o hello.ast

A slightly bigger example

use std::collections::ArrayList;

fn main() : i32 {
    let squares : ArrayList<i32> = new ArrayList<i32>();
    for (i of 0..10) {
        squares.add(i * i);                  // 0, 1, 4, 9, …, 81
    }

    let sum : i32 = 0;
    for (j of 0..squares.size()) {
        sum = sum + (squares.get(j) ?? 0);   // in range — never null
    }
    println(sum);                            // 285
    return 0;
}

for (i of 0..n) walks a range — the idiomatic loop in Axle. Index a collection over 0..xs.size() rather than iterating it directly.

println is one prelude builtin, not a family of overloads. It is variadic, and the compiler picks the format from the argument’s static type at the call site — so there is no per-type variant name to remember. The types it accepts: bool, char, string, every sized integer (i8–i64, u8–u64), f32 / f64, a class or Shared<T> reference (printed as its address), and a T | null of one of those.

ArrayList lives in std::collections — see the API reference for the complete method list.

Model data with enum and match

A closed set of cases is an enum; match selects an arm and is itself an expression, so it returns directly. The compiler checks that every variant is covered — a missing case is a compile error, not a runtime surprise:

enum Color { Red, Green, Blue }

fn name(c : Color) : string {
    return match c {
        Color::Red   => "red",
        Color::Green => "green",
        Color::Blue  => "blue",
    };
}

fn main() : i32 {
    println(name(Color::Green));             // green
    return 0;
}

Both constructs have a dedicated chapter: Enums covers variant payloads, backing types and enum methods, and Pattern matching covers every pattern kind and the exhaustiveness rule. The rest of the type-system surface — struct value types, trait contracts, generics and @derive — is in the Language tour and Generics and traits.

Limitations

  • println does not take a struct value. A value-type struct has no static type println knows how to print, and codegen stops with an internal error rather than a diagnostic. Print the fields — p.x, or a toString your class provides — instead.
  • A class prints as its address. println(obj) is a pointer, not a rendering; give the class a toString and print that when the address is not what you meant.
  • for (v of xs) wants a range or an array. A collection is walked by index: for (j of 0..squares.size()). The loop head over anything else is E0517.

See also

  • Language tour — the surface syntax in one pass.
  • Memory model — what new T(...) actually does.
  • Enums and Pattern matching — the two constructs from the last example, in depth.
  • Recipes — task-oriented examples (collections, I/O, processes, …).
  • Concept index — every concept on one page with a link to where it’s documented.
getting-startedtutorialhello-world