Language

What AEL compiles today

Every program on this page was checked, compiled, built and run on linux-x86_64 with compiler commit 6f9c44a. The output shown is the output that appeared, and the exit status shown is the status the process returned.

AEL 0.1 cannot emit a string. print accepts i64 and bool only, so there is no “hello, world” here. print("hello, world") is refused with error[E2105], and printing a bound Str<16> is refused with error[E2100]. Both refusals are shown below with their exact text.


The type vocabulary

This is the whole of it. There is nothing else in the language today.

Every type AEL 0.1 admits.
Kind Types Notes
Integers i8 i16 i32 i64 u8 u16 u32 u64 Checked arithmetic and checked shifts at every width.
Scalars bool, unit No floating point of any width, in any position.
Aggregates struct, fixed arrays [T; N] By-value aggregate parameters and results work.
References &T, &mut T Dereference is explicit. Indexing is bounds-checked.
Tagged values declared enum, Option<T>, Result<T, E> With match and checked payload access.
Error carriers CastError CapacityError IndexError StringError Returned instead of trapping when an operation will not fit.
Bounded collections Vec<T, N>, Str<N> Capacity lives in the type. They never allocate.

Those four generic names are the entire generic vocabulary. User generics are refused: let held: Vault<i64> = 1; gives error[E2900] user generics and unauthenticated runtime handle types are unavailable, and struct Box<T> is refused by the parser.

Operations on the bounded collections are: string literals, vec_empty, vec_len, str_len, length-checked vector indexing, vec_push, vec_remove, str_append and str_byte. Conversions are the eight cast_* functions. Arithmetic escapes are the wrapping_* and saturating_* families.


Three facts to read the examples by

--tier core is usually required

With no --tier, ael check uses the bootstrap tier, which accepts only fn declarations and only i64/bool annotations. Anything using a struct, array, Vec or Str needs ael check file.ael --tier core or it fails with P003/P002. ael compile uses the core tier automatically and has no --tier flag.

Dereference is explicit

Indexing through a reference is written (*samples)[i]. Writing samples[i] where samples is a &[i64; 4] fails with error[E2100] indexing requires an array or vector.

print takes i64 and bool

Nothing else. str_len returns u32, so the examples compare it against a constant and print the bool rather than printing the length. print(true) writes true.


Structs, fixed arrays, references, bounds-checked indexing

samples/checked.ael
// Structs, fixed arrays, references and bounds-checked indexing,
// with checked arithmetic and the explicit wrapping/saturating modes.

struct Reading {
    sensor: i32,
    value: i64,
}

fn total(samples: &[i64; 4]) -> i64 {
    let mut sum: i64 = 0;
    let mut i: i64 = 0;
    while i < 4 {
        sum = sum + (*samples)[i];
        i = i + 1;
    }
    return sum;
}

fn main() -> i64 {
    let samples: [i64; 4] = [10, 20, 30, 40];
    let sum: i64 = total(&samples);
    print(sum);

    let reading: Reading = Reading(7, sum);
    print(reading.value);

    // Explicit modes are the only way to leave the checked default.
    let wrapped: i8 = wrapping_add(127, 1);
    let saturated: i8 = saturating_add(127, 1);
    print(wrapped == -128);
    print(saturated == 127);

    return 0;
}
Commands
ael check checked.ael --tier core
ael compile checked.ael -o checked.aelir --target linux-x86_64 --no-metrics
ael build checked.aelir -o checked --target linux-x86_64 --profile linux-x86_64-scalar-v1 --no-metrics
./checked
What ./checked printed
100
100
true
true
Exit status 0. Image 5705 bytes.

wrapping_add(127, 1) is -128 and saturating_add(127, 1) is 127, at i8. Without one of those two words the same addition is a checked failure — see checked failures.


Bounded Vec<T, N> and Str<N>

The capacity is part of the type, there is no heap, and an operation that will not fit returns a carrier instead of trapping.

samples/bounded.ael
// Bounded collections: Vec<T, N> and Str<N>. The capacity lives in the type,
// there is no heap, and an operation that will not fit returns a carrier
// instead of trapping.

fn main() -> i64 {
    let mut queue: Vec<i64, 3> = vec_empty();
    print(vec_len(&queue) == 0);

    vec_push(&mut queue, 10);
    vec_push(&mut queue, 20);
    vec_push(&mut queue, 30);
    print(vec_len(&queue) == 3);

    // The fourth push does not trap: it hands back Err and leaves the
    // vector byte-identical.
    let overflowed = vec_push(&mut queue, 40);
    match overflowed {
        Result::Ok(nothing) => { return -1; },
        Result::Err(rejected) => { print(rejected == 40); },
    }
    print(vec_len(&queue) == 3);

    let taken = vec_remove(&mut queue, 0);
    match taken {
        Result::Ok(value) => { print(value); },
        Result::Err(error) => { return -2; },
    }

    // Str<N> is a bounded byte buffer, also with its capacity in the type.
    let mut greeting: Str<16> = "hello";
    let target: Str<8> = ", world";
    let appended = str_append(&mut greeting, &target);
    match appended {
        // print takes i64 and bool only, and str_len returns u32.
        Result::Ok(nothing) => { print(str_len(&greeting) == 12); },
        Result::Err(full) => { return -3; },
    }

    let byte = str_byte(&greeting, 0);
    match byte {
        Result::Ok(value) => { print(value == 104); },
        Result::Err(out_of_range) => { return -4; },
    }

    // Reading past the stored length is refused, not a trap.
    let past_end = str_byte(&greeting, 200);
    match past_end {
        Result::Ok(value) => { return -5; },
        Result::Err(out_of_range) => { print(true); },
    }

    return 0;
}
Commands
ael check bounded.ael --tier core
ael compile bounded.ael -o bounded.aelir --target linux-x86_64 --no-metrics
ael build bounded.aelir -o bounded --target linux-x86_64 --profile linux-x86_64-scalar-v1 --no-metrics
./bounded
What ./bounded printed
true
true
true
true
10
true
true
true
Eight lines. Exit status 0. Image 10480 bytes.

A string literal is how a Str<N> is created, and that is as far as strings go. There is no String type — let text: String = "…" gives error[R0006] unknown type name: String — and no way to print the contents of a Str<N>. One further intrinsic, str_from_bytes, type-checks and compiles to IR but is refused by name at native build time: native machine verification: native profile does not admit the str_from_bytes intrinsic.


The eight checked conversions

A value the destination type cannot represent produces Err(CastError::OutOfRange), never a wrap and never a silent truncation. The range test is taken at the source's signedness.

samples/casts.ael
// The eight cast_* conversions are checked: a value the destination type
// cannot represent produces Err(CastError::OutOfRange), never a wrap
// and never a silent truncation.

fn narrow(value: i64) -> i64 {
    let converted: Result<i8, CastError> = cast_i8(value);
    match converted {
        Result::Ok(small) => { return 1; },
        Result::Err(error) => { return 0; },
    }
}

fn main() -> i64 {
    print(narrow(100) == 1);
    print(narrow(300) == 0);
    print(narrow(-129) == 0);
    print(narrow(-128) == 1);

    // The range test is taken at the source's signedness, so u64 at 2^63
    // is refused for i64 rather than read as negative.
    let big: u64 = 9223372036854775808;
    let widened: Result<i64, CastError> = cast_i64(big);
    match widened {
        Result::Ok(value) => { return -1; },
        Result::Err(error) => { print(true); },
    }

    return 0;
}
Commands
ael check casts.ael --tier core
ael compile casts.ael -o casts.aelir --target linux-x86_64 --no-metrics
ael build casts.aelir -o casts --target linux-x86_64 --profile linux-x86_64-scalar-v1 --no-metrics
./casts
What ./casts printed
true
true
true
true
true
Five true lines. Exit status 0. Image 6367 bytes.

Checked failures

There are seven categories. Each writes AEL panic: <category> to standard error — file descriptor 2, separate from program output — and exits 101: arithmetic_overflow, division_by_zero, shift_out_of_range, index_out_of_range, invalid_discriminant, invalid_control_flow, output_io.

Arithmetic overflow

samples/overflow.ael
// Checked arithmetic is the default. An overflow is a checked failure:
// the process reports it and stops, it does not wrap.

fn main() -> i64 {
    print(1);
    let limit: i64 = 9223372036854775807;
    let overflowed: i64 = limit + 1;
    print(overflowed);
    return 0;
}
Transcript
$ ./overflow
1
AEL panic: arithmetic_overflow
$ echo $?
101

$ ./overflow 2>/dev/null
1

$ ./overflow 2>&1 1>/dev/null
AEL panic: arithmetic_overflow
The second print never runs. Image 4662 bytes. The last two commands show the stream separation: program output on stdout, the panic line on stderr.

Index out of range

Every array index is compared against the length before the address is formed, at every integer kind, including a negative signed index.

samples/bounds.ael
// Every array index is compared against the length before the address is
// formed, at every integer kind, including a negative signed index.

fn main() -> i64 {
    let values: [i64; 3] = [1, 2, 3];
    let mut i: i64 = 0;
    while i < 4 {
        print(values[i]);
        i = i + 1;
    }
    return 0;
}
Transcript
$ ./bounds
1
2
3
AEL panic: index_out_of_range
$ echo $?
101
Image 5099 bytes.

A project from ael init

A source/1 project is three files. ael check on a project directory needs no --tier.

Transcript
$ ael init demo --profile ael-project-init-filesystem-candidate/1
initialized "/path/to/parent" / "demo"
roots main.ael pack.ael metadata.ael
plan sha256:…   # digest covers the plan and its parent path, so it differs per directory

$ ael check demo
Checked source/1 project …

$ ael compile demo -o demo.aelir --target linux-x86_64 --no-metrics
$ ael build demo.aelir -o demo-bin --target linux-x86_64 --profile linux-x86_64-scalar-v1 --no-metrics
$ ./demo-bin
$ echo $?
0
The generated program prints nothing and exits 0. Image 4167 bytes.
The three generated files
// main.ael
main main() {
    return;
}

// pack.ael
pack { schema = 1; dependencies = []; }

// metadata.ael
metadata {
    schema = 1;
    name = "demo";
    source_profile = "ael-source/1";
}

Give main a return type and the project returns an exit status. Replacing main.ael with the program below makes ./demo-bin print 42 and exit 42.

main.ael
main main() -> i64 {
    let total: i64 = 6 * 7;
    print(total);
    return total;
}

The exit status is the low eight bits of the returned i64, so a program that returns 6765 exits 109.


The failures that are not checked

There are exactly two documented conditions that escape the checked model. They are listed here rather than omitted, because omitting them would make “every failure is a checked failure” look true.

Stack exhaustion

No profile bounds recursion depth or installs a guard page. This program compiles and builds cleanly.

samples/stack.ael
fn down(n: i64) -> i64 {
    if n == 0 {
        return 0;
    }
    return down(n - 1) + 1;
}

fn main() -> i64 {
    print(down(100000000));
    return 0;
}
Transcript
$ ./stack
Segmentation fault (core dumped)
$ echo $?
139
Empty stdout, empty stderr, no AEL panic line. Image 4822 bytes. Recorded as the stack-exhaustion limitation.

SIGPIPE

A print into a closed pipe kills the process before the output_io category can fire: no panic line and no exit 101. Recorded as the sigpipe-before-output-io limitation.


What the compiler refuses

Each row was produced by running the compiler. The diagnostic text is the compiler's own.

Refusals, with the exact diagnostic. Verified 19 September 2026.
You write The compiler says
print("hello, world") error[E2105] string literal requires an expected Str<N> type
let s: Str<16> = "hello, world"; print(s); error[E2100] intrinsic arguments do not match the core signature registry
let text: String = "…"; error[R0006] unknown type name: String
let ratio: f64 = 1; error[R0006] unknown type name: f64 (and error[P002] at the bootstrap tier). A float literal is refused by the parser.
let held: Vault<i64> = 1; error[E2900] user generics and unauthenticated runtime handle types are unavailable
agent worker { } error[P003] reserved ecosystem declaration has no executable Core source semantics at the core tier; at the bootstrap tier, “bootstrap supports fn declarations; this declaration is unsupported”.
A call through a value rather than a name “unresolved value; function values are unavailable”. There are no function values and no indirect calls.
str_from_bytes(&bytes) Checks and compiles to IR, then ael build fails: native machine verification: native profile does not admit the str_from_bytes intrinsic.
ael run fibonacci.ael “native execution is unavailable for this command or target…”, exit 2. A program is produced by compile then build, and launched by the operating system.
ael build fib-win.aelir --target windows-x86_64 The same refusal sentence, exit 2. No Windows image has ever been produced.

Not in the language at all

  • No heap and no dynamic strings. This is a ruling, not a gap: the support ABI defines no allocation symbol, and a request for ael.rt.alloc or ael.rt.free is refused by its own class.
  • No floating point of any width, in any position.
  • No user generics and no runtime handle types.
  • No function values and no indirect calls.
  • No threads and no concurrency an AEL program can create. There is no language surface that could name one, and the admitted computation is single-threaded.
  • No network of any kind. No first-party crate contains std::net, TcpStream or TcpListener.
  • No file I/O. Output is print, to standard output.
  • No optimizer. --opt accepts only none, and none is the only level that exists.

What is left is bounded, fixed-capacity, single-threaded computation that can print integers and booleans and return an exit status. AEL 0.1 is not a general-purpose language and this site does not describe it as one.