A programming language

Safe-by-construction
by design.

Loom makes side effects, failure modes, and scaling properties first-class citizens of every function definition. The compiler verifies them statically, so runtime surprises die at build time.

// Classic recursion behind a refined domain.
type NonNeg = Int where self >= 0

recipe fib(n: NonNeg)
  produces: Int
{
  if n <= 1 {
    n
  } else {
    fib(n - 1) + fib(n - 2)
  }
}
loom exec
$ loom exec examples/13_fibonacci.loom fib 20
[1/3] Loom → Rust ... OK (127 lines)
[2/3] Rust → binary ... OK
6765
[3/3] Execute ....... exit code 0

Why Loom

Failure modes as first-class citizens.

Six pillars hold Loom up. Each one is enforced by the compiler, not documented in comments.

🧵

Recipe-first functions

Every function is a recipe. It declares its dependencies, produced value, failure modes, observability, and scaling properties — all compiler-verified in committed mode (recipe!).

🎯

Refinement types

Tighten a base type with a pure predicate: type Port = Int where self in 1..65535. Narrowing inserts a runtime check at the dispatcher boundary. Widening is always safe.

Effect rows

Every recipe carries a row of effects: IO, Async, Fail<E>, Mut<S>, Rand, Time. Row mismatches are compile errors, not surprises.

🔀

Four codegen backends

One .loom source compiles to Rust, TypeScript, Go, or WebAssembly Text. Refinement validation runs at the dispatcher boundary in every target. Generated code is zero-dependency.

🧬

Concurrency primitives

spawn, race, all, and typed LoomChannel<T> with send/recv. The effect row tracks Async automatically.

📦

Zero-dep pipeline

loom exec drives Loom → Rust → rustc → run with a content-addressed FNV-1a cache. Warm runs reuse the compiled binary — ~4× speedup on hits. No cargo required.

The exec pipeline

Three stages, one command.

loom exec <file.loom> [recipe] [args...] parses, type-checks, verifies effects, emits self-contained Rust, compiles it with rustc, caches the binary, and runs it. Every stage prints its status.

1

Loom → Rust

Lexer, parser, bidirectional type checker, effect verifier, and Rust codegen. Emits a self-contained .rs file with an embedded stdlib.

OK (127 lines)
2

Rust → binary

Shells out to rustc --edition 2021. The generated code has zero Rust-side dependencies, so no cargo project setup is needed.

cached (b98fe747)
3

Execute

Runs the binary with stdin/stdout/stderr inherited. Refinement violations surface verbatim; the child's exit code propagates.

exit code 0

Tip Click any stage to highlight it, or jump to the quickstart to try it yourself.

One source, four targets

Compile once. Run anywhere.

loom build --target=<rust|typescript|go|wasm> file.loom emits idiomatic source for any of the four backends. Refinement validation happens at the dispatcher boundary in every target.

factorial.rs
pub fn factorial(n: i64) -> Result<i64, LoomError> {
    if n <= 1 {
        Ok(1)
    } else {
        Ok(n * factorial(n - 1)?)
    }
}

Runs with rustc --edition 2021. No dependencies. No Cargo.toml. Self-contained stdlib inlined.

factorial.ts
export function factorial(n: number): number {
    if (n <= 1) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

Runs on Node with the --experimental-strip-types flag. Integer divisions become Math.trunc() to match Loom semantics.

factorial.go
func factorial(n int64) int64 {
    if n <= 1 {
        return 1
    }
    return n * factorial(n - 1)
}

Runs with the standard go run toolchain. Concurrency lowers spawn to go func() with typed channels.

factorial.wat
(module
  (func $factorial (param $n i64) (result i64)
    (if (result i64)
      (i64.le_s (local.get $n) (i64.const 1))
      (then (i64.const 1))
      (else
        (i64.mul
          (local.get $n)
          (call $factorial
            (i64.sub (local.get $n) (i64.const 1)))))))
  (export "factorial" (func $factorial)))

The arithmetic-and-control-flow subset. Runs in any WASM runtime — wasmtime, wabt, or V8 via WebAssembly.instantiate().

Recent examples

Five ways to write Loom.

Each of these ships in the repo under examples/ and runs end-to-end through loom exec. Click a card to expand.

12 Stdlib builtins examples/12_builtins.loom

Composition of println, toString, abs, max, and min — the core stdlib surface that every backend inlines.

recipe clamped(value: Int)
  produces: Int
{
  min(max(value, 0), 100)
}

$ loom exec examples/12_builtins.loom clamped 250 → 100

13 Fibonacci + refinements examples/13_fibonacci.loom

Classic recursive algorithms (fib, fibIter, gcd, power) behind a refined NonNeg domain. The dispatcher rejects negative input before the body runs.

type NonNeg = Int where self >= 0

recipe gcd(a: NonNeg, b: NonNeg)
  produces: Int
{
  if b == 0 { a } else { gcd(b, a - b * (a / b)) }
}

$ loom exec examples/13_fibonacci.loom gcd 48 36 → 12

14 Channel pipeline examples/14_pipeline.loom

Producer → transformer → consumer using typed LoomChannels. Helper recipes keep each spawn block to a single expression so the generated Rust stays readable.

recipe pipeline(n: Int)
  produces: Int
{
  let source = channel()
  let squared = channel()
  let p = spawn { send(source, n) }
  let t = spawn { send(squared, square(recv(source))) }
  recv(squared)
}

$ loom exec examples/14_pipeline.loom pipeline 5 → 25

15 Layered refinements examples/15_refinements.loom

Multiple refined Int types (Score, Age) describe different business domains. Each gets its own runtime constructor.

type Score = Int where self in 0..101
type Age   = Int where self in 0..151

recipe grade(a: Score, b: Score, c: Score, d: Score)
  produces: Int
{
  (a + b + c + d) / 4
}

$ loom exec examples/15_refinements.loom grade 85 90 78 92 → 86

📂 Multi-file modules examples/modules/

A three-level import chain shows the module loader's diamond-dedup behavior. analytics.loom imports geometry.loom which imports math.loom. The shared leaf compiles once.

// analytics.loom
import "./geometry.loom"
import "./math.loom"

recipe hypotSq(a: Int, b: Int)
  produces: Int
{
  square(a) + square(b)
}

$ loom exec examples/modules/analytics.loom hypotSq 3 4 → 25

Quickstart

Up and running in under a minute.

  1. Install from crates.io

    bash
    cargo install --lock loom-lang

    The crate is loom-lang (name taken on crates.io), but the installed binary is simply loom. The --lock flag respects the published Cargo.lock for reproducible installs.

  2. Run an example

    bash
    loom exec examples/13_fibonacci.loom fib 20
    # [1/3] Loom → Rust ... OK (127 lines)
    # [2/3] Rust → binary ... OK
    # 6765
    # [3/3] Execute ....... exit code 0

    Prefer to hack on the compiler itself? Clone the repo and run cargo build --release — the dev binary lives at target/release/loom.

  3. Write your first recipe

    hello.loom
    type Port = Int where self in 1..65535
    
    recipe validatePort(port: Port)
      produces: Int
    {
      port
    }
    $ loom exec hello.loom validatePort 8080   # → 8080
    $ loom exec hello.loom validatePort 0      # → RefinementViolation
  4. Explore the other commands

    • loom check <file> — type-check and verify effects only
    • loom build --target=typescript <file> — emit TypeScript
    • loom test <file> — run every test_* recipe
    • loom fmt <file> — canonical format (idempotent)
    • loom lsp — start the language server on stdio
    • loom cache info — inspect the compile cache

Ready to weave?

Read the deep dive on the loom exec pipeline, or skim the LLM contextualization file to onboard an AI collaborator.