LLM agents
Brass is unlikely to be present in a model’s training data. Give the prompt
below to an agent before asking it to write .cz files. It covers every major
language and library area, but keeps each API to an overview; the reference
pages remain authoritative for exact signatures and edge cases.
# Writing Brass
You are writing **Brass**, a statically typed, structurally typed scriptinglanguage with flexible type inference. Source files use `.cz`. Do not importsyntax from Rust, TypeScript, Python, or another language unless it is listedhere.
Always validate generated code with:
```bashbrass check file.cz```
A normal `brass file.cz` run checks the functions it needs and may leaveunused functions unchecked for that run. Only `brass check` gives a completewhole-program verdict.
## Syntax essentials
- Bindings are `const name = value` or mutable `let name = value`.- Comments are `//`, `#`, and nestable `/* ... */`. A leading shebang is a `#` comment. `/** ... */` directly above a `fun` or `type` is a doc comment.- Newlines separate statements. A line continues after a binary operator or before a leading `.` in a method chain.- Strings use escapes and `{expression}` interpolation. Escape a literal opening brace as `\{`.- Equality is `==`; `=` is assignment. Compound assignments include `+=`, `-=`, `*=`, `/=`, and `%=`.- `if` and `match` are expressions. Loops are `while condition` and `for item in iterable`; `break` and `continue` are supported.- Closures are `(x) -> expression` or `(x) -> { ...; return value }`.- Functions and types may be used before their definitions. Top-level bindings initialize in textual order and may not be read early.- Top-level statements run in module dependency order; `main()` runs last if present.
```brassconst greeting = "hello"let count = 0count += 1
fun twice(x: int32) -> int32 { return x * 2}
const values = [1, 2, 3].map((x) -> twice(x))println("{greeting}: {values}")```
## Values and types
- Primitive types are `int8/16/32/64`, `uint8/16/32/64`, `float32`, `float64`, `bool`, `string`, and `void`. There is no character type; one character is a `string`.- Integer literals default to `int32`, or `int64` when needed. Float literals default to `float64`. `len` returns `int64`.- `T[]` is a growable array, `T[n]` a fixed-length array, `[T, U]` a tuple, and `[lo..hi]` a half-open integer range.- `T?` is nullable. Narrow it with `if value`, `if !value { return ... }`, or `if let` before using it as `T`.- `T!` is a fallible Result. See Error handling below.- Most annotations are optional. An annotation constrains inference; it does not opt into type safety.- Numeric conversions are implicit only when value-preserving. Use `T.from(value)!` for checked narrowing or sign changes, `T.parse(text)!` for parsing, and `string.from(value)` for text.- Arrays and records are reference values. Assignment can share them.
## Functions, parameters, and closures
```brassfun distance(x: float64, y: float64) -> float64 { return sqrt(x * x + y * y)}
fun double(x) { return x + x}
println(double(2)) // 4println(double(2.5)) // 5.0```
- Return types are inferred when omitted. `void` means no value.- An unannotated function is generic: each call site checks the body against its own argument types, so `double` above serves `int32` and `float64` alike.- An unannotated parameter that is only read is passed by shared reference. If its body mutates the parameter, the callee receives a private deep copy.- Use `ref(T)` for an explicit immutable reference and `ref(mut(T))` when the callee must mutate the caller's value. `mut(T)` is an explicit mutable copy.- `infer` is a read-only copied value whose type is inferred; `infer[]` requires an array with an inferred element type.- `self` is a reference, so instance methods can mutate their receiver.- Closures capture live bindings by reference and may mutate them.- Closures cannot currently contain `error(...)` or apply `!` to a Result. Put fallible work in a named function and call it from the closure.- Mutual-recursion cycles should annotate their return types.
## Records, methods, sums, and interfaces
Record fields live inside `type`; method implementations live outside it.An instance method has `self` first. A method without `self` is static.
```brasstype Account = { owner: string balance: int64}
fun Account.new(owner: string) -> Account { return Self { owner: owner, balance: 0 }}
fun Account.deposit(self, amount: int64) { self.balance += amount}
type Stack = { type item // a type slot: a named type parameter, no storage items: Self.item[]}
type Names = Stack { item: string } // alias pinning the slot```
- Construct a record as `Type { field: value, ... }` and call methods as `value.method()` or `Type.static_method()`. There is no UFCS.- A member written as `name(params) -> Return` inside a type is a required method signature, not stored data.- Sum types use `type Name = | Variant { fields } | Empty`. Construct a value as `Name.Variant { ... }` and match it as `Variant { ... }`.- A match on a sum must cover every variant or use `_`.- `type Child: Parent` declares structural conformance. Fields and complete method signatures are checked, including parameter passing modes, contravariant parameter compatibility, and return types; implementations are not inherited. Multiple parents are allowed.- An unannotated function parameter is structural without an explicit interface: only the fields and methods used by the body are required.- `{ field: value }` is an anonymous record. It can dispatch to exactly one in-scope nominal record whose fields and requested method fit.- `T.from(value) -> T?` performs structural conversion to record type `T`.- `type name` inside a record declares a type slot: a type parameter other fields reference as `Self.name`. A slot is not stored and never appears in a construction literal; an open slot is fixed by the first value stored.- A refined alias such as `type Names = Stack { item: string }` or `type Counts = HashMap { key: string, value: int64 }` pins type slots without creating a new nominal type.
## Control flow and patterns
```brass norunfun describe(shape: Shape) -> string { return match shape { Circle { radius } => "circle {radius}", Rectangle { width, height } => "{width}x{height}", }}
if let Ok { value } = int32.parse("42") { println(value)}```
Patterns include variant patterns, record and array destructuring, literals,bindings, and `_`. `if let pattern = value` selects one pattern. A conditionalcan also probe structural member presence: `if value.method { ... }` asks atcompile time whether that type provides the member.
`if value: Type { ... } else ...` is a compile-time type test, decided percall site of the enclosing generic function; only the selected arm is checkedand compiled. Matching accepts the exact type or a subtype (structural recordsatisfaction, declared sum parents); it never converts (`int32` fails an`int64` test, `T` fails a `T?` test). `infer` in the tested type is a hole:pinned by what the arm itself requires, otherwise matching anything(`infer[]` = any array). Inside the arm the value keeps its own type.Unselected paths contribute nothing to an instance: each instance's returntype comes from its live path alone, and an `error(...)` on a pruned pathdoes not make that instance return a Result.
```brassfun length(val) { const bytes = if val: infer { // string: to_bytes pins the hole to_bytes(val) } else if val: uint8[] { val } else if val: infer[] { val } else { return error("unsupported") } return bytes.len()}```
## Error handling
- `Result` has `Ok { value }` and `Err { error }` variants. Standard failures carry an `Error` record with `value`, `location`, `frames`, and `display()`.- `error(payload)` creates an `Err`. Returning a plain value from a fallible function wraps it as `Ok`.- `result!` unwraps `Ok` or returns the `Err` from the enclosing named function. `nullable!` unwraps a value or returns `null` early.- `result.context(message)` adds trace context only on failure.- At top level or in `main`, an unhandled `!` prints the error and exits non-zero.- When matching a standard error, inspect `error.value`; interpolating `{error}` renders through `Error`'s `display` (the trace form). Use `{error.debug()}` for the raw record representation.
```brassfun load_number(text: string) -> int32! { const value = int32.parse(text).context("reading number")! if value < 0 { return error("negative value") } return value}
match load_number("12") { Ok { value } => println(value), Err { error } => println(error.display()),}```
## Collections and strings
- Growable arrays mutate with `push`, `pop`, `insert`, and `remove`. `map`, `filter`, `fold`, `each`, `slice`, `reverse`, and `sort` return new values except that `each` is for side effects.- `HashMap` is in the prelude: `HashMap.new()`, `from_pairs`, `set`, `get`, `get_or`, `contains_key`, `delete`, `size`, `is_empty`, `keys`, `values`, `pairs`, and `clear`. Iteration order is unspecified.- String APIs are `split`, `trim`, `starts_with`, `ends_with`, `find`, `replace`, `chars`, `to_upper`, `to_lower`, and `string[].join`.- String positions and `len(string)` are UTF-8 byte offsets. Strings have no direct indexing or public substring operation; use `chars`, `split`, `find`, or `replace`.- `print` and `println` take one value. Use interpolation for several values.- Every value has `v.debug() -> string`, its canonical rendering (`Debug` protocol; a string debugs quoted: `"a".debug()` is `"\"a\""`). Primitives also have `v.display()` (a string displays unquoted). `"{v}"` and `print`/`println` render through a type's own `display` method when it declares one, else through `debug`; `"{v.debug()}"` forces the debug form. `HashMap` declares `display`: JSON-style `"key": value` lines.
## Reflection and definite assignment
- `typeof(value)` is the static type name in value position, a type in type position, and a static receiver in `typeof(value).from(other)`.- `fields(record)` is valid only as a `for` iterable. The compiler unrolls the loop over declared fields; `record[field]` projects the current field.- `let value: T` may omit an initializer, but every path must assign the whole value or all default-constructible record fields before reading it.- A reflective `fun T.decode(self) -> infer!` specializes to the expected type at each call site. The target must be known there; external data selects values, not types.
## Modules and packages
- One file is one module; `geometry/vec.cz` is `geometry.vec`.- Import names with `import path.{ A, B }` or `import path.Name`. Import a module with `import path` and use its last segment as qualifier; rename it with `import path as local`.- Names and modules beginning with `_` are private.- `core` is embedded and its public names form the implicit prelude.- `std` is an installed package, imported as `std.fs`, `std.net`, etc. It is not embedded. A complete packaged toolchain binds it automatically.- Other imports are relative to the importing module. Declared package names take precedence; project files precede `BRASS_INCLUDE` roots.- `czpm new/init/run/check/fmt/lsp` manages projects and dependencies. `czpm check` checks every owned `.cz` file.
## Core API overview
No import is needed for core names:
- I/O: `print`, `println`, `input() -> string!`.- Arrays and strings: the methods listed above; `len(array_or_string)`.- Math: `abs`, `min`, `max`, `sqrt`, `floor`, `ceil`, `pow`.- Conversion: `to_bytes`, `to_text`, `T.from`, `T.parse`, `string.from`.- Errors: `Result`, `Error`, `error`, `context`.- Testing/defaults: `assert(condition, message?)`, `T.default()`.- Collections: `HashMap`.- Reflection/concurrency builtins: `fields`, `typeof`, `spawn`, `with`, `sync`.
The compiler-owned names `len`, `spawn`, `with`, `sync`, `error`, `fields`,and `typeof` cannot be redefined.
## Standard library overview
Import these with their complete `std.*` paths:
- `std.fs`: `File`, whole-file read/write, copy/move/remove files and trees, and recursive directory creation/removal. Paths accept strings or `Path`.- `std.path`: lexical `Path` parsing/joining/normalization plus filesystem queries, canonicalization, directory entries, and `_PATH` handling.- `std.process`: `Command`, `Child`, `Stdio`, `Output`, and `exit`. Prefer `child.output()` when stdout/stderr are piped to avoid a full-pipe deadlock.- `std.env`: command arguments, variables, path separator, current directory.- `std.hash`: MD5/SHA digests, HMAC, incremental `Hasher`, hex conversion, and constant-time `equal`. Use SHA-256 or stronger for security decisions; these fast hashes are not password hashes.- `std.regex`: `Regex`, `Match`, `Group`, and `escape`. Patterns are linear-time and have no lookaround or backreferences. Brass strings are not raw: double `\\` and escape an interpolation brace as `\{`.- `std.semver`: parse, construct, compare, render, and sort semantic versions; build metadata does not affect precedence.- `std.net`: `Tcp`, `TcpListener`, `Udp`, `Datagram`, and TLS client `TlsStream`. TCP is a byte stream, so frame messages or read in a loop.- `std.url`: RFC 3986 `URI` parsing, reference resolution, path segments, and query pairs. Supporting imports are `std.url.authority`, `std.url.query`, `std.url.percent`, `std.url.validate`, `std.url.charset`, and `std.url.text`.- `std.http`: HTTP/HTTPS `fetch`, clients, requests, responses, and headers. It handles chunked, `Content-Length`, and connection-close response bodies; other transfer codings are errors.- `std.data.json`: `JsonValue` parse/stringify/accessors and typed record decoding with `into()`; typed-array decoding is not supported.- `std.data.toml`: `TomlValue` parse/stringify/accessors and scalar/record decoding; decode arrays element by element.
Native-backed `std` modules work in installed native and interpreter builds,but not in the browser playground.
## Concurrency
Concurrency is native-runtime only:
- `spawn(() -> { ... })` starts a task and is legal only inside a function; its closure takes no arguments and returns `void` (or remains open).- `with(shared, (value) -> { ... })` acquires compiler-managed shared state.- `sync()` waits for spawned work before its results are observed.
Ownership is inferred; there is no user-facing move/freeze/cown syntax. Keepblocking operations out of a shared acquisition and call `sync()` beforereading results that tasks mutate.
## Execution, caching, and tools
- `brass file.cz`: demand-driven check, native compile, and run.- `brass check file.cz`: complete check without execution; use in CI.- `brass --eager file.cz`: complete check before anything runs, then run; native compilation is first-use in both modes.- `brass repl`: eager interpreter session. Concurrency is unavailable.- `czfmt --write file.cz`: format source. `czls`: language server.- `.czcache` may be a full check or a partial normal-run snapshot; file existence alone is not a complete verdict. `BRASS_CACHE=off` disables analysis caches.
## Frequent generation mistakes
- Do not write explicit generic parameters such as `<T>`.- Do not use `pub`, `impl`, `trait`, `loop`, `defer`, `open(...)`, raw strings, string indexing, or language features not listed here.- Use `Type.Variant`, `Type.static_method`, and `value.instance_method`.- Use value-preserving implicit numeric conversions only; otherwise call `T.from(value)!`.- Narrow `T?` before use and match Results as `Ok { value }` / `Err { error }`.- A block-bodied function or closure needs `return` to produce a value.- Do not put fallible propagation directly in a closure.- Compile a `Regex` once rather than inside a loop.- Run `brass check` after every generated edit.
## Complete example
```brasstype Shape = | Circle { radius: float64 } | Rectangle { width: float64, height: float64 }
fun area(shape: Shape) -> float64 { return match shape { Circle { radius } => 3.14159 * radius * radius, Rectangle { width, height } => width * height, }}
fun main() { const shapes = [ Shape.Circle { radius: 2.0 }, Shape.Rectangle { width: 3.0, height: 4.0 }, ] const total = shapes.map((shape) -> area(shape)).fold( 0.0, (sum, value) -> sum + value, ) println("total area = {total}")}```