A statically and flexibly type-inferred scripting language
with just-in-time compilation
Brass is a statically typed, structurally typed scripting language with
flexible type inference. Its .cz source extension comes from copper and
zinc, the two metals that make up brass. A Brass program runs like a script,
but every function is fully type-checked just before it executes, and most
types are inferred rather than written. A just-in-time compiler runs the
program at native speed; an interpreter backs the REPL and WebAssembly.
Quick start:
curl -fsSL https://raw.githubusercontent.com/brass-cz/brass/refs/heads/main/scripts/install.sh | shThen,
export PATH="$HOME/.brass/bin:$PATH"
# Create new package
czpm new hello-world
cd hello-world
# Run
czpm run- Type inference everywhere. Flexible inference means most code needs no annotations; an unannotated function is checked at each call's argument types, just before it runs.
- Records and sum types under one
typekeyword. Methods are implemented withfun T.m(...): a firstselfparameter makes an instance method, otherwise it is static;Selfrefers to the type. - Structural subtyping with interface contracts.
type B: ArequiresBto provide every member ofA, checked at compile time; plain functions accept any value that has the members they use, with no inheritance. - Exhaustive pattern matching with
matchandif let. - Nullable and Result.
T?is narrowed byif;T!is aResult, witherror(x),expr!early-return propagation, and automaticOkwrapping.!on a nullable unwraps or returns null early (the return type gains?), and works at the top level and inmain, where a failure prints the error and exits. - Structural conversion.
T.from(v)for a record typeTyieldsT?: the record whenvstructurally has all ofT's fields, null otherwise. Soif let p = T.from(v)branches on the actual value. - Inferred parameter passing. An unannotated parameter is a shared
reference when the body only reads it and a private deep copy when the body
mutates it.
ref(T)(read-only reference),ref(mut(T))(writes through to the caller),mut(T)(mutable copy), andinfer(read-only copy) are explicit. Closures capture by mutable reference. - Tuples
[T, U], anonymous structural records, string interpolation, and both explicit and automatic numeric conversion. - A file-based module system where each file is a module and a leading
_marks a private name, plus a small standard library written in Brass itself. - Experimental concurrency.
spawn(f),with(cown, f), andsync()are the primitives; the compiler infers ownership, never the programmer. - Tooling: an interactive REPL and an LSP server (
czls).
Read the documentation: a step-by-step tutorial,
per-feature guides, language references, and a browser playground, built from
book/. Every language feature also has a runnable example in
examples/, each checked by cargo test.
The native build links LLVM statically for the JIT. Rather than require a
system LLVM, the ./x wrapper downloads a prebuilt LLVM into ./llvm/ on first
use and runs cargo with it on the path. Use ./x in place of cargo:
./x cargo build --release # downloads LLVM on first run -> ./target/release/brass
./x cargo test --workspace
./x cargo clippy --workspace --all-targetsLLVM is needed only by the JIT. An interpreter-only build needs no LLVM and uses
plain cargo:
cargo build -p brass_driver --no-default-features # interpreter only, no LLVMBrass also builds for wasm32-wasip1, where it runs through the interpreter.
brass path/to/file.cz # type-check and run (the LLVM JIT when available)
brass check path/to/file.cz # type-check only
brass repl path/to/file.cz # run a file through the interpreter
brass repl # interactive interpreter session
brass # no arguments: same interactive sessionA bare file argument runs on the JIT when it is built in, otherwise on the
interpreter. The JIT run checks each function before it executes but does not
wait for code the run never needs; --eager checks the whole program first, as
brass check does. Each module's top-level statements run in dependency order,
then main is called if defined. The embedded core library is an implicit
prelude; the shipped std library is imported as std.*. Everything after the
program file is passed through verbatim and is available from env.args();
driver flags such as --eager must come before the file:
brass --eager path/to/file.cz input.txt --verboseSequential execution is the tested, supported path. Two sharp edges are worth knowing:
- Concurrency is experimental. Scheduling is unstructured, so code that must
observe a spawned task's results calls
sync()first. Treat it as a preview. - The JIT and interpreter agree across the language's tested surface, but
concurrency (
spawn/sync/with) is native-only: the interpreter refuses it, so it needs the JIT. File I/O and the other nativestdmodules run on both back ends (brass replincluded); only the browser playground, which cannot load plugins, lacks them.
Mozilla Public License 2.0. See LICENSE.