Ingle for LLMs — the priors cheat-sheet

Paste this whole page into a model’s context before asking it to write Ingle. It is the short list of places where Ingle diverges from the habits a model carries over from C, Rust, Go, Python, Swift, and TypeScript — i.e. the exact spots where zero-/few-shot Ingle goes wrong. It is also a fast human reference.

Ingle is designed for least surprise to a language model, but a few things still trip up code trained on other languages. Each row below pairs the habit a model tends to reach for (✗) with the Ingle form (✓). Every snippet on this page was compiled with inglec before it was written down; the complete program at the end compiles and runs as shown, and the fragments above it are extracted from programs that do.

Do not feed a model grammar.ebnf. A formal grammar makes a model emit derivations (IFStatement: IF Expression …) instead of programs. Use this page and the examples instead — concrete code is what models generalise from.

The cheat table

✗ Habit from another language ✓ Ingle Why
func f(), def f(), function f() fn f() { … } functions are fn; return type is -> T
let mut x = 0 var x = 0 let is immutable, var is mutable — there is no let mut
reassigning a let declare it var error: cannot assign to an immutable 'let' binding
fn f() -> int { 5 } (last-expr return) fn f() -> int { return 5 } no implicit last-expression return; return is required
println("x =", n) println("x = {n}") print/println take exactly one argument; compose with interpolation
"{shape}" where shape is a struct/interface works if the type has fn show(self) -> string (the Show contract); else "{shape.area()}", "{p.x}" bare interpolation renders a number/string/bool directly, or any value whose type provides show (structural, like Go’s Stringer — no implements Show needed)
null, nil, None as a bare value Option<T> with Some(v) / None there is no null; absence is an Option
String, Vec<int>, int64, double string, [int], i64, f64 primitives are lowercase; arrays are [T]; int=64-bit, float=f64
type Id = int assumed to be a transparent alias (Go/TS type) a distinct nominal type: Id(7) constructs; int and Id don’t interchange newtypes erase to the base (zero cost) but the compiler keeps them apart; arithmetic needs an explicit int(x) unwrap
a hand-written validated wrapper (private field + checking constructor) type Percent = int where 0 <= self && self <= 100 a refinement: the where predicate is checked at construction (Percent(150) traps refinement_violation), elided in --release
ch.send(x), ch.recv() send(ch, x), recv(ch), close(ch) channel ops are free functions, not methods
let c = channel(10) let c: Channel<int> = channel(10) annotate the element type at the binding (it can’t be inferred)
case X => { … } (arrow) case X { … } match arms are case PATTERN { … }no =>
impl Trait for T { … } put methods inside the struct body there are no impl blocks (see below)
0..=3 (inclusive range) 0..3, or 0..n+1 ranges are half-open: 0..3 is 0,1,2; there is no ..=
Shape.Origin / Shape::Origin Origin enum variants are referenced bare (a qualified form also parses)
assuming Circle(radius: 2.0) is illegal Circle(2.0) or Circle(radius: 2.0) enum variants construct positionally or by field name (named mirrors a struct literal) — both are valid
use std::x, from x import y, import x import "std/string" as str imports are a quoted path with an as alias

Structs, methods, interfaces — no impl blocks

Methods live inside the struct body. Conformance is declared with implements in the header and checked by the compiler. An interface used as a type gives you dynamic dispatch — no inheritance.

interface Drawable {
    fn area(self) -> float
}

struct Rect implements Drawable {
    w: float
    h: float

    fn area(self) -> float { return self.w * self.h }   // method in the body, `self` receiver
}

// An interface as the element type holds a mix of concrete types; an interface-typed
// parameter dispatches dynamically.
fn total(shapes: [Drawable]) -> float {
    var sum = 0.0
    for s in shapes { sum = sum + s.area() }
    return sum
}

Enums and pattern matching

Variants are newline-separated. Payload fields are named in the declaration but bound positionally in a match. Every arm is case PATTERN { … } — no arrows.

enum Shape {
    Circle(radius: float)
    Rect(w: float, h: float)
    Origin
}

fn area(s: Shape) -> float {
    match s {
        case Circle(r)  { return 3.14159 * r * r }
        case Rect(w, h) { return w * h }
        case Origin     { return 0.0 }
    }
}

Option instead of null

let o: Option<int> = Some(5)
match o {
    case Some(v) { println("got {v}") }
    case None    { println("empty") }
}

Concurrency: nursery, spawn, channels

nursery is a structured-concurrency scope that joins every fiber it spawns before control leaves the block. Channels are typed and buffered; recv yields Option<T> and returns None once the channel is closed and drained.

let results: Channel<float> = channel(10)   // annotate the element type

nursery {
    for s in shapes {
        spawn work(s, results)               // spawn only inside a nursery
    }
}                                            // all spawned fibers have finished here

close(results)

var total = 0.0
loop {
    match recv(results) {
        case Some(v) { total = total + v }
        case None    { break }               // closed + drained
    }
}

A complete program (compiles and runs)

This exercises interfaces, dynamic dispatch, structured concurrency, and pattern matching together — it compiles and runs with inglec --emit=run.

interface Drawable {
    fn area(self) -> float
}

struct Rect implements Drawable {
    w: float
    h: float

    fn area(self) -> float { return self.w * self.h }
}

struct Circle implements Drawable {
    radius: float

    fn area(self) -> float { return 3.14159 * self.radius * self.radius }
}

// A worker fiber: compute one shape's area and send it down the channel.
fn process_shape(shape: Drawable, results: Channel<float>) {
    let a = shape.area()
    println("processing a shape — area: {a}")
    send(results, a)
}

fn main() {
    let shapes: [Drawable] = [
        Rect { w: 10.0, h: 5.0 },
        Circle { radius: 2.5 },
        Rect { w: 3.0, h: 4.0 }
    ]

    let results: Channel<float> = channel(10)

    nursery {
        for shape in shapes {
            spawn process_shape(shape, results)
        }
    }

    close(results)

    var total_area = 0.0
    loop {
        match recv(results) {
            case Some(area) { total_area = total_area + area }
            case None       { break }
        }
    }

    println("total area: {total_area}")
}

For the full language, read the language reference and The Ingle Book; for the design philosophy, the manifesto.