A crystallization-based programming language
Variables in Lattice exist in phases, like matter. They start as mutable flux,
and crystallize into immutable fix with freeze(). Need to change them again?
thaw() brings them back to flux. Forge blocks let you build complex immutable
structures through controlled mutation.
// Forge blocks: controlled mutation that crystallizes fix config = forge { flux temp = Map::new() temp.set("host", "localhost") temp.set("port", "8080") temp.set("debug", "true") freeze(temp) } // config is now permanently immutable print(config.get("host")) // "localhost" print(phase_of(config)) // "crystal"
Lattice structs can hold closures as fields, giving you flexible object-like patterns with explicit data flow.
struct VendingMachine { balance: Int, inventory: Map, insert_coin: Fn, select_item: Fn, dispense: Fn } fn make_vending_machine() -> VendingMachine { flux inv = Map::new() inv.set("cola", 150) inv.set("chips", 100) inv.set("candy", 75) return VendingMachine { balance: 0, inventory: inv, insert_coin: |self, amount| { self.balance + amount }, select_item: |self, item| { let price = self.inventory.get(item) if self.balance < price { "insufficient funds" } else { "ok:${item}" } }, dispense: |self, item| { let price = self.inventory.get(item) let change = self.balance - price "Dispensing ${item}! Change: ${change}" } } }
if/else blocks return values, ranges drive for loops,
and error handling is explicit with try/catch.
fn fizzbuzz(n: Int) -> String { if n % 15 == 0 { "FizzBuzz" } else { if n % 3 == 0 { "Fizz" } else { if n % 5 == 0 { "Buzz" } else { to_string(n) } } } } fn main() { for i in 1..21 { print("${i}: ${fizzbuzz(i)}") } }
First-class closures capture their environment and compose naturally.
Use map, filter, reduce, and sort to transform data.
fn main() { let data = [5, 3, 8, 1, 9, 2, 7] // Chain operations let result = data .filter(|x| { x > 3 }) .map(|x| { x * 2 }) .sort() print(result) // [10, 14, 16, 18] // Reduce to a single value let sum = data.reduce(|acc, x| { acc + x }, 0) print("sum = ${sum}") // sum = 35 }
Embed any expression directly inside a string with ${...}.
Variables, arithmetic, method calls, and nested expressions all work.
Use \${ for a literal dollar-brace.
fn main() { let name = "Lattice" let version = 8 // Variables and expressions print("hello ${name}") // hello Lattice print("2 + 2 = ${2 + 2}") // 2 + 2 = 4 print("v0.2.${version}") // v0.2.8 // Method calls print("upper: ${name.to_upper()}") // upper: LATTICE print("len: ${[1,2,3].len()}") // len: 3 // Escaped: literal ${ print("price: \${9.99}") // price: ${9.99} }
Spawn tasks inside scope blocks for parallel execution with automatic join semantics.
Channels provide thread-safe communication — only crystal (frozen) values can cross thread
boundaries, so the phase system prevents data races at the language level.
Use select to multiplex across multiple channels with optional timeouts and defaults.
fn sum_range(start: Int, end: Int) -> Int { flux total = 0 for i in start..end { total = total + i } return total } fn main() { let ch1 = Channel::new() let ch2 = Channel::new() // Each spawn runs on its own thread scope { spawn { ch1.send(freeze(sum_range(0, 500000))) } spawn { ch2.send(freeze(sum_range(500000, 1000000))) } } // Both tasks are done here let total = ch1.recv() + ch2.recv() print("total: ${total}") // total: 499999500000 }
Lattice catches mistakes early with runtime type checking, function contracts, and safe navigation — without sacrificing simplicity.
// Runtime type checking fn withdraw(account: Map, amount: Int) -> Int require amount > 0, "amount must be positive" ensure |result| { result >= 0 }, "balance can't go negative" { return account.get("balance") - amount } // Optional chaining & nil coalescing let city = user?.address?.city ?? "Unknown" // Defer for guaranteed cleanup fn process(path: String) { let fd = open(path) defer { close(fd) } // runs on any exit // ... } // Result ? operator for error propagation fn load() -> Map { let data = read_config()? // unwrap ok, or return err let parsed = validate(data)? return ok(parsed) }
JSON, math, regex, path utilities, string formatting, crypto, date/time, and more — over 120 builtin functions with no external dependencies.
fn main() { // JSON let data = json_parse('{"name": "Lattice", "version": 0.1}') print(data.get("name")) // Lattice // Regex let emails = regex_find_all( "[a-z]+@[a-z]+\\.[a-z]+", "contact alice@example.com or bob@test.org" ) print(emails) // ["alice@example.com", "bob@test.org"] // String interpolation let pi = 3.14159 print("pi ≈ ${pi}") // Path utilities let p = path_join("src", "lang", "parser.lat") print("path=${p}, dir=${path_dir(p)}, ext=${path_ext(p)}") }
Raw TCP sockets and TLS connections are first-class builtins. Build HTTP clients, servers, and encrypted communication — no external libraries required.
fn http_get(host: String, path: String) -> String { let fd = tcp_connect(host, 80) let req = "GET ${path} HTTP/1.0\r\nHost: ${host}\r\n\r\n" tcp_write(fd, req) let response = tcp_read(fd) tcp_close(fd) return response } fn main() { // Plain HTTP let html = http_get("example.com", "/") print(html) // TLS for encrypted connections if tls_available() { let fd = tls_connect("api.github.com", 443) tls_write(fd, "GET / HTTP/1.1\r\nHost: api.github.com\r\nConnection: close\r\n\r\n") print(tls_read(fd)) tls_close(fd) } }
A small, focused language that gets the fundamentals right.
Variables exist as mutable flux or immutable fix. Transition with freeze(), thaw(), and forge blocks.
Closures capture their environment and work as values. Pass them to functions, store them in structs, return them from anywhere.
Struct fields can hold closures with self access, giving you object-like patterns with explicit data flow.
if/else, match, and blocks are all expressions that return values. Less boilerplate, more clarity.
scope/spawn with channels and select multiplexing. Each task gets its own thread and GC. The phase system prevents data races by design.
Raw sockets and encrypted connections as builtins. Build HTTP clients and servers without external dependencies.
try/catch for exceptions, defer for guaranteed cleanup, and the ? operator for ergonomic Result propagation.
Runtime type annotations, require/ensure contracts, optional chaining x?.field, and ?? nil coalescing.
JSON, regex, math, crypto, file I/O, path utilities, date/time formatting, and type conversion — all built in.
A compiler written in Lattice compiles Lattice to bytecode. Single-pass, no AST — emits .latc binaries directly during parsing.
Persistent session with multi-line input and history. Function, struct, and enum declarations stay available for the entire session.
Source compiles to bytecode and runs on a stack-based VM by default — both files and the REPL. An ephemeral bump arena eliminates per-allocation overhead for string temporaries, with bulk O(1) resets at statement boundaries.
Lattice includes a self-hosted compiler written entirely in Lattice itself. It reads .lat
source files, emits bytecode, and writes .latc binaries — using the same bytecode
format as the C compiler. The self-hosted compiler is a single-pass design that emits bytecode directly
during parsing, with no intermediate AST.
# Compile a Lattice program using the self-hosted compiler clat compiler/latc.lat program.lat program.latc # Run the compiled bytecode clat program.latc # The self-hosted compiler supports: # - Functions, closures, and upvalue capture # - Variables with flux/fix/let declarations # - Control flow: if/else, while, loop, for..in # - String interpolation # - Compound assignment operators # - .latc bytecode serialization
Declare dependencies in lattice.toml, install from git repositories or the registry,
and import them like any other module. Packages are cached locally and resolved automatically.
Initialize
clat init
Add dependencies
clat add my-lib --git https://github.com/user/my-lib.git --tag v1.0
Use in your code
import "my-lib" as lib
[package] name = "my-project" version = "0.1.0" entry = "main.lat" [dependencies] json-utils = "^0.2.0" my-lib = { git = "https://github.com/user/my-lib.git", tag = "v1.0.0" } dev-tools = { git = "https://github.com/user/dev-tools.git", branch = "main" }
Git dependencies are shallow-cloned, checked out to the specified tag, branch, or commit,
and cached in ~/.lattice/packages/. Supports semver constraints:
*, ^1.2.3, ~1.2.3, >=1.0.0.
Install Lattice with a single command. Detects your platform, verifies checksums, and installs the clat binary.
curl -fsSL https://lattice-lang.org/install.sh | sh
or install via Homebrew
brew tap ajokela/lattice && brew install lattice
or download a binary directly
checksums.txt · v0.3.29
Clone and build
git clone https://github.com/ajokela/lattice.git && cd lattice && make
Run a program or start the REPL
./clat examples/phase_demo.lat