A delightful language for programmers who've suffered enough. Types without annotations. Threads without locks. Errors without surprises.
import list
fn fizzbuzz(n) {
match (n % 3, n % 5) {
(0, 0) -> "FizzBuzz"
(0, _) -> "Fizz"
(_, 0) -> "Buzz"
_ -> "{n}"
}
}
fn main() {
1..21
|> list.each { n -> println(fizzbuzz(n)) }
}
The only way to branch. Define your types, then match on their shape. The compiler verifies every case is handled.
type Shape { Circle(Int), Square(Int), Triangle(Int, Int) }
fn area(s: Shape) -> Int {
match s {
Circle(r) -> 3 * r * r
Square(w) -> w * w
Triangle(b, h) -> b * h / 2
}
}
Spawn lightweight tasks that run in parallel on a fixed thread pool. Communicate through channels. Every value is immutable, so there are no data races to debug. I/O operations transparently yield to the scheduler — no async/await needed.
import channel
import list
import task
fn main() {
let ch = channel.new(10)
let w1 = task.spawn(fn() {
channel.each(ch) { msg -> println("w1: {msg}") }
})
let w2 = task.spawn(fn() {
channel.each(ch) { msg -> println("w2: {msg}") }
})
list.each(1..100) { n -> channel.send(ch, n) }
channel.close(ch)
task.join(w1)
task.join(w2)
}
Every function that can fail returns a Result. The ? operator propagates errors without nesting. Nothing is thrown or caught.
import io
import json
type Config { name: String }
fn main() {
match io.read_file("settings.json") {
Ok(content) -> match json.parse(content, Config) {
Ok(cfg) -> println("loaded: {cfg.name}")
Err(e) -> println("parse error: {e.message()}")
}
Err(IoNotFound(path)) -> println("no config at {path} — run `silt init` first")
Err(e) -> println("read error: {e.message()}")
}
}
Stdlib errors are typed enums — IoError, JsonError, HttpError, and so on — so match can fork on specific failures like IoNotFound(path) and still fall back to .message() for the long tail.
Built-in HTTP client and server. Pattern matching replaces routing frameworks. Requests are handled concurrently.
import http
import json
type Todo { id: Int, title: String, done: Bool }
fn main() {
http.serve(8080, fn(req) {
match (req.method, http.segments(req.path)) {
(GET, ["todos"]) -> {
let todos = [
Todo { id: 1, title: "Learn silt", done: true },
Todo { id: 2, title: "Build an API", done: false },
]
Response { status: 200, body: json.stringify(todos), headers: #{} }
}
(POST, ["todos"]) ->
match json.parse(req.body, Todo) {
Ok(todo) -> Response { status: 201, body: json.stringify(todo), headers: #{} }
Err(e) -> Response { status: 400, body: e.message(), headers: #{} }
}
_ ->
Response { status: 404, body: "Not found", headers: #{} }
}
})
}
The type checker infers everything. You get static type safety without writing annotations. Define records, enums, and traits when you need structure.
type User {
name: String,
age: Int,
}
fn greet(user) {
"hello, {user.name} ({user.age})"
}
fn main() {
let u = User { name: "alice", age: 30 }
println(greet(u))
println(greet(u.{ age: 31 })) -- record update
}
curl -fsSL https://silt-lang.com/install.sh | shThe installer verifies the downloaded binary against the release's SHA256SUMS file before extracting — a mismatch aborts the install so a corrupted or tampered archive can't land on disk. To upgrade an existing install without re-running the curl pipe, use silt self-update (which performs the same verification).
Or build from source:
git clone https://github.com/rendro/silt.git
cd silt && cargo build --release
cp target/release/silt ~/.local/bin/Then:
silt init
silt runsilt init writes a silt.toml manifest and a starter src/main.silt; silt run (no arguments, inside a package) executes the entry point.
See examples/ for runnable sample programs — start with examples/hello.silt, examples/fizzbuzz.silt, and examples/records.silt.
silt run <file.silt> Run a program
silt run -w <file.silt> Run and re-run on file changes
silt check <file.silt> Type-check without running
silt check --format json <file.silt> Type-check with JSON output (for CI/editors)
silt test [path] Run test functions
silt fmt [files...] Format source code
silt fmt --check Check formatting without modifying files
silt repl Interactive REPL
silt init Create a new silt package in the current directory
silt lsp Start the language server
silt disasm <file.silt> Show bytecode disassembly (same as `silt run --disassemble`)
silt self-update Update the silt binary to the latest release
silt update [<dep-name>] Regenerate silt.lock for the current package's dependencies
silt add <name> --path <path> Add a path-based dependency to silt.toml
silt add <name> --git <url> [--rev|--branch|--tag <ref>] Add a git-based dependency to silt.toml
The --watch / -w flag works with run, check, and test. It watches the project directory for .silt file changes and automatically re-runs the command.
LSP server with diagnostics, hover types, go-to-definition, completion, signature help, document symbols, and formatting. The prebuilt silt binary from the install script includes the LSP server — just run silt lsp and point your editor at it. Vim/Neovim syntax highlighting and editor setup ship in editors/.
| Feature | Details |
|---|---|
| keywords | as else fn import let loop match mod pub return trait type when where |
| types | inferred, with ADTs, records, and traits |
| literals | 42, 0xFF, 0b1010, 1e5, 1_000, "hi {x}" |
| branching | match only |
| mutability | none |
| errors | Result / Option / ? |
| concurrency | CSP with real parallelism |
| collections | [1, 2, 3] list, #{"k": "v"} map, #[1, 2] set |
| stdlib | small but exhaustive |
| tools | REPL, formatter, test runner, LSP |
New here? Read Why silt to see how it compares to Rust, Go, Gleam, and OCaml. Then start with Getting Started to install silt and walk through the essentials, and see the Language Guide for the complete reference and the Standard Library for every built-in.
Deeper dives: Concurrency, FFI (embedding silt in Rust), and Editor Setup.
A hosted version with an interactive playground is at silt-lang.com.
MIT