docs / language

The fx language

fx is a systems language built for locality of reasoning: allocation, mutation, ownership, and I/O show up in the source, then lower to readable C on a small zspec substrate. Version 0.7.1 is already a full programming surface, not just “regions + vec.”

What fx is trying to be

Design pillars

1. Explicit effects

fn main() -> i32 effects { alloc, mut, io } {
    // may allocate, mutate, and do I/O
}

Implemented effects: alloc, mut, io. Pure functions omit the clause. Callers can see cost before reading the body.

2. Named regions

region r = arena(4096);   // heap arena
region t = temp(1024);    // short-lived heap batch
region s = scope;         // stack borrow region (no heap)
region f = fx(4096);      // hierarchical fx region

Full walkthrough: Regions and effects (ownership, & / &mut / &region, all region kinds).

3. Dual emission

4. Small but real standard library

std/ is ordinary fx you import. See Standard library for APIs beyond vec.

Program structure

Types (0.7 surface)

CategoryWhat you can use
Integersi8 i16 i32 i64 · u8 u16 u32 u64
Floatsf32 f64 (unsuffixed float literals default to f64; use 1.5f32)
Other scalarsbool · string · void · core_Err
Aggregatesstruct · enum (unit + payload variants)
CollectionsVec<T> · [T; N] · &[T] · StrBuilder · Map<string, i32>
ErrorsResult<T, E> (usually core_Err)
Ownershipown T · &T · &mut T · &region r T
GenericsFunctions and structs (fn new<T>(…), Box<T>, Pair<A,B>) · no traits

Numeric rule of thumb: same-family ops only; i32↔i64 and f32↔f64 can widen; no implicit signed↔unsigned or int↔float. Use postfix cast: x as u32.

Control flow and data

enum Tok { Num(i32), End }

fn value(t: Tok) -> i32 {
    return match t {
        Num(n) => n,
        End => 0,
    };
}

Errors: Result and ?

using core;

fn parse_positive(n: i32) -> Result<i32, core_Err> {
    if (n <= 0) {
        return Err(CORE_ERR_INVALID_ARG);
    }
    return Ok(n);
}

fn main() -> Result<i32, core_Err> {
    let v = parse_positive(10)?;
    return Ok(v);
}

There is no Option: use Result. Map lookup misses are errors and compose with ?.

Value-threading (important idiom)

Growing collections return an updated handle. Reassign the result (same pattern for vec, map, strbuf):

v = vec_push(v, 40);
m = map_insert(m, "width", 30);
b = strbuf_push(b, "hello");

Prefer vec_get(v, i) for element access. There is no operator indexing on Vec (v[i] is for arrays/slices only).

First programs

Minimal

fn main() -> i32 {
    return 42;
}

fx new tiny --scaffold minimal

Recommended (visible heap + std/vec)

import std/vec;

fn main() -> i32 effects { alloc, mut } {
    region r = arena(4096);
    let v: Vec<i32> = vec.new(0);
    let v2: Vec<i32> = vec.push(v, 40);
    let v3: Vec<i32> = vec.push(v2, 2);
    return vec.get(v3, 0) + vec.get(v3, 1);
}

fx new hello (default simple scaffold)

Arrays and slices

fn sum(s: &[i32]) -> i32 {
    let mut acc: i32 = 0;
    for (let i: i32 = 0; i < s.len; i = i + 1) {
        acc = acc + s[i];
    }
    return acc;
}

fn main() -> i32 {
    let table: [i32; 3] = [10, 20, 12];
    let view: &[i32] = &table;
    return sum(view);  // 42
}

Working with C

fx run examples/showcase_wrap/compute.fx --host examples/showcase_wrap/host.c

See Wrap / C host for extern "c" and linking.

What not to expect (yet)

Compact lookup: Reference (0.7 surface) · Regions · Std · CLI