Quickstart
Build the Sakura Lang compiler from source, then check, test and run a first program.
Pre-alpha: build from source
The compiler runs the core language and substantial M3 functionality on JavaScript, including contracts, std handlers, permissions, scaffolding, LSP and MCP. No release or installer is published. See the roadmap for remaining M3 gates and planned M4/M5 work.
Install from source
You need rustup and Node.js (sakura run and sakura test execute the generated JavaScript with node; CI uses Node 26). The repository pins its Rust toolchain in compiler/rust-toolchain.toml, so rustup fetches the right version on first build.
git clone https://github.com/icantcodefyi/sakura-lang
cd sakura-lang/compiler
cargo install --locked --path crates/sakura_cli
sakura --versionsakura 0.0.0The standard library is embedded in the binary, so sakura works from any directory. To try it without installing, run cargo run -p sakura_cli -- <command> inside compiler/.
Run an example
compiler/examples/ holds the design's example programs as projects. All twelve pass the configured run/test/check modes listed on the roadmap:
sakura run compiler/examples/counter
sakura test compiler/examples/typed_errors1. milk
2. eggs
1. breadcounter is a user-defined effect whose handler keeps state; typed_errors recovers from typed failures with try/catch. The examples README records each mode; agent_loop and secrets currently have check-only coverage.
Create a project
Run sakura new cli hello to scaffold a project (lib, agent, and api templates also exist), or write the two files below by hand. A project is a sakura.toml manifest and a src/ directory, and one file is one module.
[package]
name = "hello"
version = "0.1.0"
edition = "2026"
[permissions][permissions] is the ceiling on what the program may do (design §6). This program only prints, so it is empty. sakura run enforces startup grants and passes the table as SAKURA_PERMISSIONS for host-handler enforcement. Missing required root authority exits with 75.
/// Build a greeting for a person.
fn greet(name: String) -> String { "Hello, {name}" }
pub fn main() -> Unit ! {Console} { Console.print(greet("world")) }
test "greets by name" { expect_eq(greet("Ada"), "Hello, Ada") }
property "always starts with Hello"(name: String) {
expect(String.starts_with(greet(name), "Hello, "))
}greet has no !, so it is pure. main declares Console, the only effect the program performs. This is the canonical layout: sakura fmt keeps a short body on its signature's line.
Check, test and run
Run these from hello/. With no path, each command uses src/.
sakura checkno problemssakura check parses, resolves names, checks types and effects, and checks that every match is exhaustive. It generates no code, and it accepts typed holes.
sakura testtest greets by name ... ok
test always starts with Hello ... ok
2 passed, 0 failed, 0 skipped (seed 0)sakura test runs every test and property block under deterministic handlers (a fixed clock, a seeded random source, captured console output) with the network denied. --seed 7 changes the property inputs, --filter greets runs matching tests only, and --json writes one record per test.
sakura runHello, worldsakura run checks the program, compiles it to JavaScript under target/sakura/js/, and runs main with node. Arguments after -- go to the program. The exit code is the program's: 0 for success, 1 when main fails with an uncaught typed error, 70 for a defect such as integer overflow, with one JSON defect record on stderr.
sakura fmt rewrites files in the one canonical format; sakura fmt --check changes nothing and exits 1 if a file is not formatted.
Check it, as an agent would
Suppose greet starts printing without declaring it:
/// Build a greeting for a person.
fn greet(name: String) -> String {
Console.print("greeting {name}")
"Hello, {name}"
}
pub fn main() -> Unit ! {Console} { Console.print(greet("world")) }error[V3001]: function `greet` performs `Console`, which its signature does not allow (allowed: `{}`)
┌─ src/main.sakura:2:4
│
2 │ fn greet(name: String) -> String {
│ ^^^^^
3 │ Console.print("greeting {name}")
│ -------------------------------- `Console.print` performs `Console` here
│
= expected: {}
= found: {Console}
= help: Add `Console` to the signature
= help: Handle the effect locally with `with`
= see `sakura explain V3001`
1 errorsakura check --json writes the same error as JSON Lines, one record per line, followed by a summary record. The diagnostic record, pretty-printed with hashes shortened:
{
"schema_version": "1.0",
"kind": "diagnostic",
"id": "d:e8f9a0e1d729d3d1",
"code": "V3001",
"severity": "error",
"phase": "effect",
"message": "function `greet` performs `Console`, which its signature does not allow (allowed: `{}`)",
"snapshot": "sha256:9470da24…",
"symbol_id": null,
"spans": [
{ "uri": "file:///…/hello/src/main.sakura", "content_hash": "sha256:f1ee9f10…",
"start_byte": 38, "end_byte": 43, "role": "primary" },
{ "uri": "file:///…/hello/src/main.sakura", "content_hash": "sha256:f1ee9f10…",
"start_byte": 72, "end_byte": 104, "role": "cause",
"label": "`Console.print` performs `Console` here" }
],
"related_ids": [],
"expected": { "render": "{}", "row": { "labels": [], "tail": null } },
"actual": { "render": "{Console}", "row": { "labels": [{ "effect": "std::Console", "args": [] }], "tail": null } },
"obligation": null,
"counterexample": null,
"explanation_uri": "sakura://explain/V3001",
"fixes": [
{ "id": "f:e8f9a0e1d729d3d1.0", "title": "Add `Console` to the signature",
"applicability": "suggested", "impact": "interface_change",
"edits": [{ "span": { "uri": "file:///…/hello/src/main.sakura", "content_hash": "sha256:f1ee9f10…",
"start_byte": 67, "end_byte": 67, "role": "primary" },
"replacement": " ! {Console}" }] },
{ "id": "f:e8f9a0e1d729d3d1.1", "title": "Handle the effect locally with `with`",
"applicability": "incomplete", "impact": "local",
"edits": [/* wraps the body in `with ?handler { … }` */] }
]
}Spans are zero-based, half-open UTF-8 byte offsets, and an edit applies only while content_hash still matches. The first fix changes the function's interface, so it is suggested, never machine_applicable: an agent must not widen a signature silently. sakura explain V3001 prints the rationale with a wrong and a fixed program. See Diagnostics for the format.
A typed hole is how an agent asks the compiler what fits:
fn unfinished(x: Int) -> Int { ?result }hole ?result: expected Int, effects {}; locals [x: Int]; candidates [x, Int.abs(?i), Int.clamp(?i, ?lo, ?hi), …]sakura check accepts holes; sakura build and sakura run reject them with V2050. See Typed holes.
Commands
| Command | Today |
|---|---|
sakura check [--json] [paths] | Parse, resolve, type and effect check, exhaustiveness. Holes allowed |
sakura build [paths] | Check, then generate JavaScript. Holes rejected (V2050) |
sakura run [path] [-- args] | Build and run main with node |
sakura test [--json] [--seed N] [--filter text] [--update-snapshots] | Run tests and properties with deterministic handlers |
sakura fmt [--check] [paths] | Canonical format. There are no options |
sakura explain V3001 | Rationale, causes and repairs for a diagnostic code |
sakura prompt | Print the language guide for agents (llms.txt) |
Also available: sakura new, sakura init, sakura fix, sakura check --frozen-specs all [--since rev], sakura lsp, and sakura mcp serve. sakura doc and trust-site sakura audit are M3 completion work. Package commands remain planned; verification/WASM are M4 and native is M5.
Next
Start the Language Tour.