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.
Type annotations are enforced at runtime. Function contracts with require/ensure add preconditions and postconditions.
x?.field, x?.method(), x?[index] — safely navigate nullable values. Compose with ?? for defaults.
JSON, regex, math, crypto, file I/O, path utilities, date/time formatting, and type conversion — all built in.
The interactive REPL is written in Lattice itself. Multi-line input, history, and expression evaluation built in.
Source compiles to bytecode and runs on a stack-based VM by default — both files and the REPL. Full phase system support including react, bond, seed, pressure, and alloy types.
Lattice is written in C with minimal dependencies. Build from source in seconds.
Clone the repository
git clone https://github.com/ajokela/lattice.git
Build with make
cd lattice && make
Run a program or launch the REPL
./clat examples/phase_demo.lat
Or start the interactive REPL
./clat