This is the abridged developer documentation for Brine # Brine documentation > Learn Brine, a compiled systems language with Python-like syntax, compile-time proofs, and race-free parallelism on every core, the GPU and many machines. Brine is a compiled systems language for programs that need to be **fast, parallel and correct at the same time**. You write code that reads like Python. The compiler turns it into native code, proves what it can about it (array bounds, overflow, your own contracts), and spreads it across every core you have, or onto a GPU, with results that are the same bits on any machine. ```brine fn index_of(xs: [int], target: int) -> int: ensures -1 <= result < len(xs) # proven by the compiler var lo = 0 var hi = len(xs) while lo < hi: invariant 0 <= lo <= hi <= len(xs) let mid = lo + (hi - lo) / 2 if xs[mid] == target: # bounds check proven: removed return mid if xs[mid] < target: lo = mid + 1 else: hi = mid return -1 fn main(): let squares = par [i * i for i in 0..1_000_000] # runs on all cores let total = par sum(float(x) for x in squares) # same bits on any machine print(index_of(squares, 144), total) ``` ## Start here [Section titled “Start here”](#start-here) [Install brine](/getting-started/installation/)One command on Linux, macOS and Windows. [Your first project](/getting-started/first-project/)brine new, run, check and test in five minutes. [A tour of brine](/getting-started/tour/)The whole language, one example at a time. [Coming from Python](/getting-started/from-python/)What's the same, what's different, and why. ## What makes Brine different [Section titled “What makes Brine different”](#what-makes-brine-different) Parallel by syntax `par for`, `par [...]`, `par sum(...)` and `par(a, b)` use every core. A data race is a compile error. [Parallelism →](/parallelism/overview/) Rules can't be broken `requires`, `ensures` and `invariant` are proven at compile time. There is no `unsafe` and no flag to turn checks off. [Proofs →](/proofs/overview/) GPUs, no annotations Big `par` constructs move to an NVIDIA or Apple GPU on their own when that’s faster, with the same result bits. [GPU offload →](/parallelism/gpu/) Made for agents too Error codes, `--json` everywhere, `brine explain`, `brine guide`, and every page of these docs as Markdown. [For agents →](/agents/overview/) ## For coding agents [Section titled “For coding agents”](#for-coding-agents) Point your agent at [`/llms.txt`](/llms.txt) for an index of these docs, or [`/llms-full.txt`](/llms-full.txt) for all of them in one file. Every page is also available as Markdown: add `.md` to its URL (this page is [`/index.md`](/index.md)). Inside a project, `brine guide` prints the whole language on one page. # Your first project > Create a Brine project with brine new, then run, check, test and format it, with the files it creates and the output of each command. `brine new` creates a project with a program, a test, and an `AGENTS.md` for coding agents. From then on the loop is: edit, `brine check`, `brine run`, `brine test`. This page walks through each command with the output it prints. You need Brine [installed](/getting-started/installation/) first. ## Create and run a project [Section titled “Create and run a project”](#create-and-run-a-project) 1. Create the project: ```sh brine new hello ``` 2. Go into it and run it: ```sh cd hello brine run ``` ```text Built hello v0.1.0 [dev] in 2.04s Running target/dev/hello.exe Hello, brine! [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] ``` The first build also compiles Brine’s runtime, so it takes a couple of seconds. Later builds reuse it. (On Linux and macOS the binary is `target/dev/hello`, with no `.exe`.) 3. Run it again without changing anything: ```sh brine run ``` ```text Fresh hello v0.1.0 [dev] in 0.02s Running target/dev/hello.exe Hello, brine! [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] ``` `Fresh` means nothing changed, so Brine ran the binary it already had. The `Built`, `Fresh` and `Running` status lines go to stderr; your program’s output goes to stdout. Add `-q` to hide the status lines. ## What’s in a project [Section titled “What’s in a project”](#whats-in-a-project) ```text hello/ ├── .gitignore ├── AGENTS.md ├── README.md ├── brine.toml └── src/ └── main.br ``` `brine new` also runs `git init` in the folder, unless it’s already inside a git repository. To turn an existing folder into a project, run `brine init` in it; it creates the same files and skips any that already exist. ### brine.toml [Section titled “brine.toml”](#brinetoml) brine.toml ```toml [package] name = "hello" version = "0.1.0" ``` The file marks the project root. brine commands look for it in the current folder and its parents, so you can run them from anywhere inside the project. The program starts at `src/main.br`. See [Projects](/tooling/projects/) for the other settings, such as `[check] typed = true`. ### src/main.br [Section titled “src/main.br”](#srcmainbr) src/main.br ```brine # Welcome to brine! Run this program with `brine run`. fn greet(name: str) -> str: return f"Hello, {name}!" fn main(): print(greet("brine")) # parallel by default: this runs across all your cores let squares = par [i * i for i in 0..10] print(squares) test "greeting": assert greet("you") == "Hello, you!" ``` `fn main()` is where the program starts. `par [...]` builds the list of squares on every core. The `test` block at the end is a test, which `brine run` ignores and `brine test` runs. ### AGENTS.md [Section titled “AGENTS.md”](#agentsmd) Instructions for coding agents working in the project: the commands to run and the rules that most often trip up someone who knows Python. Many coding agents read `AGENTS.md` on their own when they start work in a folder. See [AGENTS.md and common mistakes](/agents/agents-md/) for its full text and an explanation of each rule. ### .gitignore and README.md [Section titled “.gitignore and README.md”](#gitignore-and-readmemd) `.gitignore` holds one line, `/target`: builds and the build cache go in `target/` (`target/dev/`, `target/release/`, `target/cache/`). `README.md` is a short stub for you to fill in. ## Check, run, test, format [Section titled “Check, run, test, format”](#check-run-test-format) | Command | What it does | | ------------- | ------------------------------------------------------------------------- | | `brine check` | type-check and prove, without building; the fastest feedback | | `brine run` | build `src/main.br` and run it | | `brine test` | build and run every `test "...":` block, in parallel | | `brine fmt` | rewrite every source file in the standard format | | `brine build` | build a binary into `target/dev/` (or `target/release/` with `--release`) | ### brine check [Section titled “brine check”](#brine-check) ```sh brine check ``` ```text Checked hello in 0.00s — 0 runtime checks proven unnecessary, 1 remain (see --report) ``` `check` parses, type-checks, and runs the prover. It doesn’t build anything, so it’s the command to run after every edit. The second half of the line counts the safety checks (array bounds, overflow, division by zero) that the prover removed and those that stay in the program as runtime checks. `--report` lists the ones that remain: ```sh brine check --report ``` ```text src/main.br:10:24: overflow check `i * i` Checked hello in 0.00s — 0 runtime checks proven unnecessary, 1 remain ``` The prover handles linear arithmetic, so it can’t show that `i * i` stays in range; the program checks it at runtime instead. A remaining check is not an error. See [Runtime checks](/proofs/runtime-checks/). ### brine test [Section titled “brine test”](#brine-test) ```sh brine test ``` ```text Built tests [dev] in 0.10s test greeting ... ok test result: ok. 1 passed; 0 failed; finished in 0.02s ``` Each test runs in its own process. `brine test shout` runs only the tests whose names contain `shout`. See [Testing](/language/testing/). ### brine fmt [Section titled “brine fmt”](#brine-fmt) ```sh brine fmt ``` ```text 1 file checked, 0 reformatted ``` There’s one standard format (4-space indentation, spaces around operators), so code always looks the same. `brine fmt --check` changes nothing and exits with status 1 if a file needs formatting, for CI. See [Formatting](/tooling/formatting/). ## The edit-run loop [Section titled “The edit-run loop”](#the-edit-run-loop) Here is one round of changes: add a function and a test, make a mistake, and fix it. 1. Add a `shout` function and a test for it to `src/main.br`. Write it carelessly, and pass it a number by mistake: src/main.br ```brine # Welcome to brine! Run this program with `brine run`. fn greet(name: str) -> str: return f"Hello, {name}!" fn shout(name:str)->str: return greet(name).upper() fn main(): print(greet("brine")) print(shout(42)) # parallel by default: this runs across all your cores let squares = par [i * i for i in 0..10] print(squares) test "greeting": assert greet("you") == "Hello, you!" test "shouting": assert shout("you") == "HELLO, YOU" ``` 2. Check it: ```sh brine check ``` ```text error[E0301]: mismatched types --> src/main.br:11:17 | 11 | print(shout(42)) | ^^ expected `str`, found `int` | = help: convert with `str(...)` or use an f-string like f"{x}" = run `brine explain E0301` for more about this error error: could not compile due to 1 error ``` Every error has a code, the exact place, and a hint. `brine explain E0301` explains the code at length. Change the call to `shout("brine")`. 3. Format the file: ```sh brine fmt ``` ```text Formatted src/main.br 1 file checked, 1 reformatted ``` `fn shout(name:str)->str:` is now `fn shout(name: str) -> str:`, and its body is indented by 4 spaces. 4. Run the tests: ```sh brine test ``` ```text Built tests [dev] in 0.09s test greeting ... ok test shouting ... FAILED ---- shouting ---- panic: assertion failed: shout("you") == "HELLO, YOU" --> src/main.br:21:5 test result: FAILED. 1 passed; 1 failed; finished in 0.03s ``` The expected string is missing its `!`. Fix the test to compare with `"HELLO, YOU!"` and run `brine test` again: ```text Built tests [dev] in 0.09s test greeting ... ok test shouting ... ok test result: ok. 2 passed; 0 failed; finished in 0.02s ``` 5. Run the program: ```sh brine run ``` ```text Built hello v0.1.0 [dev] in 0.09s Running target/dev/hello.exe Hello, brine! HELLO, BRINE! [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] ``` ## Single files and release builds [Section titled “Single files and release builds”](#single-files-and-release-builds) You don’t need a project to try something. Every command also takes a file: ```sh brine run scratch.br brine check scratch.br brine test scratch.br brine run scratch.br -- first second # arguments after `--` go to the program ``` `brine run --release` and `brine build --release` optimize through the C compiler. They take longer to build and run faster: ```sh brine build --release ``` ```text Built hello v0.1.0 [release] in 2.59s Output target/release/hello.exe ``` ## Next [Section titled “Next”](#next) * [A tour of Brine](/getting-started/tour/): the rest of the language in one page. * [Coming from Python](/getting-started/from-python/): what’s the same and what isn’t. * [The brine command](/tooling/cli/): every command and flag. # Coming from Python > What carries over from Python to Brine, and every difference that trips Python programmers up, with the compiler error each one gives and the fix. Brine reads like Python, so most of what you know carries over: indentation, `for` loops, comprehensions, f-strings, slices. The differences come from Brine being compiled, statically typed and safe to run in parallel. Most are compile errors with a code and a fix, so the compiler tells you when a Python habit doesn’t apply. The ones to remember: declare variables with `let` or `var`, convert numbers explicitly, values are copied, and failures are `Result` values, not exceptions. ## What carries over [Section titled “What carries over”](#what-carries-over) ```brine fn main(): let words = ["brine", "is", "a", "language"] let lengths = [len(w) for w in words if len(w) > 1] # comprehension for i, w in enumerate(words): # enumerate, unpacking if i == 0: print(f"{w:>8} first") # f-string with a format elif len(w) < 3: continue else: print(f"{w:>8} {len(w)}") print(lengths, words[1:3], words[:2]) # slices print("is" in words, "x" not in words, 0 <= len(words) < 10) print(" ".join(words).upper(), "a,b".split(","), " pad ".strip()) let (q, r) = (17 / 5, 17 % 5) # tuple unpacking print(q, r, 'single quotes work too', max(lengths), sum(lengths)) for i in range(10, 0, -3): print(i, end=" ") print() ``` Output ```text brine first language 8 [5, 2, 8] ["is", "a"] ["brine", "is"] true true true BRINE IS A LANGUAGE ["a", "b"] pad 3 2 single quotes work too 8 15 10 7 4 1 ``` Also the same: `#` comments, `while`, `break`, `continue`, `pass`, `and`, `or`, `not`, `a if cond else b`, `match`/`case`, `print(..., sep=, end=)`, `str(x)`, `int("42")`, `float("2.5")`, and most string methods. Files end in `.br` and use 4-space indentation (tabs are an error). ## At a glance [Section titled “At a glance”](#at-a-glance) | Python | Brine | Notes | | ------------------------------ | ---------------------------------------- | -------------------------------------------------------------------------------- | | `def f(x):` | `fn f(x: int) -> int:` | parameter and return types are required ([E0110](/reference/errors/#e0110)) | | `class Point:` | `struct Point:` | fields with types, methods inside ([E0110](/reference/errors/#e0110)) | | `x = 1` | `let x = 1` / `var x = 1` | `let` can’t change; `var` can | | `x = 2` (new name, same scope) | a new name | no shadowing ([E0207](/reference/errors/#e0207)) | | `True`, `False`, `None` | `true`, `false`, `None` | `None` needs a known `Option` type ([E0332](/reference/errors/#e0332)) | | `n * 0.5` with `n: int` | `float(n) * 0.5` | no implicit conversions ([E0305](/reference/errors/#e0305)) | | `7 // 2` | `7 / 2` | integer `/` already rounds down; `//` is [E0105](/reference/errors/#e0105) | | `7 / 2` → `3.5` | `float(7) / float(2)` | | | `2 ** 10` | `pow(2.0, 10.0)` | no `**` operator; `pow` takes floats | | `xs[-1]` | `xs[len(xs) - 1]` | no negative indexes (panics at runtime) | | `b = a` (same list) | `b = a` (a copy) | values, not shared references | | `f(xs)` changes `xs` | `f(ref xs)` | the function declares `ref xs: [int]` | | `xs.append(x)` | `xs.push(x)` | [E0206](/reference/errors/#e0206) suggests the name | | `sorted(xs)` | `var ys = xs` then `ys.sort()` | `sort` is in place | | `"ab" * 3` | `"ab".repeat(3)` | | | `d[k]` raises `KeyError` | `d[k]` panics | test `k in d`, or use `d.get(k)` | | `if xs:` | `if len(xs) > 0:` | conditions must be `bool` ([E0307](/reference/errors/#e0307)) | | `x is None` | `x == None` or `x.is_none()` | | | `try` / `except` | `Result`, `match`, `?` | no exceptions | | `else if` | `elif` | [E0114](/reference/errors/#e0114), with a fix | | `a, b = b, a` | `let (x, y) = (b, a)` | no multiple assignment to existing variables | | `lambda x: x + 1` | a named `fn` | no lambdas | | `def f(x, y=0)` | two functions, or pass `y` | no default arguments | | `f(y=1, x=2)` | `f(x=2, y=1)` | keyword arguments must be in parameter order ([E0302](/reference/errors/#e0302)) | | `{k: v for ...}` | a loop that fills a `var m: {K: V} = {}` | list comprehensions only | | `import math`; `math.sqrt(x)` | `sqrt(x)` | math functions are built-ins; `import` loads your own `.br` files | | global variables | `const` at the top level, or pass values | statements go inside functions | | `"""multi-line"""` | `"line 1\nline 2"` | strings end on the line they start | | `xs[::2]` | `[xs[i] for i in range(0, len(xs), 2)]` | slices have no step | ## Declaring variables: let and var [Section titled “Declaring variables: let and var”](#declaring-variables-let-and-var) Every variable is declared once, with `let` (can’t change) or `var` (can). Assigning to a name that was never declared is an error, and so is changing a `let`: ```brine fn main(): let count = 0 for i in 0..3: count += i print(count) ``` ```text error[E0401]: cannot assign to `count` because it was declared with `let` --> letlet.br:4:9 | 4 | count += i | ^^^^^ immutable 2 | let count = 0 | ----- declared here | = help: declare it with `var count = ...` to make it mutable = run `brine explain E0401` for more about this error error: could not compile due to 1 error ``` ```brine fn main(): total = 0 print(total) ``` ```text error[E0201]: cannot assign to undefined variable `total` --> varundecl.br:2:5 | 2 | total = 0 | ^^^^^ not declared | = help: declare it first: `var total = ...` = run `brine explain E0201` for more about this error error[E0201]: cannot find value `total` --> varundecl.br:3:11 | 3 | print(total) | ^^^^^ not found in this scope = run `brine explain E0201` for more about this error error: could not compile due to 2 errors ``` A name can be declared only once per function, even in an inner block. Python lets you rebind `x = int(x)`; in Brine, pick a new name: ```brine fn main(): let x = 1 let x = 2 print(x) ``` ```text error[E0207]: `x` is already defined in this function --> shadow.br:3:9 | 3 | let x = 2 | ^ defined again here 2 | let x = 1 | - first defined here | = help: brine doesn't allow shadowing; pick a new name, or declare it with `var` and assign to it = run `brine explain E0207` for more about this error error: could not compile due to 1 error ``` Why: a name that means one thing for the whole function is easier to read and lets the prover reason about it. See [Bindings](/language/bindings/). ## Static types [Section titled “Static types”](#static-types) Local types are inferred, but function parameters and return values need types, and a variable keeps its type: ```brine fn double(x): return x * 2 fn main(): print(double(2)) ``` ```text error[E0110]: parameter `x` needs a type --> noparamtype.br:1:12 | 1 | fn double(x): | ^ expected `: type` | = help: write `x: int` (or another type) = run `brine explain E0110` for more about this error error: could not compile due to 1 error ``` `int` is a 64-bit integer, not Python’s unlimited one. Overflow stops the program with a panic instead of giving a wrong answer, and a literal that doesn’t fit is a compile error ([E0315](/reference/errors/#e0315)). Use `i128` or `u128` for bigger numbers. See [Values and types](/language/values-and-types/). ## No implicit conversions [Section titled “No implicit conversions”](#no-implicit-conversions) An `int` and a `float` never mix on their own, not even in `==`. Convert one side: ```brine fn mean(xs: [float]) -> float: return sum(x for x in xs) / len(xs) fn main(): print(mean([1.0, 2.0, 3.0])) ``` ```text error[E0305]: cannot divide `float` and `int` --> conv.br:2:12 | 2 | return sum(x for x in xs) / len(xs) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `/` with mismatched operands | = help: brine never converts numbers implicitly; use `float(x)` or `int(x)` = run `brine explain E0305` for more about this error error: could not compile due to 1 error ``` ```brine fn mean(xs: [float]) -> float: return sum(x for x in xs) / float(len(xs)) fn main(): print(mean([1.0, 2.0, 3.0])) ``` The same goes for strings: `"n = " + n` is an error; write `f"n = {n}"` or `"n = " + str(n)`. Literals adapt to their context, so `let f: float = 1` and `let b: u8 = 200` are fine. ## Integer division rounds down [Section titled “Integer division rounds down”](#integer-division-rounds-down) `/` on two integers gives an integer, rounded down like Python’s `//`. `%` takes the sign of the right side, as in Python. Python’s `//` is an error that points you to `/`: ```brine fn main(): print(7 / 2, -7 / 2, -7 % 2, float(7) / float(2)) ``` Output ```text 3 -4 1 3.5 ``` ```brine fn main(): print(7 // 2) ``` ```text error[E0105]: `//` is not an operator in brine --> floordiv.br:2:13 | 2 | print(7 // 2) | ^^ use `/` | = note: `/` on integers already rounds down like Python's `//`; comments start with `#` = run `brine explain E0105` for more about this error error: could not compile due to 1 error ``` ## Values are copied [Section titled “Values are copied”](#values-are-copied) In Python, `b = a` makes two names for one list. In Brine it makes a copy, and so does passing an argument. Copies are cheap (arrays and strings are shared until one side changes), and a change through one name never shows up in another: ```brine fn main(): let a = [1, 2, 3] var b = a b.push(4) var grid = [[0] * 3] * 2 # two separate rows, unlike Python grid[0][0] = 1 print(a, b, grid) ``` Output ```text [1, 2, 3] [1, 2, 3, 4] [[1, 0, 0], [0, 0, 0]] ``` Parameters are read-only. To change the caller’s variable, the function declares a `ref` parameter and the caller writes `ref` as well, so you can see it at the call: ```brine fn add_item(xs: [int]): xs.push(4) fn main(): var xs = [1, 2, 3] add_item(xs) print(xs) ``` ```text error[E0403]: cannot push to parameter `xs` --> alias.br:2:5 | 2 | xs.push(4) | ^^ parameters are read-only 1 | fn add_item(xs: [int]): | -- declared here | = help: to change a local copy, declare it `mut xs: ...`; to change the caller's variable, declare it `ref xs: ...` and call with `ref` = run `brine explain E0403` for more about this error error: could not compile due to 1 error ``` ```brine fn add_item(ref xs: [int]): xs.push(4) fn main(): var xs = [1, 2, 3] add_item(ref xs) print(xs) ``` Why: with no hidden sharing, two `par` iterations can’t change the same list behind your back, which is what makes [race freedom](/parallelism/race-freedom/) checkable. See [Functions](/language/functions/). ## No negative indexes [Section titled “No negative indexes”](#no-negative-indexes) `xs[-1]` is not the last element. It’s an out-of-bounds index, and the program stops when it reaches it: ```brine fn main(): let xs = [1, 2, 3] print(xs[-1]) ``` Output ```text panic: index -1 is out of bounds for an array of length 3 --> negidx.br:3:11 ``` Write `xs[len(xs) - 1]`. Slices don’t take negative bounds either: `xs[len(xs) - 2:]` is the last two elements. ## Strings index by bytes [Section titled “Strings index by bytes”](#strings-index-by-bytes) Strings are UTF-8. `len(s)`, `s[i]` and slices count bytes, not characters. For ASCII text that’s the same thing. To walk characters, use `for c in s` or `s.chars()`: ```brine fn main(): let s = "héllo" print(len(s), len(s.chars()), s[0], s[1:3]) for c in s: print(c, end=" ") print() ``` Output ```text 6 5 h é h é l l o ``` `é` takes two bytes, so `s[1:3]` is `"é"` and `len(s)` is 6. Strings can’t be changed in place (`s[0] = "x"` is [E0404](/reference/errors/#e0404)); build a new one. See [Strings](/language/strings/). ## Missing map keys panic [Section titled “Missing map keys panic”](#missing-map-keys-panic) Reading a key that isn’t there stops the program. There’s no `KeyError` to catch. Test first, or use `get`, which returns an `Option`: ```brine fn main(): let ages = {"ann": 31} if "bob" in ages: print(ages["bob"]) print(ages.get("bob"), ages.get("bob", 0), ages.get("ann")) ``` Output ```text None 0 Some(31) ``` Without the check, `ages["bob"]` prints `panic: key "bob" is not in the map`. See [Maps](/language/maps/). ## match covers every case [Section titled “match covers every case”](#match-covers-every-case) `match` looks like Python’s, but it must handle every possible value. A missing case is a compile error that names it: ```brine enum Color: Red Green Blue fn name(c: Color) -> str: match c: case Color.Red: return "red" case Color.Green: return "green" fn main(): print(name(Color.Blue)) ``` ```text error[E0320]: this `match` doesn't handle every possible value --> nomatch.br:7:11 | 7 | match c: | ^ of type `Color` | = note: not handled: `Color.Blue` = help: add `case Color.Blue:`, or a catch-all `case _:` = run `brine explain E0320` for more about this error error: could not compile due to 1 error ``` See [Enums and match](/language/enums-and-match/). ## No exceptions: Result and ? [Section titled “No exceptions: Result and ?”](#no-exceptions-result-and-) There is no `try`, `except` or `raise`. A function that can fail returns `Result[T, E]`, and the caller decides what to do. `x?` returns the error to the caller, like an exception propagating, but visible in the code and in the function’s type: ```brine fn parse_bit(s: str) -> Result[int, str]: if s == "0": return Ok(0) if s == "1": return Ok(1) return Err(f"not a bit: {s}") fn parse_pair(a: str, b: str) -> Result[int, str]: let hi = parse_bit(a)? let lo = parse_bit(b)? return Ok(hi * 2 + lo) fn main(): match parse_pair("1", "x"): case Ok(v): print(v) case Err(e): print("error:", e) ``` Output ```text error: not a bit: x ``` File and process functions return `Result` too: `read_file(path)` is a `Result[str, str]`, never a crash. `panic(msg)` is for bugs, and can’t be caught. `main` may return `Result[(), str]`; an `Err` prints `error: ...` and exits with status 1. See [Error handling](/language/error-handling/). ## None needs a type [Section titled “None needs a type”](#none-needs-a-type) `None` is a value of `Option[T]`, so Brine needs to know the `T`. There’s no `null`: ```brine fn main(): let x = None print(x) ``` ```text error[E0332]: can't infer which `Option` type `None` belongs to --> none.br:2:13 | 2 | let x = None | ^^^^ type unknown here | = help: add a type annotation, e.g. `let x: Option[int] = None` = run `brine explain E0332` for more about this error error: could not compile due to 1 error ``` ```brine fn main(): let x: Option[int] = None print(x == None, x.is_none(), x.unwrap_or(5)) ``` ## Conditions must be bool [Section titled “Conditions must be bool”](#conditions-must-be-bool) There’s no truthiness. An empty list, `0` or `""` isn’t false; write the test you mean: ```brine fn main(): let xs = [1] if xs: print("non-empty") ``` ```text error[E0307]: condition must be a `bool`, found `[int]` --> truthy.br:3:8 | 3 | if xs: | ^^ not a bool | = help: check the length explicitly, e.g. `len(xs) > 0` = run `brine explain E0307` for more about this error error: could not compile due to 1 error ``` ## Keywords and operators [Section titled “Keywords and operators”](#keywords-and-operators) Brine uses `fn`, `struct` and `elif`. The compiler recognizes the spellings from Python and other languages and says what to write instead: ```brine def add(a: int, b: int) -> int: return a + b fn main(): print(add(1, 2)) ``` ```text error[E0110]: brine uses `fn` to define functions --> def.br:1:1 | 1 | def add(a: int, b: int) -> int: | ^^^ use `fn` here = run `brine explain E0110` for more about this error error: could not compile due to 1 error ``` ```brine fn main(): let x = 3 if x < 0: print("neg") else if x == 0: print("zero") ``` ```text error[E0114]: use `elif` instead of `else if` --> elif.br:5:5 | 5 | else if x == 0: | ^^^^^^^ write `elif` = run `brine explain E0114` for more about this error error: could not compile due to 1 error ``` Operators from C-like languages are [E0105](/reference/errors/#e0105), and braces or semicolons are [E0106](/reference/errors/#e0106): | Written | Error | Write instead | | -------------------------- | ----- | ---------------------------------- | | `a && b`, `a \|\| b`, `!a` | E0105 | `a and b`, `a or b`, `not a` | | `i++`, `i--` | E0105 | `i += 1`, `i -= 1` | | `7 // 2` | E0105 | `7 / 2` | | `x > 0 ? a : b` | E0110 | `a if x > 0 else b` | | `fn main() {` | E0106 | `fn main():` and an indented block | | `print(1);` | E0106 | `print(1)` | | `class P:` | E0110 | `struct P:` | | `True` | E0201 | `true` | ## One change per expression [Section titled “One change per expression”](#one-change-per-expression) An expression can’t change a variable and also read it, or change it twice. Python evaluates left to right; Brine rules out the question: ```brine fn main(): var xs = [1, 2, 3] print(xs.pop(), xs) ``` ```text error[E0405]: `xs` is changed and also read in the same expression --> twovars.br:3:11 | 3 | print(xs.pop(), xs) | ^^^^^^^^ changed here | -- read here | = help: make the change in its own statement first, then use the result = run `brine explain E0405` for more about this error error: could not compile due to 1 error ``` Split it in two: `let last = xs.pop()`, then `print(last, xs)`. ## Summary [Section titled “Summary”](#summary) * Declare with `let` or `var`; declare each name once per function. * Write parameter and return types; convert numbers with `float()`, `int()`. * Integer `/` rounds down; there’s no `//` or `**`. * Assignment and arguments copy. Use `ref` to change a caller’s variable. * No negative indexes; strings index by bytes; missing map keys panic. * `match` must be exhaustive; `None` needs a type; conditions must be `bool`. * Failures are `Result` values passed up with `?`, not exceptions. * `fn`, `struct`, `elif`, `and`/`or`/`not`, `true`/`false`. Next: [A tour of Brine](/getting-started/tour/) covers the parts Python doesn’t have: `par`, contracts and `world`. # Installation > Install Brine with one command on Linux, macOS or Windows, check your toolchain with brine doctor, and upgrade, uninstall or build from source. One command installs the `brine` tool into `~/.brine/bin` and adds it to your `PATH`. Brine also needs a C compiler on your machine; run `brine doctor` afterwards to check that everything works. ## Quick install [Section titled “Quick install”](#quick-install) * Linux and macOS ```sh curl -fsSL https://raw.githubusercontent.com/brinelang/brine/main/install.sh | sh ``` The script picks the build for your system (`linux` or `macos`, `x64` or `arm64`), checks it against the release’s `SHA256SUMS`, and puts `brine` in `~/.brine/bin`. It adds that folder to your `PATH` in your shell’s startup file (`~/.zshrc`, `~/.bashrc`, `~/.bash_profile` on macOS bash, `~/.config/fish/conf.d/brine.fish`, or `~/.profile`). Open a new terminal, or run the `export` line it prints. * Windows In PowerShell (5.1 or later): ```powershell irm https://raw.githubusercontent.com/brinelang/brine/main/install.ps1 | iex ``` The script downloads `brine-windows-x64.zip`, checks it against the release’s `SHA256SUMS`, puts `brine.exe` in `%USERPROFILE%\.brine\bin` and adds that folder to your user `PATH`. New terminals see it; the current PowerShell session can use it right away. On Windows on ARM it installs the x64 build, which runs under emulation. Then check the install: ```sh brine --version ``` ```text brine 0.4.0 ``` Run the installer again to upgrade to the latest release. ### Installer settings [Section titled “Installer settings”](#installer-settings) Set these environment variables before running the installer: | Variable | Effect | | ------------------------ | -------------------------------------------------------------------------------- | | `BRINE_VERSION=0.4.0` | install a given version (default: the latest release) | | `BRINE_INSTALL=~/.brine` | where to install; the binary goes in `$BRINE_INSTALL/bin` | | `BRINE_NO_MODIFY_PATH=1` | don’t add Brine to your `PATH` (the installer prints the folder to add yourself) | | `BRINE_DOWNLOAD_URL=...` | where the release files are, for mirrors (default: GitHub releases) | * Linux and macOS ```sh curl -fsSL https://raw.githubusercontent.com/brinelang/brine/main/install.sh | BRINE_VERSION=0.4.0 sh ``` * Windows ```powershell $env:BRINE_VERSION = '0.4.0' irm https://raw.githubusercontent.com/brinelang/brine/main/install.ps1 | iex ``` ## Requirements [Section titled “Requirements”](#requirements) Brine needs a C compiler: `gcc` or `clang`, or MinGW on Windows. It uses it to build the runtime, to link your program, and for `--release` builds. The installer warns you if it can’t find one. * Linux and macOS ```sh xcode-select --install # macOS sudo apt install gcc # Debian, Ubuntu sudo dnf install gcc # Fedora ``` * Windows ```powershell winget install -e --id BrechtSanders.WinLibs.POSIX.UCRT # or scoop install mingw-winlibs ``` To use a specific compiler, set `BRINE_CC` (or `CC`) to its name or path. ### Check your setup [Section titled “Check your setup”](#check-your-setup) `brine doctor` checks each part of the toolchain and builds and runs a small parallel program: ```sh brine doctor ``` Output on Windows ```text ✓ brine 0.4.0 (windows x86_64) ✓ C compiler gcc — gcc (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders, r1) 16.2.0 ✓ parallelism 32 hardware threads ✓ local sockets needed for `brine run --ranks` ✓ build + run compiled and ran a parallel program ✓ debugger gdb ``` A `✗` line says what’s missing. The debugger (gdb or lldb) is optional; you only need it for `brine debug`. ### GPUs [Section titled “GPUs”](#gpus) GPU offload needs nothing at build time. If an NVIDIA driver or an Apple silicon GPU is present when your program runs, Brine can use it; otherwise everything runs on the CPU. There is no CUDA toolkit or SDK to install. See [GPU offload](/parallelism/gpu/). ### WebAssembly [Section titled “WebAssembly”](#webassembly) Building for WebAssembly (`--target wasm` or `--target web`) also needs [wasi-sdk](https://github.com/WebAssembly/wasi-sdk/releases): unpack it and set `WASI_SDK_PATH` to its folder. `brine run --target wasm` runs the result with Node.js. Both are optional, and WebAssembly support isn’t in a release yet: build Brine [from source](#build-from-source) to use it. See [WebAssembly](/tooling/webassembly/). ## Supported platforms [Section titled “Supported platforms”](#supported-platforms) | Platform | Prebuilt | Native dev builds | GPU | | ------------------- | -------- | ----------------- | ----------------- | | Linux x86-64 | ✅ | ✅ | NVIDIA (CUDA) | | Linux ARM64 | ✅ | ✅ | NVIDIA (CUDA) | | Windows x86-64 | ✅ | ✅ | NVIDIA (CUDA) | | macOS Apple silicon | ✅ | via C | Apple GPU (Metal) | | macOS Intel | ✅ | via C | – | “Native dev builds” means the compiler writes machine code itself, so dev builds are fast. Everywhere else, and for every `--release` build, it goes through the C compiler. See [Platforms](/reference/platforms/) and [Builds and backends](/tooling/builds-and-backends/). ## Prebuilt archives [Section titled “Prebuilt archives”](#prebuilt-archives) Every release on the [releases page](https://github.com/brinelang/brine/releases) has an archive per platform and a `SHA256SUMS` file: | File | Contains | | -------------------------- | ----------------------------- | | `brine-linux-x64.tar.gz` | `brine-linux-x64/brine` | | `brine-linux-arm64.tar.gz` | `brine-linux-arm64/brine` | | `brine-macos-arm64.tar.gz` | `brine-macos-arm64/brine` | | `brine-macos-x64.tar.gz` | `brine-macos-x64/brine` | | `brine-windows-x64.zip` | `brine-windows-x64\brine.exe` | The archive holds a single executable. Put it anywhere on your `PATH`. ## Uninstall [Section titled “Uninstall”](#uninstall) * Linux and macOS Delete `~/.brine` (or your `BRINE_INSTALL` folder), then remove the lines the installer added to your shell’s startup file: a `# brine` comment followed by an `export PATH=...` (or `fish_add_path`) line. ```sh rm -rf ~/.brine ``` * Windows Delete `%USERPROFILE%\.brine` and remove its `bin` folder from your user `PATH` (Settings → System → About → Advanced system settings → Environment Variables). ```powershell Remove-Item -Recurse -Force "$HOME\.brine" ``` ## Build from source [Section titled “Build from source”](#build-from-source) The compiler is written in Brine, so the first build starts from the C that Brine generates for itself, the bootstrap seed `bootstrap/brinec.c`. All you need is a C11 compiler. * Linux and macOS ```sh git clone https://github.com/brinelang/brine && cd brine cc -std=c11 -O2 -fno-strict-aliasing -w -Iruntime bootstrap/brinec.c runtime/brine_rt.c -o brine -lm -lpthread -ldl ./brine build compiler/main.br -o brine2 # brine rebuilding itself ``` * Windows With MinGW gcc or clang: ```sh git clone https://github.com/brinelang/brine && cd brine gcc -std=c11 -O2 -fno-strict-aliasing -w -Iruntime bootstrap/brinec.c runtime/brine_rt.c -o brine.exe -lws2_32 -lshell32 ./brine build compiler/main.br -o brine2 ``` The first command gives you the full `brine` tool. The second has it rebuild the compiler from its Brine sources. A correct compiler reaches a fixed point: building again with `brine2` gives the same C. ## Next [Section titled “Next”](#next) * [Your first project](/getting-started/first-project/): `brine new`, `run`, `check` and `test`. * [A tour of Brine](/getting-started/tour/): the whole language, one example at a time. # A tour of Brine > The whole Brine language in one page, from values and functions to par loops, GPUs, the world module, contracts and tests, with a compiling example for each. This page walks through Brine in about fifteen minutes. Every example is a whole program that compiles; paste one into `tour.br` and run it with `brine run tour.br`. Each section links to the page that covers its topic in full. ## Hello, Brine [Section titled “Hello, Brine”](#hello-brine) hello.br ```brine fn main(): print("Hello, brine!") ``` Blocks start with `:` and are indented 4 spaces, as in Python. Functions use `fn`, and the program starts at `main`. Comments start with `#`. ## Values and types [Section titled “Values and types”](#values-and-types) ```brine fn main(): let n = 42 # int (64-bit) let x = 2.5 # float (64-bit) let small: u8 = 200 # literals adapt to the type let ok = n > 10 and not (x < 0.0) let name = "brine" let point = (3, 4) # tuple print(n, x, small, ok, name, point.0) print(f"{name} has {len(name)} letters, x = {x:.3f}") print(float(n) * x, int(x), str(n) + "!") print(-7 / 2, -7 % 2) # integer `/` rounds down ``` Output ```text 42 2.5 200 true brine 3 brine has 5 letters, x = 2.500 105.0 2 42! -4 1 ``` Types are static and inferred. There are no implicit conversions: an `int` and a `float` don’t mix until you write `float(n)` or `int(x)`. Integer overflow, division by zero and out-of-range conversions stop the program with a clear message (a *panic*) unless the compiler proves they can’t happen. More in [Values and types](/language/values-and-types/). ## Bindings [Section titled “Bindings”](#bindings) ```brine fn main(): let limit = 10 # immutable var total = 0 # mutable for i in 0..limit: total += i let (q, r) = (17 / 5, 17 % 5) # unpack a tuple var names: [str] = [] # annotate when it can't be inferred names.push("ann") print(total, q, r, names) ``` Output ```text 45 3 2 ["ann"] ``` `let` can’t change, `var` can. A name is declared once per function: there’s no shadowing. More in [Bindings](/language/bindings/). ## Control flow [Section titled “Control flow”](#control-flow) ```brine fn classify(x: int) -> str: if x < 0: return "negative" elif x == 0: return "zero" return "small" if x < 10 else "large" fn main(): for x in [-3, 0, 7, 12]: print(x, classify(x)) var k = 1 while k < 100: k *= 3 print(k, 0 <= k < 1000) ``` Output ```text -3 negative 0 zero 7 small 12 large 243 true ``` `0..n` counts from 0 to n - 1 and `0..=n` includes n. Comparisons chain, and logic is `and`, `or`, `not`. More in [Control flow](/language/control-flow/). ## Functions and values [Section titled “Functions and values”](#functions-and-values) ```brine fn area(w: float, h: float) -> float: return w * h fn fill(ref xs: [int], v: int): # changes the caller's array for i in 0..len(xs): xs[i] = v fn main(): print(area(2.0, 3.5)) let a = [1, 2, 3] var b = a # a copy (cheap: copy-on-write) b[0] = 99 print(a, b) fill(ref b, 7) # the caller writes `ref` too print(b) ``` Output ```text 7.0 [1, 2, 3] [99, 2, 3] [7, 7, 7] ``` Everything is a value: assigning or passing an array copies it, so changing `b` never changes `a`. Copies are cheap because arrays and strings are shared until one side changes. To let a function change your variable, it declares a `ref` parameter and you pass `ref b`, so it’s visible at the call. More in [Functions](/language/functions/). ## Structs and methods [Section titled “Structs and methods”](#structs-and-methods) ```brine struct Particle: pos: float vel: float fn step(ref self, dt: float): # changes its receiver self.pos += self.vel * dt fn energy(self) -> float: return 0.5 * self.vel * self.vel fn main(): var p = Particle(pos=0.0, vel=2.0) p.step(0.1) print(p, p.energy()) ``` Output ```text Particle(pos=0.2, vel=2.0) 2.0 ``` Methods go inside the struct. A method that changes its receiver takes `ref self`. More in [Structs](/language/structs/). ## Enums and match [Section titled “Enums and match”](#enums-and-match) ```brine enum Shape: Circle(radius: float) Rect(w: float, h: float) Empty fn area(self) -> float: match self: # must cover every case case Shape.Circle(r): return 3.14159 * r * r case Shape.Rect(w, h): return w * h case Shape.Empty: return 0.0 fn describe(n: int) -> str: match n: case 0: return "zero" case 1 | 2 | 3: return "a few" case x if x < 0: return "negative" case _: return "many" fn main(): let shapes = [Shape.Circle(radius=1.0), Shape.Rect(w=2.0, h=3.0), Shape.Empty] for s in shapes: print(s, s.area()) print(describe(2), describe(-5), describe(40)) ``` Output ```text Shape.Circle(radius=1.0) 3.14159 Shape.Rect(w=2.0, h=3.0) 6.0 Shape.Empty 0.0 a few negative many ``` Each enum variant can carry its own fields. A `match` must handle every possible value; leave out `case Shape.Empty:` and the program doesn’t compile ([E0320](/reference/errors/#e0320)). More in [Enums and match](/language/enums-and-match/) and [Generics](/language/generics/). ## Option, Result and ? [Section titled “Option, Result and ?”](#option-result-and-) ```brine fn first_even(xs: [int]) -> Option[int]: for x in xs: if x % 2 == 0: return Some(x) return None fn parse_bit(s: str) -> Result[int, str]: if s == "0": return Ok(0) if s == "1": return Ok(1) return Err(f"not a bit: {s}") fn parse_pair(a: str, b: str) -> Result[int, str]: let hi = parse_bit(a)? # an Err returns from parse_pair at once let lo = parse_bit(b)? return Ok(hi * 2 + lo) fn main(): print(first_even([1, 3, 4, 5]), first_even([1, 3])) print(parse_pair("1", "0"), parse_pair("1", "x")) match parse_pair("1", "1"): case Ok(v): print("value", v) case Err(e): print("error", e) print(first_even([7]).unwrap_or(-1)) ``` Output ```text Some(4) None Ok(2) Err("not a bit: x") value 3 -1 ``` Brine has no exceptions and no null. A function that can fail returns `Result[T, E]`; a value that may be missing is `Option[T]`. `x?` gives the value inside, or returns the `Err`/`None` to the caller. File and process functions such as `read_file` return `Result` too. More in [Error handling](/language/error-handling/). ## Arrays, strings and maps [Section titled “Arrays, strings and maps”](#arrays-strings-and-maps) ```brine fn main(): var xs = [3, 1, 2] xs.push(5) xs.sort() let evens = [x * x for x in 0..10 if x % 2 == 0] print(xs, xs[1:3], xs[len(xs) - 1], evens) let s = "hello, brine" print(s.split(", "), s.upper(), s.find("brine"), s[0:5]) var ages = {"ann": 31, "bob": 27} ages["cy"] = 40 print(ages.get("zed"), "bob" in ages, ages.keys()) ``` Output ```text [1, 2, 3, 5] [2, 3] 5 [0, 4, 16, 36, 64] ["hello", "brine"] HELLO, BRINE Some(7) hello None true ["ann", "bob", "cy"] ``` There are no negative indexes: the last element is `xs[len(xs) - 1]`. Strings are UTF-8 and index by bytes. Reading a missing map key panics, so test with `in` or use `get`. More in [Arrays](/language/arrays/), [Strings](/language/strings/) and [Maps](/language/maps/). ## Parallel code with par [Section titled “Parallel code with par”](#parallel-code-with-par) ```brine fn collatz_len(n: int) -> int: var x = n var steps = 0 while x != 1: x = x / 2 if x % 2 == 0 else 3 * x + 1 steps += 1 return steps fn main(): let n = 1_000_000 let lens = par [collatz_len(i) for i in 1..=n] # parallel map let longest = par max(l for l in lens) # parallel reduction let total = par sum(float(l) for l in lens) print(f"longest: {longest}, average: {total / float(n):.2f}") var out = [0.0] * 8 par for i in 0..8: # parallel loop out[i] = sqrt(float(i)) # writes only out[i] print(out) let (a, b) = par(collatz_len(27), collatz_len(97)) # fork-join print(a, b) ``` Output ```text longest: 524, average: 131.43 [0.0, 1.0, 1.4142135623730951, 1.7320508075688772, 2.0, 2.23606797749979, 2.449489742783178, 2.6457513110645907] 111 118 ``` | Construct | Meaning | | ------------------------- | ------------------------------------------------------------------ | | `par for i in lo..hi:` | a loop whose iterations run on every core | | `par [e for i in lo..hi]` | a parallel map | | `par sum(e for ...)` | a parallel reduction: `sum`, `product`, `min`, `max`, `any`, `all` | | `par(f(a), g(b))` | run independent expressions at the same time; returns a tuple | The thread count defaults to every core; `brine run --threads 4` or `BRINE_THREADS=4` changes it. More in [Parallelism](/parallelism/overview/). ## Data races are compile errors [Section titled “Data races are compile errors”](#data-races-are-compile-errors) Inside a `par` body, variables from outside are read-only, except that a `par for` may write its own element of an outer array (`out[i]`). Break the rule and the program doesn’t compile: ```brine fn main(): let xs = [1, 2, 3, 4] var total = 0 par for i in 0..len(xs): total += xs[i] print(total) ``` ```text error[E0501]: cannot assign to `total` inside a `par` body --> race.br:5:9 | 5 | total += xs[i] | ^^^^^ would be a data race | = note: `total` is shared by all parallel iterations = help: a `par for` may only write `total[i]` (its own element); to combine values use `par sum(...)`, `par [...]`, or a sequential loop = run `brine explain E0501` for more about this error error: could not compile due to 1 error ``` The fix is a reduction: ```brine fn main(): let xs = [1, 2, 3, 4] let total = par sum(x for x in xs) print(total) ``` More in [Race freedom](/parallelism/race-freedom/). ## The same bits on every machine [Section titled “The same bits on every machine”](#the-same-bits-on-every-machine) A `par` reduction combines its elements in one fixed order that depends only on how many there are, never on how many threads run. A float `par sum` gives the same bits on 1 thread, 32 threads, or a GPU: determ.br ```brine fn main(): let xs = [1.0 / float(i) for i in 1..=1_000_000] let seq = sum(x for x in xs) # left to right let tree = par sum(x for x in xs) # fixed tree order print(f"{seq:.17f}") print(f"{tree:.17f}") ``` ```sh brine run determ.br --threads 1 brine run determ.br --threads 32 ``` Output (both runs) ```text 14.39272672286498889 14.39272672286572430 ``` The `par sum` can differ from a left-to-right `sum` in the last bits. It is usually closer to the exact answer: here the exact sum is 14.3927267228657236… More in [Deterministic reductions](/parallelism/deterministic-reductions/). ## GPUs without annotations [Section titled “GPUs without annotations”](#gpus-without-annotations) The same `par` constructs can run on an NVIDIA GPU (CUDA) or an Apple GPU (Metal). You don’t change the code or install anything beyond the driver: gpu.br ```brine fn main(): let n = 4_000_000 let xs = par [float(i) * 0.001 for i in 0..n] var ys = [0.0] * n par for i in 0..n: var v = xs[i] for k in 0..200: v = v * 0.999 + 0.001 ys[i] = sqrt(v) print(par sum(y for y in ys)) ``` ```sh brine run gpu.br --release # GPU when it's faster BRINE_GPU=off brine run gpu.br --release # CPU only BRINE_GPU=on brine run gpu.br --release # GPU whenever it can BRINE_GPU_LOG=1 brine run gpu.br --release # where each `par` ran, and why ``` Output (every run) ```text 152610131.78961766 ``` The compiler decides which bodies can run on a GPU (numeric code, loops, structs, array reads and `out[i]` writes). The runtime measures each construct and moves it to the GPU only when it expects that to be faster. Results are the same bits either way. More in [GPU offload](/parallelism/gpu/). ## Many processes with world [Section titled “Many processes with world”](#many-processes-with-world) The built-in `world` module runs one program as several cooperating processes, MPI style: world.br ```brine fn main(): let rank = world.rank() let size = world.size() var local = 0 for i in 0..1_000_000: if i % size == rank: local += i let total = world.sum(local) # every rank gets the total let all = world.gather_all(rank * rank) # everyone gets everyone's value if rank == 0: print(f"{size} ranks, total = {total}, squares = {all}") ``` ```sh brine run world.br --ranks 4 ``` Output ```text 4 ranks, total = 499999500000, squares = [0, 1, 4, 9] ``` Every `world` call is collective, and the compiler checks that all ranks make the same calls in the same order, so the program can’t deadlock: ```brine fn main(): if world.rank() == 0: let total = world.sum(1) print(total) ``` ```text error[E0510]: some ranks might not reach this communication --> dead.br:3:21 | 3 | let total = world.sum(1) | ^^^^^^^^^^^^ calls `world.sum` 2 | if world.rank() == 0: | ----------------- this may differ between ranks | = note: every rank must make the same `world` calls in the same order, otherwise the program could hang = help: move the call out of the rank-dependent branch or loop; compute rank-specific values first, then communicate on every rank = run `brine explain E0510` for more about this error error: could not compile due to 1 error ``` More in [The world module](/parallelism/world/) and [Deadlock freedom](/parallelism/deadlock-freedom/). ## Contracts the compiler proves [Section titled “Contracts the compiler proves”](#contracts-the-compiler-proves) ```brine fn clamp(x: int, lo: int, hi: int) -> int: requires lo <= hi # every caller must prove this ensures lo <= result <= hi # the compiler proves this if x < lo: return lo if x > hi: return hi return x fn last(xs: [int]) -> int: requires len(xs) >= 1 return xs[len(xs) - 1] # in bounds: no runtime check fn main(): let xs = [4, 8, 15] print(clamp(15, 0, 10), last(xs)) ``` Output ```text 10 15 ``` `requires`, `ensures`, `invariant` and `prove` are checked at compile time. If the compiler can’t prove one, it’s an error, with the facts it knew. Call `last` with an empty array and the program doesn’t compile: ```brine fn last(xs: [int]) -> int: requires len(xs) >= 1 return xs[len(xs) - 1] fn main(): let xs: [int] = [] print(last(xs)) ``` ```text error[E0601]: cannot prove the precondition of `last` --> proof.br:7:11 | 7 | print(last(xs)) | ^^^^^^^^ this call might violate `requires len(xs) >= 1` 2 | requires len(xs) >= 1 | ------------ precondition declared here | = note: with the values used here, this is always false = help: add an `assert` before the call to check it at runtime, or a `requires` to this function so its callers must prove it = run `brine explain E0601` for more about this error error: could not compile due to 1 error ``` `assert` is the runtime counterpart: it’s checked when the program runs, in every build, and the prover treats it as a fact afterwards. There’s no flag to turn checks off. More in [Proofs](/proofs/overview/), [Contracts](/proofs/contracts/) and [Fixing proof failures](/proofs/fixing-proof-failures/). ## Tests [Section titled “Tests”](#tests) clamp.br ```brine fn clamp(x: int, lo: int, hi: int) -> int: requires lo <= hi ensures lo <= result <= hi if x < lo: return lo if x > hi: return hi return x fn main(): print(clamp(15, 0, 10)) test "clamp stays in range": assert clamp(15, 0, 10) == 10 assert clamp(-5, 0, 10) == 0 test "clamp keeps values inside": assert clamp(7, 0, 10) == 7 ``` ```sh brine test clamp.br ``` ```text Built tests [dev] in 0.10s test clamp stays in range ... ok test clamp keeps values inside ... ok test result: ok. 2 passed; 0 failed; finished in 0.03s ``` Tests live next to the code. `brine test` runs each one in its own process, in parallel. More in [Testing](/language/testing/). ## Where to go next [Section titled “Where to go next”](#where-to-go-next) * [Coming from Python](/getting-started/from-python/): the differences that trip people up. * [The language](/language/values-and-types/): every feature in detail, plus [modules](/language/modules/), [built-ins](/language/builtins/), the [os module](/language/os-module/) and [C interop](/language/c-interop/). * [Quick reference](/reference/quick-reference/): the whole language on one page (also `brine guide`). * [Using Brine with coding agents](/agents/overview/). # AGENTS.md and common mistakes > The AGENTS.md file Brine writes into every new project, and each rule in it that trips people up, with the wrong code, the compiler's error and the fix. Every project made by `brine new` or `brine init` has an `AGENTS.md` that tells coding agents how to work in it: the commands to run, and the rules that most often trip up a model, or a person, who knows Python. This page shows the file, then takes each rule in turn with code that breaks it, what the compiler says, and the fix. ## The file [Section titled “The file”](#the-file) This is the `AGENTS.md` that `brine new hello` writes (Brine 0.4.0): AGENTS.md ```md # AGENTS.md — working on `hello` This is a brine project. brine is a compiled, verified, natively parallel language with Python-like syntax. ## Commands - `brine check` — type-check and verify (fast; run after every edit) - `brine run` — build and run `src/main.br` - `brine test` — run all `test "...":` blocks - `brine fmt` — format all sources (always run before committing) - `brine guide` — the full language reference (read it once) - `brine explain ` — explanation of any error code, e.g. `brine explain E0601` Add `--json` to any command for machine-readable output (one JSON object per line). ## Rules that commonly trip people up - `let` is immutable, `var` is mutable; no shadowing. - No implicit numeric conversions: write `float(i)`, `int(x)`. - Arguments are passed by value. A function that changes the caller's variable declares `ref x: T` and callers write `f(ref x)`; methods use `ref self`. - `requires`/`ensures`/`invariant`/`prove` must be proven at compile time. If the prover can't, the error lists the known facts; add an `invariant`, a `requires`, or an `assert` (runtime check that becomes a known fact). - Inside `par for i in ...:` you may only write `out[i]` of outer arrays. - Maps: `m[k]` panics if `k` is missing; test with `k in m` or use `m.get(k)`. - No negative indexes (`xs[-1]` is an error); write `xs[len(xs) - 1]`. - Strings index and slice by bytes; `s[i]` is a one-character `str`. - One expression can't change a variable and also read it, or change it twice (`print(xs.pop(), xs)`, `xs.push(xs.pop())`); split it into two statements. Uses on different sides of `and`/`or`/`if-else` are fine. - Types are optional (`let x = 5` or `let x: int = 5`); if brine.toml has `typed = true` under `[check]`, every `let`/`var`/`for` variable needs one. - `match` must cover every case; read enum variant data with `match`, not `.field`. - Expected failures return `Result[T, E]` (`Ok(x)` / `Err(e)`) or `Option[T]` (`Some(x)` / `None`); `panic` is for bugs. `x?` passes a failure up to the caller. ``` The file is yours to edit: add your project’s own conventions below the generated part. `brine init` doesn’t overwrite an existing `AGENTS.md`. ## let is immutable, var is mutable [Section titled “let is immutable, var is mutable”](#let-is-immutable-var-is-mutable) `let` declares a name that never changes; `var` declares one that can. Each name is declared once per function, so there’s no shadowing either. ```brine fn main(): let count = 0 for i in 0..3: count += i print(count) ``` ```text error[E0401]: cannot assign to `count` because it was declared with `let` --> letlet.br:4:9 | 4 | count += i | ^^^^^ immutable 2 | let count = 0 | ----- declared here | = help: declare it with `var count = ...` to make it mutable = run `brine explain E0401` for more about this error error: could not compile due to 1 error ``` Fix: declare it with `var`. ```brine fn main(): var count = 0 for i in 0..3: count += i print(count) ``` Declaring the same name again is [E0207](/reference/errors/#e0207); pick a new name, or make the first one a `var` and assign to it. See [Bindings](/language/bindings/). ## No implicit numeric conversions [Section titled “No implicit numeric conversions”](#no-implicit-numeric-conversions) `int` and `float` (and `i32`, `u8`, …) never convert on their own, not even in arithmetic or comparisons. ```brine fn mean(xs: [float]) -> float: return sum(x for x in xs) / len(xs) fn main(): print(mean([1.0, 2.0, 3.0])) ``` ```text error[E0305]: cannot divide `float` and `int` --> conv.br:2:12 | 2 | return sum(x for x in xs) / len(xs) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `/` with mismatched operands | = help: brine never converts numbers implicitly; use `float(x)` or `int(x)` = run `brine explain E0305` for more about this error error: could not compile due to 1 error ``` Fix: convert explicitly. `int(x)` truncates toward zero, and conversions to smaller types such as `i32(n)` are checked. ```brine fn mean(xs: [float]) -> float: return sum(x for x in xs) / float(len(xs)) fn main(): print(mean([1.0, 2.0, 3.0])) ``` See [Values and types](/language/values-and-types/). ## Arguments are passed by value; ref changes the caller’s variable [Section titled “Arguments are passed by value; ref changes the caller’s variable”](#arguments-are-passed-by-value-ref-changes-the-callers-variable) Arguments are copies, and parameters are read-only. A function that changes the caller’s variable declares a `ref` parameter, and the caller writes `ref` too, so the change is visible where it happens. ```brine fn add_one(ref xs: [int]): xs.push(1) fn main(): var xs: [int] = [] add_one(xs) print(xs) ``` ```text error[E0402]: parameter `xs` is a `ref`; pass the argument with `ref` --> refex.br:6:13 | 6 | add_one(xs) | ^^ the callee may modify this | = help: passing by reference is always visible at the call site in brine = run `brine explain E0402` for more about this error error: could not compile due to 1 error ``` Fix: `add_one(ref xs)`. ```brine fn add_one(ref xs: [int]): xs.push(1) fn main(): var xs: [int] = [] add_one(ref xs) print(xs) ``` Methods work the same way. A method that changes its struct takes `ref self`; with plain `self`, the receiver is read-only: ```brine struct Counter: n: int fn bump(self): self.n += 1 fn main(): var c = Counter(n=0) c.bump() print(c.n) ``` ```text error[E0403]: cannot assign to parameter `self` --> refself.br:5:9 | 5 | self.n += 1 | ^^^^^^ parameters are read-only 4 | fn bump(self): | ---- declared here | = help: to change a local copy, declare it `mut self: ...`; to change the caller's variable, declare it `ref self: ...` and call with `ref` = run `brine explain E0403` for more about this error error: could not compile due to 1 error ``` Fix: `fn bump(ref self):`. The call stays `c.bump()`. ```brine struct Counter: n: int fn bump(ref self): self.n += 1 fn main(): var c = Counter(n=0) c.bump() print(c.n) ``` To change a local copy without touching the caller’s value, declare the parameter `mut x: T`. See [Functions](/language/functions/). ## Contracts must be proven at compile time [Section titled “Contracts must be proven at compile time”](#contracts-must-be-proven-at-compile-time) `requires`, `ensures`, `invariant` and `prove` are not runtime checks. The compiler must prove them, or the program doesn’t compile. Here `first_or_last` calls `last` without knowing that `xs` is non-empty: ```brine fn last(xs: [int]) -> int: requires len(xs) >= 1 return xs[len(xs) - 1] fn first_or_last(xs: [int]) -> int: return last(xs) fn main(): print(first_or_last([1, 2, 3])) ``` ```text error[E0601]: cannot prove the precondition of `last` --> lastreq.br:6:12 | 6 | return last(xs) | ^^^^^^^^ this call might violate `requires len(xs) >= 1` 2 | requires len(xs) >= 1 | ------------ precondition declared here | = note: nothing is known here about the values involved = note: to prove: len(xs) > 0 = help: add an `assert` before the call to check it at runtime, or a `requires` to this function so its callers must prove it = run `brine explain E0601` for more about this error error: could not compile due to 1 error ``` The notes list what the prover knew (“nothing”) and what it needed. There are three fixes. Pass the obligation to the callers with a `requires`: ```brine fn last(xs: [int]) -> int: requires len(xs) >= 1 return xs[len(xs) - 1] fn first_or_last(xs: [int]) -> int: requires len(xs) >= 1 return last(xs) fn main(): print(first_or_last([1, 2, 3])) ``` Or check it at runtime with an `assert`, which the prover then knows is true: ```brine fn last(xs: [int]) -> int: requires len(xs) >= 1 return xs[len(xs) - 1] fn first_or_last(xs: [int]) -> int: assert len(xs) >= 1 return last(xs) fn main(): print(first_or_last([1, 2, 3])) ``` Or handle the case where it’s false: ```brine fn last(xs: [int]) -> int: requires len(xs) >= 1 return xs[len(xs) - 1] fn last_or(xs: [int], default: int) -> int: if len(xs) == 0: return default return last(xs) fn main(): print(last_or([1, 2, 3], 0), last_or([], 0)) ``` For loops, the missing fact is usually an `invariant`. Don’t weaken or delete the contract to make the error go away. See [Contracts](/proofs/contracts/) and [Fixing proof failures](/proofs/fixing-proof-failures/). ## par for may only write out\[i] [Section titled “par for may only write out\[i\]”](#par-for-may-only-write-outi) Inside a `par` body, variables from outside are read-only. The one exception: `par for i in ...` may write element `i` of an outer array, exactly the loop variable. Any other write could race with another iteration. ```brine fn main(): let n = 8 var out = [0] * n par for i in 0..n - 1: out[i + 1] = i print(out) ``` ```text error[E0501]: cannot assign to `out` inside a `par` body --> parwrite.br:5:9 | 5 | out[i + 1] = i | ^^^^^^^^^^ would be a data race | = note: `out` is shared by all parallel iterations = help: a `par for` may only write `out[i]` (its own element); to combine values use `par sum(...)`, `par [...]`, or a sequential loop = run `brine explain E0501` for more about this error error: could not compile due to 1 error ``` Fix: index the write with the loop variable and shift the read instead, or build the array with `par [...]`. To add values up, use a reduction such as `par sum(...)`. ```brine fn main(): let n = 8 var out = [0] * n par for i in 1..n: out[i] = i - 1 let same = par [0 if i == 0 else i - 1 for i in 0..n] print(out, out == same) ``` See [Race freedom](/parallelism/race-freedom/). ## m\[k] panics if k is missing [Section titled “m\[k\] panics if k is missing”](#mk-panics-if-k-is-missing) Reading a missing key stops the program. This compiles, then panics when it runs: ```brine fn main(): var counts = {"a": 1} for w in ["a", "b", "a"]: counts[w] += 1 print(counts) ``` Output ```text panic: key "b" is not in the map --> mapmiss.br:4:9 ``` Fix: test with `in`, or read with `get`, which returns an `Option` (or a default when you pass one). ```brine fn main(): var counts: {str: int} = {} for w in ["a", "b", "a"]: if w in counts: counts[w] += 1 else: counts[w] = 1 print(counts, counts.get("z", 0)) ``` Output ```text {"a": 2, "b": 1} 0 ``` See [Maps](/language/maps/). ## No negative indexes [Section titled “No negative indexes”](#no-negative-indexes) `xs[-1]` isn’t the last element. It’s out of bounds, and the program stops when it gets there: ```brine fn main(): let xs = [10, 20, 30] print(xs[-1]) ``` Output ```text panic: index -1 is out of bounds for an array of length 3 --> lastneg.br:3:11 ``` This is a runtime panic, not a compile error, so `brine check` won’t flag it. Fix: count from the end with `len`. ```brine fn main(): let xs = [10, 20, 30] print(xs[len(xs) - 1], xs[len(xs) - 2:]) ``` See [Arrays](/language/arrays/). ## Strings index and slice by bytes [Section titled “Strings index and slice by bytes”](#strings-index-and-slice-by-bytes) `len(s)`, `s[i]` and `s[a:b]` count bytes of UTF-8. For ASCII text that’s the same as characters; for other text it isn’t: ```brine fn main(): let s = "héllo" print(len(s), len(s.chars()), s[0], s[1:3]) let at = s.find("l").unwrap() print(at, s[at:]) ``` Output ```text 6 5 h é 3 llo ``` `é` is two bytes, so the first `l` is at byte 3. Use positions from `find` rather than counting characters, and walk characters with `for c in s` or `s.chars()`. See [Strings](/language/strings/). ## One expression can’t change a variable and read it [Section titled “One expression can’t change a variable and read it”](#one-expression-cant-change-a-variable-and-read-it) An expression that changes a variable (a mutating method such as `pop`, or a `ref` argument) can’t also read it or change it again elsewhere. Then the order in which arguments are evaluated can never change the result. ```brine fn main(): var xs = [1, 2, 3] xs.push(xs.pop()) print(xs) ``` ```text error[E0405]: `xs` is changed twice in the same expression --> pushpop.br:3:13 | 3 | xs.push(xs.pop()) | ^^^^^^^^ changed here | ----------------- and changed here | = help: make one change in its own statement first, e.g. `let v = ...`, then use `v` = run `brine explain E0405` for more about this error error: could not compile due to 1 error ``` Fix: split it into two statements. ```brine fn main(): var xs = [1, 2, 3] let v = xs.pop() xs.push(v) print(xs) ``` `print(xs.pop(), xs)` is the same error (“changed and also read”). Uses on different sides of `and`, `or` or `if`/`else` are fine, because only one side runs first. ## Types are optional, unless the project says otherwise [Section titled “Types are optional, unless the project says otherwise”](#types-are-optional-unless-the-project-says-otherwise) `let x = 5` and `let x: int = 5` are both fine. A project can require a written type on every variable with this in `brine.toml`: brine.toml ```toml [check] typed = true ``` `brine check --typed` does the same for one run. This program compiles normally: ```brine fn main(): let x = 5 var total: int = 0 for i in 0..x: total += i print(total) ``` With `--typed`, each variable without a type is an error, with the type to add: ```sh brine check --typed typed.br ``` ```text error[E0337]: `x` has no written type --> typed.br:2:9 | 2 | let x = 5 | ^ add `: int` | = note: this project asks for a type on every variable (`typed = true` in brine.toml, or `--typed`) = run `brine explain E0337` for more about this error error[E0337]: `i` has no written type --> typed.br:4:9 | 4 | for i in 0..x: | ^ add `: int` | = note: this project asks for a type on every variable (`typed = true` in brine.toml, or `--typed`) = run `brine explain E0337` for more about this error error: could not compile due to 2 errors ``` Fix: `let x: int = 5` and `for i: int in 0..x:`. In `--json` output, each E0337 carries a fix that inserts the type. See [Projects](/tooling/projects/). ## match must cover every case [Section titled “match must cover every case”](#match-must-cover-every-case) A `match` has to handle every possible value. The error names a value it misses: ```brine enum Color: Red Green Blue fn name(c: Color) -> str: match c: case Color.Red: return "red" case Color.Green: return "green" fn main(): print(name(Color.Blue)) ``` ```text error[E0320]: this `match` doesn't handle every possible value --> nomatch.br:7:11 | 7 | match c: | ^ of type `Color` | = note: not handled: `Color.Blue` = help: add `case Color.Blue:`, or a catch-all `case _:` = run `brine explain E0320` for more about this error error: could not compile due to 1 error ``` Fix: add the case, or end with `case _:`. ## Read enum data with match, not .field [Section titled “Read enum data with match, not .field”](#read-enum-data-with-match-not-field) The fields of an enum variant aren’t readable with `.`, because the value might be a different variant: ```brine enum Shape: Circle(radius: float) Square(side: float) fn main(): let s = Shape.Circle(radius=1.0) print(s.radius) ``` ```text error[E0205]: enum `Shape` has no fields --> field.br:7:13 | 7 | print(s.radius) | ^^^^^^ use `match` to read the data in a variant = run `brine explain E0205` for more about this error error: could not compile due to 1 error ``` Fix: match on the variant and bind its fields. ```brine enum Shape: Circle(radius: float) Square(side: float) fn main(): let s = Shape.Circle(radius=1.0) match s: case Shape.Circle(r): print(r) case Shape.Square(side): print(side) ``` See [Enums and match](/language/enums-and-match/). ## Failures are Result and Option, not exceptions [Section titled “Failures are Result and Option, not exceptions”](#failures-are-result-and-option-not-exceptions) Brine has no exceptions. An expected failure is a `Result[T, E]` or an `Option[T]`; `panic` is for bugs. `x?` returns a failure to the caller, so the function using it must return a matching `Result` or `Option`: ```brine fn parse_bit(s: str) -> Result[int, str]: if s == "0": return Ok(0) if s == "1": return Ok(1) return Err(f"not a bit: {s}") fn main(): let b = parse_bit("1")? print(b) ``` ```text error[E0341]: `?` needs the function to return Result[..., str] --> qmark.br:9:13 | 9 | let b = parse_bit("1")? | ^^^^^^^^^^^^^^^ this returns from a function that returns `()` | = help: change the return type to `Result[..., str]`, or handle the value with `match` = run `brine explain E0341` for more about this error error: could not compile due to 1 error ``` Fix: handle it with `match`, or make the function return a `Result`. `main` may return `Result[(), E]`; an `Err` prints `error: ...` and exits with status 1. ```brine fn parse_bit(s: str) -> Result[int, str]: if s == "0": return Ok(0) if s == "1": return Ok(1) return Err(f"not a bit: {s}") fn main() -> Result[(), str]: let b = parse_bit("1")? print(b) match parse_bit("7"): case Ok(v): print(v) case Err(e): print("bad input:", e) return Ok(()) ``` See [Error handling](/language/error-handling/). ## Related [Section titled “Related”](#related) * [Using Brine with coding agents](/agents/overview/) * [Coming from Python](/getting-started/from-python/): more differences, with their errors * [Error codes](/reference/errors/) # Machine-readable output > The exact JSON that brine check, build, test, run, explain and the other commands print with --json, one object per line, with exit codes and scripts that consume it. Add `--json` to a brine command and it prints one JSON object per line on stdout instead of text for people. Errors have the same shape from every command, many carry exact text `fixes`, and the exit code tells you whether the command succeeded. Everything on this page was printed by Brine 0.4.0. ```sh brine check --json brine check --json src/main.br brine test --json ``` ## Rules for every command [Section titled “Rules for every command”](#rules-for-every-command) * **One object per line.** Each line is a complete JSON object. Read the output line by line and parse each one; don’t parse it as one document. * **stdout only.** JSON goes to stdout. With `--json`, the status lines (`Built`, `Checked`, …) are not printed. * **Deterministic.** The same input gives the same lines in the same order. * **Exit codes** carry the result: | Exit code | Meaning | | --------- | ------------------------------------------------------------------------------------- | | `0` | success: the code compiled, every test passed, nothing to format | | `1` | compile errors, a failed test, or files that `fmt --check` would change | | `2` | the command couldn’t run: a missing file, an unknown error code, bad arguments | | `101` | `brine run`: the program panicked | | other | `brine run`: the program’s own exit code (`main` returned an `int`, or called `exit`) | * **Positions** are 1-based. `line` and `column` mark the first character of the span; `end_line` and `end_column` mark the position just after its last character. Columns count characters (Unicode code points), not bytes. * **Paths** in `file` are as Brine displays them: relative to the current folder (`src/main.br`, `race.br`). ## Diagnostics [Section titled “Diagnostics”](#diagnostics) Every compile error is a diagnostic object, whichever command found it (`check`, `build`, `run`, `test`, `fmt`). This is the data race from [Race freedom](/parallelism/race-freedom/): ```sh brine check --json race.br ``` Output (one line) ```text {"severity":"error","code":"E0501","message":"cannot assign to `total` inside a `par` body","file":"race.br","line":5,"column":9,"end_line":5,"end_column":14,"label":"would be a data race","related":[],"notes":["`total` is shared by all parallel iterations"],"help":["a `par for` may only write `total[i]` (its own element); to combine values use `par sum(...)`, `par [...]`, or a sequential loop"],"fixes":[]} ``` The same object, spread out: ```json { "severity": "error", "code": "E0501", "message": "cannot assign to `total` inside a `par` body", "file": "race.br", "line": 5, "column": 9, "end_line": 5, "end_column": 14, "label": "would be a data race", "related": [], "notes": ["`total` is shared by all parallel iterations"], "help": ["a `par for` may only write `total[i]` (its own element); to combine values use `par sum(...)`, `par [...]`, or a sequential loop"], "fixes": [] } ``` | Field | Type | Meaning | | -------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------- | | `severity` | string | `"error"` for everything in this page; read it rather than assuming | | `code` | string | the error code; `brine explain ` explains it | | `message` | string | the one-line summary (the text after `error[E0501]:`) | | `file`, `line`, `column`, `end_line`, `end_column` | string, numbers | the primary span: the code at fault | | `label` | string | the note under the primary span (`^^^^^ would be a data race`) | | `related` | array | other spans that explain the error, each `{file, line, column, end_line, end_column, label}` | | `notes` | array of strings | facts about the error; for proof failures, what the prover knew and needed | | `help` | array of strings | how to fix it | | `fixes` | array | exact edits that fix it; see [Fixes](#fixes) | All the fields are always present, even when the arrays are empty. The text output of `brine check` shows the same information: each `notes` entry is a `= note:` line, each `help` entry a `= help:` line, and `related` spans are the ones underlined with `-`. ### Proof failures [Section titled “Proof failures”](#proof-failures) A proof failure has the same shape. `related` points at the contract that couldn’t be proven: ```sh brine check --json proof.br ``` Output ```text {"severity":"error","code":"E0601","message":"cannot prove the precondition of `last`","file":"proof.br","line":7,"column":11,"end_line":7,"end_column":19,"label":"this call might violate `requires len(xs) >= 1`","related":[{"file":"proof.br","line":2,"column":14,"end_line":2,"end_column":26,"label":"precondition declared here"}],"notes":["with the values used here, this is always false"],"help":["add an `assert` before the call to check it at runtime, or a `requires` to this function so its callers must prove it"],"fixes":[]} ``` When the prover knew some facts but not enough, `notes` lists them and the goal. This is `clamp` with its `requires lo <= hi` missing, which gives one error per `return` it can’t prove: ```brine fn clamp(x: int, lo: int, hi: int) -> int: ensures lo <= result <= hi if x < lo: return lo if x > hi: return hi return x fn main(): print(clamp(5, 0, 10)) ``` Output ```text {"severity":"error","code":"E0602","message":"cannot prove the postcondition of `clamp`","file":"ens.br","line":4,"column":9,"end_line":4,"end_column":18,"label":"this might return without `lo <= result <= hi` being true","related":[{"file":"ens.br","line":2,"column":13,"end_line":2,"end_column":31,"label":"postcondition declared here"}],"notes":["known facts here: lo > x","to prove: hi >= lo"],"help":["strengthen the function's `requires`, add a loop `invariant`, or check the claim with `assert` before returning"],"fixes":[]} {"severity":"error","code":"E0602","message":"cannot prove the postcondition of `clamp`","file":"ens.br","line":6,"column":9,"end_line":6,"end_column":18,"label":"this might return without `lo <= result <= hi` being true","related":[{"file":"ens.br","line":2,"column":13,"end_line":2,"end_column":31,"label":"postcondition declared here"}],"notes":["known facts here: x >= lo, x > hi","to prove: hi >= lo"],"help":["strengthen the function's `requires`, add a loop `invariant`, or check the claim with `assert` before returning"],"fixes":[]} ``` The notes start with `known facts here:` and `to prove:`. Here the goal `hi >= lo` is exactly the missing `requires`. See [Fixing proof failures](/proofs/fixing-proof-failures/). ### Fixes [Section titled “Fixes”](#fixes) Many errors carry one or more edits that fix them. Each fix replaces the text from (`line`, `column`) up to (`end_line`, `end_column`) with `replacement`: ```brine fn main(): let a = true let b = false if a && b: print("both") ``` Output ```text {"severity":"error","code":"E0105","message":"`&&` is not an operator in brine","file":"op.br","line":4,"column":10,"end_line":4,"end_column":12,"label":"use `and`","related":[],"notes":[],"help":[],"fixes":[{"file":"op.br","line":4,"column":10,"end_line":4,"end_column":12,"replacement":"and","message":"replace with `and`"}]} ``` | Field | Type | Meaning | | -------------------------------------------------- | --------------- | ------------------------------------------ | | `file`, `line`, `column`, `end_line`, `end_column` | string, numbers | the text to replace | | `replacement` | string | the new text | | `message` | string | what the fix does, for showing to a person | When the start and end are the same, the fix is an insertion. A missing `ref` at a call: Output ```text {"severity":"error","code":"E0402","message":"parameter `xs` is a `ref`; pass the argument with `ref`","file":"stage2.br","line":6,"column":13,"end_line":6,"end_column":15,"label":"the callee may modify this","related":[],"notes":[],"help":["passing by reference is always visible at the call site in brine"],"fixes":[{"file":"stage2.br","line":6,"column":13,"end_line":6,"end_column":13,"replacement":"ref ","message":"add `ref`"}]} ``` Errors that came with fixes while this page was written: | Code | Fix | | --------------------------------- | ----------------------------------------------------------- | | [E0105](/reference/errors/#e0105) | `&&` → `and` (and the other operators from other languages) | | [E0114](/reference/errors/#e0114) | `else if` → `elif` | | [E0201](/reference/errors/#e0201) | a misspelled name → the closest match (`lenn` → `len`) | | [E0337](/reference/errors/#e0337) | insert the missing type (`: int`) under `--typed` | | [E0402](/reference/errors/#e0402) | insert `ref `before an argument | The compiler reports syntax errors before type errors, so applying one round of fixes can uncover more. Check again after applying them. ### Errors without a location [Section titled “Errors without a location”](#errors-without-a-location) When a command can’t run at all, it prints an object with only `severity`, `code` (always `null`) and `message`, and exits with status 2: ```sh brine check --json nope.br ``` Output ```text {"severity":"error","code":null,"message":"file `nope.br` not found"} ``` ## brine check [Section titled “brine check”](#brine-check) On success, `check` prints one object and exits with status 0: Output ```text {"ok":true,"checks_proven":0,"checks_remaining":1} ``` | Field | Type | Meaning | | ------------------ | ------- | -------------------------------------------------------------- | | `ok` | boolean | `true` | | `checks_proven` | number | runtime checks (bounds, overflow, division) the prover removed | | `checks_remaining` | number | runtime checks that stay in the program | On failure it prints one diagnostic per error and exits with status 1. There is no `{"ok":false}` line; use the exit code, or look for objects with a `severity`. `--report` adds one object per remaining runtime check before the summary: ```sh brine check --json --report ``` Output ```text {"runtime_check":"overflow check","location":"src/main.br:14:24","code":"i * i"} {"ok":true,"checks_proven":0,"checks_remaining":1} ``` | Field | Type | Meaning | | --------------- | ------ | --------------------------------------------- | | `runtime_check` | string | the kind of check, such as `"overflow check"` | | `location` | string | `file:line:column` | | `code` | string | the source text being checked | ## brine build [Section titled “brine build”](#brine-build) On success, `build` prints the path of the binary: Output ```text {"built":"target/dev/hello.exe"} ``` On failure it prints diagnostics and exits with status 1, like `check`. ## brine run [Section titled “brine run”](#brine-run) `run` builds and then runs the program. With `--json`, compile errors are diagnostics as above. When the build succeeds, it prints no JSON of its own: your program’s stdout and stderr pass through unchanged, and the exit code is the program’s. ```sh brine run --json panic.br ``` Output (stderr), exit code 101 ```text panic: index 3 is out of bounds for an array of length 3 --> panic.br:4:11 ``` Because program output and diagnostics share stdout, a tool that needs to tell them apart should run `brine build --json` and then the binary it names. ## brine test [Section titled “brine test”](#brine-test) `test` prints one object per test, in the order the tests appear in the source, after they have all finished: failtest.br ```brine fn double(x: int) -> int: return x * 2 fn main(): print(double(21)) test "doubles": assert double(2) == 4 test "prints then fails": print("about to check") assert double(3) == 7 test "panics": let xs: [int] = [] print(xs[0]) ``` ```sh brine test --json failtest.br ``` Output, exit code 1 ```text {"test":"doubles","ok":true,"seconds":0.023,"output":""} {"test":"prints then fails","ok":false,"seconds":0.026,"output":"about to check\n\npanic: assertion failed: double(3) == 7\n --> failtest.br:12:5\n"} {"test":"panics","ok":false,"seconds":0.015,"output":"\npanic: index 0 is out of bounds for an array of length 0\n --> failtest.br:16:11\n"} ``` | Field | Type | Meaning | | --------- | ------- | ------------------------------------------------------------------------------------------- | | `test` | string | the name from `test "...":` | | `ok` | boolean | whether it passed | | `seconds` | number | how long it took, to the millisecond | | `output` | string | everything the test printed (stdout, then stderr), including the panic message and location | There is no summary line; count the objects. The exit code is 0 if every test passed and 1 otherwise. If the tests don’t compile, `test` prints diagnostics instead and exits with status 1. A filter works as usual: `brine test --json failtest.br doubles` prints only the first line. ## brine explain [Section titled “brine explain”](#brine-explain) ```sh brine explain --json E0601 ``` Output ```text {"code":"E0601","title":"precondition not proven","explanation":"The function's `requires` clause must hold at every call, and the compiler\nmust be able to prove it from what it knows at the call site. Rules can't be\nbroken, so an unproven claim is an error, not a runtime check.\n\nFix it by giving the prover the facts it needs, for example:\n\n if 0 <= i and i < len(xs): # the call is only made when it's safe\n print(get(xs, i))\n\n assert 0 <= i < len(xs) # checked at runtime, then known to the prover\n print(get(xs, i))\n\nor by adding a `requires` to the calling function, which moves the obligation\nto its callers. The error lists the facts the prover knew and the claim it\nneeded.\n"} ``` | Field | Type | Meaning | | ------------- | ------ | ----------------------------------------------------------------- | | `code` | string | the error code | | `title` | string | a short name for the error | | `explanation` | string | the full explanation, as Markdown-like text with `\n` line breaks | With no code, `brine explain --json` lists every code, one `{code, title}` object per line: Output (first lines) ```text {"code":"E0101","title":"invalid literal"} {"code":"E0102","title":"unterminated string"} {"code":"E0103","title":"bad indentation"} ``` An unknown code prints an [error without a location](#errors-without-a-location) and exits with status 2. ## Other commands [Section titled “Other commands”](#other-commands) | Command | Output | | -------------------------- | --------------------------------------------------------------------------------------------- | | `brine new --json hello` | `{"created":"hello"}` | | `brine fmt --json` | nothing on success; files are rewritten | | `brine fmt --check --json` | `{"unformatted":"ugly.br"}` for each file that needs formatting; exit code 1 if there are any | | `brine version --json` | `{"version":"0.4.0"}` | | `brine doctor --json` | one `{"check":..., "ok":..., "detail":...}` per check | brine doctor --json ```text {"check":"brine","ok":true,"detail":"0.4.0 (windows x86_64)"} {"check":"C compiler","ok":true,"detail":"gcc — gcc (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders, r1) 16.2.0"} {"check":"parallelism","ok":true,"detail":"32 hardware threads"} {"check":"local sockets","ok":true,"detail":"needed for `brine run --ranks`"} {"check":"build + run","ok":true,"detail":"compiled and ran a parallel program"} ``` The optional debugger line of the text output isn’t part of the JSON. ## Consuming the output [Section titled “Consuming the output”](#consuming-the-output) ### With jq [Section titled “With jq”](#with-jq) ```sh # file:line:column: code message, one per error brine check --json | jq -r 'select(.severity == "error") | "\(.file):\(.line):\(.column): \(.code) \(.message)"' # the names of failing tests brine test --json | jq -r 'select(.ok == false) | .test' # group errors by code brine check --json | jq -s 'map(select(.code)) | group_by(.code) | map({code: .[0].code, count: length})' ``` ### With Python [Section titled “With Python”](#with-python) This script applies every machine fix that `brine check` offers, checks again until no fixes are left, and prints what remains for a person (or an agent) to fix: brine_fix.py ```python """Apply the machine fixes from `brine check --json`, then report what's left.""" import json import subprocess import sys def check(target): out = subprocess.run(["brine", "check", "--json", *target], capture_output=True, text=True, encoding="utf-8").stdout return [json.loads(line) for line in out.splitlines() if line.strip()] def apply_fixes(diags): edits = {} # file -> [(line, column, end_line, end_column, replacement)] for d in diags: for f in d.get("fixes", []): edits.setdefault(f["file"], []).append( (f["line"], f["column"], f["end_line"], f["end_column"], f["replacement"])) for path, fixes in edits.items(): with open(path, encoding="utf-8", newline="") as fh: lines = fh.read().split("\n") # last fix first, so earlier positions stay valid for line, col, end_line, end_col, text in sorted(fixes, reverse=True): head = lines[line - 1][:col - 1] tail = lines[end_line - 1][end_col - 1:] lines[line - 1:end_line] = [head + text + tail] with open(path, "w", encoding="utf-8", newline="") as fh: fh.write("\n".join(lines)) return sum(len(v) for v in edits.values()) target = sys.argv[1:] # syntax errors come before type errors, so fixing can reveal more fixes for _ in range(10): diags = check(target) if not any(d.get("fixes") for d in diags): break print(f"applied {apply_fixes(diags)} fixes") for d in diags: if d.get("ok"): print("ok") elif d.get("severity") == "error": where = f'{d["file"]}:{d["line"]}:{d["column"]}' if "file" in d else "brine" print(f'{where}: {d["code"]}: {d["message"]}') for h in d.get("help", []): print(f" help: {h}") ``` Python string indexes count code points, as brine’s columns do, so the slices line up. Run it on this file, which has five mistakes: demo.br ```brine fn add_one(ref xs: [int]): xs.push(1) fn main(): var xs: [int] = [] add_one(xs) let ready = len(xs) > 0 && xs[0] == 1 if not ready: print("empty") else if lenn(xs) == 1: print("one") let n: float = len(xs) print(n) ``` ```sh python brine_fix.py demo.br ``` Output ```text applied 2 fixes applied 2 fixes demo.br:12:20: E0301: mismatched types help: convert explicitly with `float(...)` ``` The first round fixed the syntax errors (`&&` and `else if`); the second fixed the missing `ref` and the misspelled `len`. The last error needs a decision, `float(len(xs))`, so it’s left for you. ## Related [Section titled “Related”](#related) * [Using brine with coding agents](/agents/overview/) * [Error codes](/reference/errors/) * [The brine command](/tooling/cli/) # Using Brine with coding agents > How Brine is built for coding agents, the edit-check-fix loop, brine guide and brine explain, AGENTS.md, these docs as Markdown and llms.txt, and a snippet for your agent's instructions. Brine is designed to be written by coding agents as well as people. The loop is short: edit, run `brine check --json`, fix what it reports, run `brine test`. Every error has a stable code, an exact location, the reason, and how to fix it, and the compiler catches the mistakes that are hardest to find by reading code: data races, deadlocks, and broken contracts. ## Why Brine suits agents [Section titled “Why Brine suits agents”](#why-brine-suits-agents) The design doc lists these principles for agent usability: * **Stable error codes.** Every error has a code (`E0501`), a primary span, a “why” and a “how to fix”. Codes are stable, so an agent can look them up and learn them. * **Machine-applicable fixes.** In `--json` output, many errors carry a `fixes` array of exact text replacements. See [Machine-readable output](/agents/json-output/#fixes). * **Deterministic output.** The same input gives the same diagnostics in the same order, so runs can be compared and cached. * **No configuration** for anything common. `brine guide` and an `AGENTS.md` in every project give an agent what it needs to start. * **Syntax models already know.** Brine looks like Python, minus the parts that cause bugs. When an agent writes Python or C habits (`def`, `else if`, `&&`, `x++`), the compiler names the Brine spelling, often with a fix. The language’s rules help too. A mistake that would be a silent bug elsewhere is a compile error in Brine: a data race in a parallel loop ([E0501](/reference/errors/#e0501)), a collective call that could deadlock ([E0510](/reference/errors/#e0510)), a precondition a caller can’t guarantee ([E0601](/reference/errors/#e0601)). Runtime checks for bounds, overflow and division by zero can’t be turned off, so a wrong index stops the program with a location instead of corrupting memory. ## The loop [Section titled “The loop”](#the-loop) 1. Edit the code. 2. Run `brine check --json`. It type-checks and runs the prover without building, so it’s fast. Each problem is one JSON object on its own line. 3. Fix each error. Apply its `fixes` if it has any; otherwise read `help`, or run `brine explain `. 4. Repeat until `brine check` prints `{"ok":true,...}`. 5. Run `brine test --json`: one object per test, with its output if it failed. 6. Run `brine fmt` before committing. ```sh brine check --json # after every edit brine test --json # when it compiles brine fmt # before committing ``` `brine check` exits with status 0 when the code compiles and 1 when it doesn’t, so a script can loop on the exit code alone. See [Machine-readable output](/agents/json-output/) for every object shape and exit code. ## brine guide [Section titled “brine guide”](#brine-guide) `brine guide` prints the whole language on one page: syntax, types, built-ins, `par`, `world`, contracts and the CLI, with examples. It’s about 500 lines, small enough to read into context once at the start of a task. ```sh brine guide ``` ```text # brine quick reference brine is a compiled, verified, natively parallel language with Python-like syntax. Blocks use `:` plus 4-space indentation. Comments start with `#`. ... ``` It’s the same text as the [quick reference](/reference/quick-reference/), and it always matches the installed compiler. ## brine explain [Section titled “brine explain”](#brine-explain) Every error ends with `run brine explain for more about this error`. The explanation says why the rule exists and shows the usual fixes: ```sh brine explain E0501 ``` ```text E0501: writing shared state in `par` Iterations of `par` code run at the same time. To rule out data races, a `par` body may not assign variables from outside it, call functions that mutate them, or push/pop them. The one exception: in `par for i in ...`, arrays from outside may be written at index `i`: var out = [0.0] * n par for i in 0..n: out[i] = f(i) # OK: each iteration writes its own slot To combine results, use a reduction instead: `par sum(f(i) for i in 0..n)`. ``` `brine explain` with no code lists every code and its title. Both forms take `--json`. The same explanations are on the [error codes](/reference/errors/) page. ## AGENTS.md in every project [Section titled “AGENTS.md in every project”](#agentsmd-in-every-project) `brine new` and `brine init` write an `AGENTS.md` into the project. It lists the commands and the rules that most often trip up a model that knows Python: `let` and `var`, explicit conversions, `ref`, proofs, `par for` writes, map keys, negative indexes, string bytes. Many agents read `AGENTS.md` automatically. See [AGENTS.md and common mistakes](/agents/agents-md/) for the full text and an example of each rule. ## These docs, for agents [Section titled “These docs, for agents”](#these-docs-for-agents) Every page of this site is also plain Markdown: * **Add `.md` to any URL.** `https://docs.brinelang.org/proofs/contracts/` is also `https://docs.brinelang.org/proofs/contracts.md`. Code blocks, tabs and notes come through as Markdown. * **Copy page.** The button under each page title copies that page’s Markdown. “View as Markdown” opens it, and “Ask Claude” and “Ask ChatGPT” start a chat that reads it. * **[`/llms.txt`](/llms.txt)** is an index of the site for language models, following the [llms.txt](https://llmstxt.org/) convention, with the key facts for writing brine at the top. * **[`/llms-full.txt`](/llms-full.txt)** is the whole site in one file. * **[`/llms-small.txt`](/llms-small.txt)** is the same with non-essential content removed, for smaller context windows. * **One topic at a time:** [`/_llms-txt/language.txt`](/_llms-txt/language.txt), [`/_llms-txt/parallelism.txt`](/_llms-txt/parallelism.txt) and [`/_llms-txt/proofs.txt`](/_llms-txt/proofs.txt) hold one section of the docs each. ## Add brine to your agent’s instructions [Section titled “Add brine to your agent’s instructions”](#add-brine-to-your-agents-instructions) Paste this into your agent’s instructions (`AGENTS.md`, `CLAUDE.md`, a Cursor rule, or a system prompt) when it works on brine code outside a project made by `brine new`: AGENTS.md ```md ## brine This project uses brine, a compiled language with Python-like syntax, compile-time proofs and race-free `par` parallelism. It is not Python. - Read `brine guide` once before writing brine. Docs: https://docs.brinelang.org (add `.md` to any page URL for Markdown; https://docs.brinelang.org/llms.txt is an index). - After every edit run `brine check --json` and fix every diagnostic. Apply the `fixes` array when present. `brine explain ` explains any error code. - Run `brine test --json` before finishing, and `brine fmt` before committing. - Common mistakes: `let` is immutable and `var` is mutable, with no shadowing; no implicit numeric conversions (`float(i)`); integer `/` rounds down; arguments are copies, so a function that changes the caller's variable takes `ref x: T` and is called as `f(ref x)`; no negative indexes; strings index by bytes; `m[k]` panics on a missing key (use `k in m` or `m.get(k)`); `match` must cover every case; failures are `Result`/`Option` with `?`, not exceptions; `elif`, not `else if`. - `requires`, `ensures` and `invariant` must be proven at compile time. If the prover can't, add a `requires`, an `invariant`, or an `assert`. - Inside `par for i in ...:` only `out[i]` of outer arrays may be written; use `par sum(...)` or `par [...]` to combine results. ``` ## Related [Section titled “Related”](#related) * [AGENTS.md and common mistakes](/agents/agents-md/) * [Machine-readable output](/agents/json-output/) * [The brine command](/tooling/cli/) * [Error codes](/reference/errors/) # Arrays > Array literals, comprehensions, methods, slices, nested arrays, copy-on-write value semantics and reductions over generators in Brine. An array `[T]` is a growable list of values of one type. Arrays are **values**: `let b = a` gives you an independent copy (made lazily, so it’s cheap), and an index outside the array stops the program unless the compiler proved it can’t happen. ```brine fn main(): var xs = [3, 1, 2] xs.push(5) xs.sort() print(xs, len(xs), xs[0], 5 in xs) ``` Output ```text [1, 2, 3, 5] 4 1 true ``` ## Making arrays [Section titled “Making arrays”](#making-arrays) Write the elements in brackets, repeat one value with `* n`, or build an array with a comprehension: ```brine fn main(): let primes = [2, 3, 5, 7] let zeros = [0.0] * 4 let squares = [i * i for i in 0..6] let even_squares = [i * i for i in 0..10 if i % 2 == 0] let words = ["fig", "pear", "plum"] let lengths = [len(w) for w in words] print(primes, zeros, squares) print(even_squares, lengths) ``` Output ```text [2, 3, 5, 7] [0.0, 0.0, 0.0, 0.0] [0, 1, 4, 9, 16, 25] [0, 4, 16, 36, 64] [3, 4, 4] ``` * `[x] * n` makes `n` copies of `x`. The value goes on the left: `3 * [0]` is an error. A negative `n` panics. * A comprehension is `[expr for name in iterable]` with an optional `if filter`. The iterable is a range (`0..n`, `0..=n`, `range(a, b, step)`), an array, a string or a map. Tuples unpack: `[name for name, age in people if age > 30]`. * One `for` per comprehension: `[x for row in grid for x in row]` is an error (E0110). Use a loop with `extend` instead. * An empty array needs a type: `var names: [str] = []`. `let xs = []` alone is error [E0308](/reference/errors/#e0308). `par [...]` builds the array on every core. See [parallelism](/parallelism/overview/). ## Reading elements [Section titled “Reading elements”](#reading-elements) ```brine fn main(): let xs = [10, 20, 30, 40] print(xs[0], xs[len(xs) - 1], len(xs)) print(20 in xs, xs.contains(99), 99 not in xs) print(xs.index(30), xs.index(99)) print(xs == [10, 20, 30, 40], xs != [40, 30, 20, 10]) ``` Output ```text 10 40 4 true false true Some(2) None true true ``` * `xs[i]` reads element `i`, counting from 0. * `x in xs` and `xs.contains(x)` are the same test. * `xs.index(x)` returns `Option[int]`: `Some(i)` for the first match, `None` if there is none. See [error handling](/language/error-handling/) for `Option`. * `==` and `!=` compare length and every element. Arrays have no `<`: only numbers and strings are ordered. An index outside the array is a panic: ```brine fn main(): let xs = [1, 2, 3] let i = len(args()) + 5 print(xs[i]) ``` Output ```text panic: index 5 is out of bounds for an array of length 3 --> main.br:4:11 ``` The program exits with code 101. When the compiler can prove an index is in range (as in `for i in 0..len(xs): xs[i]`), it removes the check. `brine check --report` lists the checks that remain. See [proofs](/proofs/overview/). ## No negative indexes [Section titled “No negative indexes”](#no-negative-indexes) Brine has no negative indexes. `xs[-1]` isn’t the last element; it’s out of bounds, and panics like any other bad index. Write the index out: ```brine fn main(): let xs = [1, 2, 3] let last = xs[len(xs) - 1] let tail = xs[len(xs) - 2:] print(last, tail) ``` Output ```text 3 [2, 3] ``` Python’s `xs[-1]` hides an off-by-one when the index is computed: `i - 1` with `i` at 0 silently gives the last element. In Brine it stops with a clear message. ## Changing an array [Section titled “Changing an array”](#changing-an-array) Methods that change an array need a `var`: ```brine fn main(): var xs = [3, 1, 2] xs.push(5) let last = xs.pop() let first = xs.pop(0) print(last, first, xs) xs.insert(0, 9) xs.extend([7, 8]) print(xs) xs.sort() print(xs) xs.reverse() print(xs) xs[0] = 100 xs[1] += 1 print(xs) xs.clear() print(xs, len(xs)) ``` Output ```text 5 3 [1, 2] [9, 1, 2, 7, 8] [1, 2, 7, 8, 9] [9, 8, 7, 2, 1] [100, 9, 7, 2, 1] [] 0 ``` * `pop()` on an empty array panics (`pop() from an empty array`), and so do `pop(i)` and `insert(i, x)` with `i` out of range. `insert(len(xs), x)` appends. * `sort()` works on arrays of numbers and strings and is stable. Arrays of tuples, structs or `bool` can’t be sorted (E0305). * There is no `+` for arrays. Use `extend`. ## Slices [Section titled “Slices”](#slices) `xs[a:b]` is a new array with the elements from index `a` up to, not including, `b`. Leave out `a` to start at 0 and `b` to go to the end: ```brine fn main(): let xs = [10, 20, 30, 40, 50] var middle = xs[1:4] middle[0] = 0 print(middle, xs[:2], xs[3:], xs[5:], xs[:]) print(xs) ``` Output ```text [0, 30, 40] [10, 20] [40, 50] [] [10, 20, 30, 40, 50] [10, 20, 30, 40, 50] ``` A slice is a copy: changing it never changes the original. Bounds must satisfy `0 <= a <= b <= len(xs)`; anything else panics, with no clamping as in Python: ```text panic: slice [1:5] is out of bounds for an array of length 3 ``` ## Nested arrays [Section titled “Nested arrays”](#nested-arrays) An array can hold arrays. Build a grid with a comprehension, so each row is its own value: ```brine fn main(): var grid = [[0] * 3 for _ in 0..2] grid[1][2] = 7 let copy = grid grid[0][0] = 1 print(grid) print(copy) print(len(grid), len(grid[0]), grid[1][2]) ``` Output ```text [[1, 0, 0], [0, 0, 7]] [[0, 0, 0], [0, 0, 7]] 2 3 7 ``` `[[0] * 3] * 2` works too. Because arrays are values, its rows are independent: setting `g[0][0]` changes only the first row, unlike Python lists. ## Arrays are values [Section titled “Arrays are values”](#arrays-are-values) Assigning an array, passing it to a function, or storing it in a struct copies it. The copy is lazy (*copy-on-write*): both names share one buffer until one of them changes, and only then is the buffer copied. Copies cost almost nothing, and changing one array never changes another. ```brine fn add_one(mut xs: [int]) -> [int]: for i in 0..len(xs): xs[i] += 1 return xs fn main(): var a = [3, 1, 2] let b = a a.sort() let c = add_one(a) print(a, b, c) ``` Output ```text [1, 2, 3] [3, 1, 2] [2, 3, 4] ``` To let a function change the caller’s array, pass it by reference: `fn fill(ref xs: [int], v: int)`, called as `fill(ref data, 0)`. See [functions](/language/functions/). ## Looping over an array [Section titled “Looping over an array”](#looping-over-an-array) `for x in xs` walks a snapshot of `xs` taken when the loop starts, so the body may change `xs` safely: ```brine fn main(): var xs = [1, 2, 3] for x in xs: xs.push(x * 10) print(xs) for i, x in enumerate(["a", "b"]): print(i, x) ``` Output ```text [1, 2, 3, 10, 20, 30] 0 a 1 b ``` The loop runs three times. Changes made in the body show up after the loop. See [control flow](/language/control-flow/) for more loop forms. ## One change per statement [Section titled “One change per statement”](#one-change-per-statement) A statement can’t both change a variable and read it somewhere else, or change it twice. Otherwise the result would depend on which part runs first: ```brine fn main(): var xs = [1, 2, 3] print(xs.pop(), xs) ``` ```text error[E0405]: `xs` is changed and also read in the same expression --> main.br:3:11 | 3 | print(xs.pop(), xs) | ^^^^^^^^ changed here | -- read here | = help: make the change in its own statement first, then use the result = run `brine explain E0405` for more about this error error: could not compile due to 1 error ``` Split it in two: `let last = xs.pop()`, then `print(last, xs)`. The same applies to `xs.push(xs.pop())`. Reading the array in the changing call’s own arguments is fine (`xs.push(len(xs))`), and so is using it on the two sides of `and`, `or` or `if ... else`. See [E0405](/reference/errors/#e0405). ## Reductions [Section titled “Reductions”](#reductions) `sum`, `product`, `min`, `max`, `any` and `all` take a generator: a comprehension without the brackets. No array is built. ```brine fn main(): let xs = [4, -2, 8, 5] print(sum(x for x in xs), product(x for x in xs)) print(min(x for x in xs), max(x * x for x in xs)) print(any(x < 0 for x in xs), all(x < 10 for x in xs)) print(sum(i for i in 0..101 if i % 2 == 0)) print(sum(float(x) / 2.0 for x in xs)) ``` Output ```text 15 -320 -2 64 true true 2550 7.5 ``` * Over an empty sequence, `sum` is 0, `product` is 1, `any` is `false` and `all` is `true`. `min` and `max` panic: `min() of an empty sequence`. * `min(a, b)` and `max(a, b)` with two arguments are the plain built-ins. * A generator can only be passed to one of these six; `len(x for x in xs)` is error [E0316](/reference/errors/#e0316). Use `[x for x in xs]` to build an array. * Overflow in a reduction panics like any other overflow. * `par sum(...)` and the others run on every core and give the same bits on any machine. See [deterministic reductions](/parallelism/deterministic-reductions/). ## Reference [Section titled “Reference”](#reference) | Operation | Result | Notes | | -------------------------------------------- | ------------- | ------------------------------------------------------------ | | `len(xs)` | `int` | | | `xs[i]` | `T` | panics if out of range; no negative indexes | | `xs[a:b]`, `xs[:b]`, `xs[a:]` | `[T]` | a copy; panics if out of range | | `x in xs`, `xs.contains(x)` | `bool` | | | `xs.index(x)` | `Option[int]` | first match | | `xs == ys` | `bool` | element by element | | `xs.push(x)` | | append | | `xs.pop()` | `T` | remove the last; panics if empty | | `xs.pop(i)` | `T` | remove at `i`, shifting the rest left | | `xs.insert(i, x)` | | put `x` at `i`, shifting the rest right; `0 <= i <= len(xs)` | | `xs.extend(ys)` | | append every element of `ys` | | `xs.sort()` | | numbers and strings; stable | | `xs.reverse()` | | | | `xs.clear()` | | remove everything | | `sum`, `product`, `min`, `max`, `any`, `all` | | over a generator: `sum(x for x in xs)` | ## Rules [Section titled “Rules”](#rules) * Elements all have one type. An empty array needs an annotation. * Indexes start at 0 and are never negative. Out-of-range indexes and slices panic. * `let b = a` copies; changing `b` never changes `a`. * `for x in xs` loops over a snapshot. * A statement can’t change an array and read it elsewhere. Related: [strings](/language/strings/), [maps](/language/maps/), [values and types](/language/values-and-types/), [built-in functions](/language/builtins/). # Bindings > Declaring names with let, var and const, type annotations, tuple unpacking, the no-shadowing rule, the _ placeholder, and requiring written types with typed = true. `let` declares a name you can’t change, `var` one you can, and `const` a compile-time constant at the top level. Types are inferred unless you write them. A name can’t be declared again while it’s still in scope (no shadowing). ## let and var [Section titled “let and var”](#let-and-var) ```brine const LIMIT = 100 const GREETING: str = "hello" fn main(): let x = 10 # immutable var total = 0 # mutable total += x total = total * 2 print(x, total, LIMIT, GREETING) ``` Output ```text 10 20 100 hello ``` A `let` can never be assigned to or changed in place, and neither can anything inside it: no `xs.push(...)` on a `let` array, no `p.x = 1` on a `let` struct, no `ref` method calls on it. ```brine fn main(): let x = 1 x = 2 print(x) ``` ```text error[E0401]: cannot assign to `x` because it was declared with `let` --> main.br:3:5 | 3 | x = 2 | ^ immutable 2 | let x = 1 | - declared here | = help: declare it with `var x = ...` to make it mutable = run `brine explain E0401` for more about this error error: could not compile due to 1 error ``` Every variable starts with a value: `var x: int` on its own is a syntax error. The compound assignments are `+=`, `-=`, `*=`, `/=` and `%=`. There are none for the bitwise operators: write `x = x & mask`. ## const [Section titled “const”](#const) `const` declares a constant at the top level of a module. Its value is computed at compile time from literals, other constants, arithmetic, comparisons and logic. ```brine const SIZE = 2 * 50 const LIMIT = SIZE + 1 const RATE: float = 0.25 const NAME = "brine" fn main(): print(SIZE, LIMIT, RATE, NAME) ``` Output ```text 100 101 0.25 brine ``` A function call or an array isn’t a constant expression ([E0317](/reference/errors/#e0317)). The exception is `embed("file.txt")`, which reads a file into a `str` at compile time and works in a `const` too (see [built-ins](/language/builtins/)). ## Type annotations [Section titled “Type annotations”](#type-annotations) Write a type after the name, with a colon. Annotations are optional, as in Python, but you need one when the compiler can’t infer the type, as for an empty array or map. ```brine fn main(): let n: i32 = 7 # annotate whenever you like var names: [str] = [] # ...and when the type can't be inferred var counts: {str: int} = {} names.push("ann") counts["ann"] = 1 for i: int in 0..2: # loop variables too print(i, n) print(names, counts) ``` Output ```text 0 7 1 7 ["ann"] {"ann": 1} ``` ```brine fn main(): var names = [] names.push("ann") ``` ```text error[E0308]: cannot infer the element type of an empty array --> main.br:2:17 | 2 | var names = [] | ^^ type unknown | = help: annotate the variable, e.g. `var xs: [int] = []` = run `brine explain E0308` for more about this error error: could not compile due to 1 error ``` Function parameters and return types are always written (see [functions](/language/functions/)); only local variables are inferred. ## Tuple unpacking [Section titled “Tuple unpacking”](#tuple-unpacking) A `let` or `var` can take a tuple apart. Patterns nest. ```brine fn divmod(a: int, b: int) -> (int, int): return (a / b, a % b) fn main(): let (q, r) = divmod(17, 5) let (a, (b, c)) = (1, (2, 3)) var (lo, hi) = (0, 10) lo += 1 let (first, _) = ("ann", 31) let (_, _, last) = (1, 2, 3) print(q, r, a, b, c, lo, hi, first, last) ``` Output ```text 3 2 1 2 3 1 10 ann 3 ``` To annotate an unpacking, write the tuple type after the pattern: `let (q, r): (int, int) = (17 / 5, 17 % 5)`. `for` loops unpack the same way (`for name, age in pairs:`); see [control flow](/language/control-flow/#tuple-unpacking-in-for). ## The `_` placeholder [Section titled “The \_ placeholder”](#the-_-placeholder) `_` stands for a value you don’t need: in unpacking (`let (q, _) = ...`), as a loop variable (`for _ in 0..n:`), and as a [`match` pattern](/language/enums-and-match/#patterns). It may appear any number of times and can never be read. ```brine fn main(): let (q, _) = (17 / 5, 17 % 5) print(q, _) ``` ```text error[E0201]: cannot find value `_` --> main.br:3:14 | 3 | print(q, _) | ^ not found in this scope | = help: `_` ignores a value; give the value a name to use it = run `brine explain E0201` for more about this error error: could not compile due to 1 error ``` ## No shadowing [Section titled “No shadowing”](#no-shadowing) You can’t declare a name that is already in scope in the same function, not even in an inner block. Reading code, a name always means the one variable. ```brine fn main(): let count = 1 if count > 0: let count = 2 print(count) ``` ```text error[E0207]: `count` is already defined in this function --> main.br:4:13 | 4 | let count = 2 | ^^^^^ defined again here 2 | let count = 1 | ----- first defined here | = help: brine doesn't allow shadowing; pick a new name, or declare it with `var` and assign to it = run `brine explain E0207` for more about this error error: could not compile due to 1 error ``` A parameter counts too: `fn f(n: int)` can’t declare `let n`. Use `mut n: int` if you want to change a local copy (see [functions](/language/functions/#mut-parameters)). Blocks that don’t overlap may reuse a name: two `for i` loops one after the other, or the same name in the `if` and the `else` branch. ```brine fn main(): var total = 0 for _ in 0..3: total += 1 for i in 0..2: total += i for i in 0..2: # a new loop: `i` can be used again total += i if total > 3: let note = "big" print(note) else: let note = "small" print(note) print(total) ``` Output ```text big 5 ``` ## Requiring written types [Section titled “Requiring written types”](#requiring-written-types) Some teams want every variable’s type on the page. Put this in `brine.toml`: brine.toml ```toml [check] typed = true ``` or pass `--typed` to `brine check`, `brine run` or `brine build`. Then every `let`, `var` and `for` variable needs a written type, and [E0337](/reference/errors/#e0337) names each one that’s missing, with the type to add: ```brine fn main(): let n: int = 3 let xs = [1, 2] for i in 0..n: print(i, xs) ``` ```sh brine check --typed main.br ``` ```text error[E0337]: `xs` has no written type --> main.br:3:9 | 3 | let xs = [1, 2] | ^^ add `: [int]` | = note: this project asks for a type on every variable (`typed = true` in brine.toml, or `--typed`) = run `brine explain E0337` for more about this error error[E0337]: `i` has no written type --> main.br:4:9 | 4 | for i in 0..n: | ^ add `: int` | = note: this project asks for a type on every variable (`typed = true` in brine.toml, or `--typed`) = run `brine explain E0337` for more about this error error: could not compile due to 2 errors ``` Comprehension variables (`[x * x for x in xs]`), `match` bindings and `_` don’t need a type even then: their types come from the code right next to them. ## Rules [Section titled “Rules”](#rules) * `let` is immutable, all the way down. `var` is mutable. * `const` is top-level and computed at compile time. * Types are inferred for locals; annotate with `name: Type`. Empty `[]` and `{}` need one. * `let (a, (b, c)) = ...` unpacks tuples, nested. * `_` ignores a value and can’t be read. * No shadowing: a name can’t be declared again while it’s in scope, including parameters. * `typed = true` under `[check]` (or `--typed`) requires a written type on every variable (E0337). # Built-in functions > Every function Brine provides without an import, from print and len to math, conversions, embed(), files and processes, and which of them can run inside par. These functions are always available, with no import. Files and processes report failure as `Result` or `Option` values, never by crashing. A few built-ins change the world outside the program, and those can’t be called inside a `par` body ([E0503](/reference/errors/#e0503)). | Function | Result | | | --------------------------------------------------------------- | ------------------ | -------------------------------------- | | `print(a, b, ..., sep=" ", end="\n")` | | write to stdout | | `eprint(a, b, ..., sep=" ", end="\n")` | | write to stderr | | `len(x)` | `int` | array, string (bytes) or map | | `abs(x)` | same as `x` | any number | | `min(a, b)`, `max(a, b)` | same as `a` | two numbers of the same type | | `sqrt`, `sin`, `cos`, `tan`, `exp`, `log` | float | one float | | `floor`, `ceil`, `round` | float | one float | | `pow(x, y)` | `float` | two `float`s | | `sum`, `product`, `min`, `max`, `any`, `all` | | over a generator: `sum(x for x in xs)` | | `range(stop)`, `range(start, stop)`, `range(start, stop, step)` | | in `for` and comprehensions only | | `enumerate(xs)` | `[(int, T)]` | | | `int(x)`, `float(x)`, `str(x)`, `i32(x)`, `u8(x)`, … | | conversions | | `ord(c)`, `chr(n)` | `int`, `str` | code points | | `clock()` | `float` | seconds, for timing | | `args()` | `[str]` | command-line arguments | | `exit(code)` | | stop with an exit code | | `panic(msg)` | | stop with a message | | `embed("file")` | `str` | a file’s text, read at compile time | | `read_file(path)` | `Result[str, str]` | | | `write_file(path, text)` | `Result[(), str]` | | | `file_exists(path)` | `bool` | | | `getenv(name)` | `Option[str]` | | | `run(cmd)` | `Result[int, str]` | run a program and wait for it | The [os module](/language/os-module/) has more for files, directories and processes, and [world](/parallelism/overview/) is for programs that run as many processes. ## Printing [Section titled “Printing”](#printing) `print` writes its arguments separated by `sep` (default `" "`) and followed by `end` (default `"\n"`). `eprint` does the same on stderr. ```brine fn main(): print("a", 1, 2.5, true) print([1, 2], ("x", 3), {"k": "v"}, Some("s")) print("no newline", end="") print(" | then", 1, 2, sep="-") print() eprint("this goes to stderr") ``` Output ```text a 1 2.5 true [1, 2] ("x", 3) {"k": "v"} Some("s") no newline | then-1-2 this goes to stderr ``` * A string argument prints as its text. Strings inside arrays, tuples, maps, structs and enums print quoted, so `["a"]` is distinguishable from `[a]`. * Every value prints except `()` on its own (`Ok(())` prints fine). Structs print as `Point(x=1, y=2)` and enum variants as `Shape.Circle(radius=1.0)`. * `print()` with no arguments prints an empty line. * `sep` and `end` must be strings and are the only keyword arguments. ## Numbers [Section titled “Numbers”](#numbers) ```brine fn main(): print(abs(-3), abs(-2.5), min(3, 7), max(2.5, 1.0)) print(sqrt(2.0), pow(2.0, 10.0), exp(1.0), log(10.0)) print(sin(0.0), cos(0.0), tan(0.0)) print(floor(-1.5), ceil(1.2), round(2.5), round(-2.5), int(round(2.6))) print(sqrt(-1.0), log(0.0)) ``` Output ```text 3 2.5 3 2.5 1.4142135623730951 1024.0 2.718281828459045 2.302585092994046 0.0 1.0 0.0 -2.0 2.0 3.0 -3.0 3 nan -inf ``` * `abs` works on any number and returns the same type. `abs` of the most negative integer overflows and panics. * `min(a, b)` and `max(a, b)` need two numbers of the **same** type: `min(1, 2.0)` is error E0301. A literal adapts to the other argument (`min(x, 3)` with an `i32` `x` is fine). They don’t compare strings. * `sqrt`, `sin`, `cos`, `tan`, `exp`, `log` (natural log), `floor`, `ceil` and `round` take a `float` or `f32` and return the same type. An integer literal is read as a float (`sqrt(4)` is `2.0`); an integer variable needs `float(n)`. * `pow(x, y)` takes and returns `float` (`f64`). * `floor`, `ceil` and `round` return floats. Convert with `int(...)` for an integer. `round` rounds halves away from zero (`round(2.5)` is `3.0`), not to even as Python does. * Math on floats follows IEEE 754: `sqrt(-1.0)` is `nan` and `0.0 / 0.0` is `nan`, with no panic. Integer division by zero panics. `len`, `abs`, `min` and `max` can also appear in `requires`, `ensures` and other [contracts](/proofs/overview/). ## Reductions [Section titled “Reductions”](#reductions) `sum`, `product`, `min`, `max`, `any` and `all` with one generator argument fold a sequence without building an array: `sum(x * x for x in xs)`, `any(w == "" for w in words)`. `min` and `max` of an empty sequence panic. See [arrays](/language/arrays/#reductions). ## Ranges and `enumerate` [Section titled “Ranges and enumerate”](#ranges-and-enumerate) ```brine fn main(): print([i for i in range(5)], [i for i in range(2, 5)], [i for i in range(10, 0, -3)]) print([i for i in 0..4], [i for i in 0..=4], [i for i in 5..2]) for i, w in enumerate(["a", "b"]): print(i, w) print(enumerate(["x", "y"])) ``` Output ```text [0, 1, 2, 3, 4] [2, 3, 4] [10, 7, 4, 1] [0, 1, 2, 3] [0, 1, 2, 3, 4] [] 0 a 1 b [(0, "x"), (1, "y")] ``` * `a..b` counts from `a` up to `b - 1`, `a..=b` includes `b`. `range(...)` works as in Python, including a negative step; a step of 0 panics. * Ranges exist only as the iterable of a `for` loop or comprehension. `let r = range(0, 3)` is error E0312; write `[i for i in 0..3]` for an array. * `enumerate(xs)` returns an array of `(index, element)` tuples. ## Conversions [Section titled “Conversions”](#conversions) There are no implicit conversions. Call the type as a function: ```brine fn main(): let n = 7 let x = 2.9 print(float(n) / 2.0, int(x), int(-x), int(true)) print(str(n) + "!", str([1, 2]), int("42") + 1, float("2.5")) let small: u8 = u8(200) print(i32(n) + i32(1), small) ``` Output ```text 3.5 2 -2 1 7! [1, 2] 43 2.5 8 200 ``` * `int(x)` of a float truncates toward zero. A float that doesn’t fit, or `nan`, panics. * `i32(n)`, `u8(n)` and the other sized types check the range and panic if the value doesn’t fit: `integer conversion out of range for u8`. * `int(true)` is 1. There is no `bool(...)`: write `n != 0`. * `str(x)` works for any printable value. `int(s)` and `float(s)` parse a string and panic on bad input. See [strings](/language/strings/#converting-to-and-from-strings). * `ord(c)` is the code point of a one-character string and `chr(n)` the string for a code point. ## The program: `args`, `exit`, `panic`, `clock` [Section titled “The program: args, exit, panic, clock”](#the-program-args-exit-panic-clock) greet.br ```brine fn main() -> int: let a = args() if len(a) == 0: eprint("usage: greet ...") return 2 for name in a: print(f"hello, {name}") return 0 ``` ```sh brine run greet.br -- ann "bob smith" ``` Output ```text hello, ann hello, bob smith ``` * `args()` is the command-line arguments, without the program’s own name. With `brine run`, they go after `--`. * `main` may return nothing (exit code 0), an `int` (the exit code), or a `Result[(), str]` (an `Err(e)` prints `error: e` and exits with 1). See [functions](/language/functions/). * `exit(code)` stops the program at once with that exit code. * `panic(msg)` stops the program with a message and the source location, and exit code 101. Use it for bugs, not for expected failures (return a `Result` for those; see [error handling](/language/error-handling/)). * `clock()` is a monotonic time in seconds as a `float`. Subtract two readings to time something: ```brine fn work(n: int) -> int: var total = 0 for i in 0..n: total += i % 7 return total fn main(): let t0 = clock() let r = work(10_000_000) let dt = clock() - t0 print(r, f"{dt:.3f} s") ``` Output ```text 29999994 0.013 s ``` (The time differs from run to run.) ```brine fn main(): let n = 3 panic(f"bad value: {n}") ``` Output ```text panic: bad value: 3 --> main.br:3:5 ``` ## `embed` [Section titled “embed”](#embed) `embed("path")` is a `str` holding a file’s text, read when the program is **compiled**, so the program doesn’t need the file at run time. Use it for help text, templates and test data: ```brine const HELP = embed("help.txt") fn main(): print(HELP, end="") ``` * The path must be a string literal, relative to the directory of the `.br` file that contains the call. * The file must exist when you compile and be UTF-8 text. `\r\n` line endings become `\n`. * It works in `const`s, as above, and anywhere an expression can go. * A missing file or a non-literal path is error E0707: ```text error[E0707]: cannot embed `nope.txt` --> main.br:2:17 | 2 | print(embed("nope.txt")) | ^^^^^^^^^^ no file at `nope.txt` | = help: the path is relative to the directory of the file that contains the `embed` = run `brine explain E0707` for more about this error error: could not compile due to 1 error ``` ## Files, environment and processes [Section titled “Files, environment and processes”](#files-environment-and-processes) ```brine fn main(): match read_file("missing.txt"): case Ok(text): print(len(text.lines()), "lines") case Err(e): print("error:", e) print(write_file("out.txt", "hi\n"), file_exists("out.txt"), read_file("out.txt")) print(getenv("NO_SUCH_VARIABLE"), getenv("PATH").is_some()) print(run(["no-such-program"])) ``` Output ```text error: cannot read missing.txt: No such file or directory Ok(()) true Ok("hi\n") None true Err("cannot run no-such-program: program not found") ``` * `read_file(path)` returns the whole file as `Ok(text)`, or `Err(message)`. The file must be UTF-8 text; otherwise it’s an `Err` (`cannot read b.bin: the file is not valid UTF-8 text`). * `write_file(path, text)` creates or replaces the file. * `file_exists(path)` is `true` for an existing file or directory. * `getenv(name)` is `Some(value)` or `None`. * `run(cmd)` starts `cmd[0]` (searched on `PATH`) with the rest as arguments, waits for it, and returns `Ok(exit code)`. The program shares the terminal: its output goes straight to yours. It’s `Err` only if the program can’t start. To capture output, run it in the background or pass environment variables, use [`os.spawn`](/language/os-module/#processes). With `?` these read naturally in a function that returns `Result`: ```brine fn line_count(path: str) -> Result[int, str]: let text = read_file(path)? return Ok(len(text.lines())) fn main() -> Result[(), str]: write_file("notes.txt", "one\ntwo\nthree\n")? print(line_count("notes.txt")?) return Ok(()) ``` Output ```text 3 ``` ## Inside `par` [Section titled “Inside par”](#inside-par) `par` bodies run in an unpredictable order, so built-ins whose effects would depend on that order are compile errors there: | Allowed in `par` | Not allowed in `par` (E0503) | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | | `print`, `eprint` (lines from different iterations may come in any order), `panic`, `read_file`, `file_exists`, `getenv`, `clock`, `args`, and everything else on this page | `write_file`, `run`, `exit` | ```brine fn main(): par for i in 0..4: let r = write_file(f"out{i}.txt", "hi") ``` ```text error[E0503]: `write_file` can't be called inside a `par` body --> main.br:3:17 | 3 | let r = write_file(f"out{i}.txt", "hi") | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not allowed here | = help: its effects would happen in an unpredictable order; do it before or after the `par` = run `brine explain E0503` for more about this error error: could not compile due to 1 error ``` Compute the results in parallel, then write them afterwards. Some `os` functions have the same rule; see [the os module](/language/os-module/#inside-par). Related: [strings](/language/strings/), [arrays](/language/arrays/), [the os module](/language/os-module/), [C interop](/language/c-interop/). # C interop > Declare and call C standard library functions from Brine with extern fn, limited to integer, float and bool arguments and results. `extern fn` declares a function written in C so Brine code can call it. For now an extern function may only take and return integers, floats and bools, and only the C standard library (with the math library) is linked in. ```brine extern fn cbrt(x: float) -> float extern fn hypot(x: float, y: float) -> float extern fn atan2(y: float, x: float) -> float extern fn llabs(x: i64) -> i64 extern fn toupper(c: i32) -> i32 fn main(): print(cbrt(27.0), hypot(3.0, 4.0), atan2(1.0, 1.0) * 4.0) print(llabs(-42), chr(int(toupper(i32(ord("a")))))) ``` Output ```text 3.0 5.0 3.141592653589793 42 A ``` ## Declaring a C function [Section titled “Declaring a C function”](#declaring-a-c-function) * Write `extern fn name(params) -> type` at the top level, with no body. `name` is the C symbol, exactly as written. * Parameter and return types must be integer types (`int`/`i64`, `i32`, `u8`, …), floats (`float`/`f64`, `f32`) or `bool`. The return type may be left out for a C function that returns `void`. * Call it like any Brine function. Match the C types exactly, because Brine trusts the declaration: | C | Brine | | -------------------------- | ----------------- | | `int` | `i32` | | `unsigned int` | `u32` | | `long long`, `int64_t` | `i64` (= `int`) | | `size_t` on 64-bit systems | `u64` | | `double` | `float` (= `f64`) | | `float` | `f32` | | `bool` (C99 `_Bool`) | `bool` | | `void` result | no `->` | C’s `long` is 32 bits on Windows and 64 bits on Linux and macOS, so prefer functions that use `int`, `long long` or fixed-width types (`llabs`, not `labs`). ## What’s not supported [Section titled “What’s not supported”](#whats-not-supported) Strings, arrays, pointers, structs, callbacks and variadic functions (`printf`) can’t cross the boundary, and `ref` and `mut` parameters aren’t allowed: ```brine extern fn puts(s: str) -> i32 fn main(): puts("hi") ``` ```text error[E0311]: extern functions may only take integers, floats and bools --> main.br:1:16 | 1 | extern fn puts(s: str) -> i32 | ^ parameter has type `str` = run `brine explain E0311` for more about this error error: could not compile due to 1 error ``` There is also no way yet to link other C libraries or your own C files: the program links against the C standard library and the math library (on Linux and macOS also `pthread` and `dl`). `brine check` doesn’t link, so a misspelled or missing C function shows up only when you build or run, as error E0705 naming the undefined symbol: ```text error[E0705]: linking the program failed = note: ... undefined reference to `no_such_c_function' ``` ## What Brine knows about C calls [Section titled “What Brine knows about C calls”](#what-brine-knows-about-c-calls) Brine can’t see inside a C function, so: * The prover knows nothing about the result beyond its type. Add an `assert` to hand it a fact: `let r = rand()` then `assert r >= 0`. * The compiler allows extern calls inside `par`. Only call functions there that are safe to call from several threads at once. Pure math functions like `cbrt` are; `rand` is not. * In a [`world`](/parallelism/overview/) program, a value returned by C may differ between ranks, so it counts as rank-dependent and can’t decide whether a `world` call happens ([E0510](/reference/errors/#e0510)): ```brine extern fn rand() -> i32 fn main(): if rand() % 2 == 0: world.barrier() ``` ```text error[E0510]: some ranks might not reach this communication --> main.br:5:9 | 5 | world.barrier() | ^^^^^^^^^^^^^^^ calls `world.barrier` 4 | if rand() % 2 == 0: | --------------- this may differ between ranks | = note: every rank must make the same `world` calls in the same order, otherwise the program could hang = help: move the call out of the rank-dependent branch or loop; compute rank-specific values first, then communicate on every rank = run `brine explain E0510` for more about this error error: could not compile due to 1 error ``` Related: [built-in functions](/language/builtins/) (most math is built in: `sqrt`, `sin`, `exp`, `pow`, …), [functions](/language/functions/), [proofs](/proofs/overview/). # Control flow > if, elif and else, conditional expressions, chained comparisons, for loops over ranges, arrays and strings, while, break, continue, pass, and the logical operators. Control flow reads like Python: `if`/`elif`/`else`, `for x in ...`, `while`, `break`, `continue` and `pass`, with `and`, `or` and `not`. The differences: conditions must be `bool` (no “truthy” values), and a `for` loop over an array walks a snapshot of it. ## if, elif and else [Section titled “if, elif and else”](#if-elif-and-else) ```brine fn classify(x: int) -> str: if x < 0: return "negative" elif x == 0: return "zero" else: return "positive" fn main(): for x in [-3, 0, 8]: let parity = "even" if x % 2 == 0 else "odd" print(x, classify(x), parity) ``` Output ```text -3 negative odd 0 zero even 8 positive even ``` `a if cond else b` is the conditional expression. Both branches must have the same type. A condition must be a `bool`. An integer, a string or an array isn’t true or false on its own; compare explicitly. ```brine fn main(): let items = [1, 2] if len(items): print("not empty") ``` ```text error[E0307]: condition must be a `bool`, found `int` --> main.br:3:8 | 3 | if len(items): | ^^^^^^^^^^ not a bool | = help: compare explicitly, e.g. `x != 0` = run `brine explain E0307` for more about this error error: could not compile due to 1 error ``` Habits from other languages get a direct fix. `else if` is [E0114](/reference/errors/#e0114) (write `elif`), and `&&`, `||` and `!` are [E0105](/reference/errors/#e0105) (write `and`, `or`, `not`): ```brine fn main(): let x = 3 if x > 0 && x < 5: print("in range") ``` ```text error[E0105]: `&&` is not an operator in brine --> main.br:3:14 | 3 | if x > 0 && x < 5: | ^^ use `and` = run `brine explain E0105` for more about this error error: could not compile due to 1 error ``` ## Comparisons and logic [Section titled “Comparisons and logic”](#comparisons-and-logic) Comparisons chain, as in Python: `0 <= i < n` means `0 <= i and i < n`. `and` and `or` short-circuit: the right side runs only when it’s needed. ```brine fn main(): let xs = [4, 8, 15] let i = 2 print(0 <= i < len(xs)) print(1 < 2 < 3 > 0) print(len(xs) > 0 and xs[0] == 4) let empty: [int] = [] print(len(empty) > 0 and empty[0] == 4) # stops before the index print(not (i == 2) or i > 1) ``` Output ```text true true true false true ``` `==` and `!=` compare values by content on every type (two arrays are equal when their elements are; the same goes for maps, tuples, structs and enums). `<`, `<=`, `>` and `>=` work on numbers and strings. ## for over ranges [Section titled “for over ranges”](#for-over-ranges) `a..b` counts from `a` up to `b - 1`; `a..=b` includes `b`. `range(...)` works like Python’s and is the way to count with a step, including downward. ```brine fn main(): for i in 0..4: print(i, end=" ") print() for i in 1..=4: print(i, end=" ") print() for i in range(5): print(i, end=" ") print() for i in range(2, 5): print(i, end=" ") print() for i in range(0, 10, 3): print(i, end=" ") print() for i in range(10, 0, -3): print(i, end=" ") print() ``` Output ```text 0 1 2 3 1 2 3 4 0 1 2 3 4 2 3 4 0 3 6 9 10 7 4 1 ``` A range whose end is below its start runs zero times. A `range` step of zero panics. The loop variable has the type of the bounds: `for j in 0..n` with `n: u8` gives a `u8`. Ranges exist only in `for` loops and comprehensions; they aren’t values: ```brine fn main(): let r = 0..5 print(r) ``` ```text error[E0312]: ranges can only be used in `for` loops and comprehensions --> main.br:2:13 | 2 | let r = 0..5 | ^^^^ range used as a value | = help: to make an array, write `[i for i in 0..n]` = run `brine explain E0312` for more about this error error: could not compile due to 1 error ``` ## for over arrays, strings and maps [Section titled “for over arrays, strings and maps”](#for-over-arrays-strings-and-maps) ```brine fn main(): var xs = [1, 2, 3] for x in xs: # loops over xs as it was here xs.push(x * 10) print(xs) for i, name in enumerate(["ann", "bob"]): print(i, name) for c in "héllo": print(c, end="|") print() ``` Output ```text [1, 2, 3, 10, 20, 30] 0 ann 1 bob h|é|l|l|o| ``` * `for x in xs` walks a **snapshot** of `xs` taken when the loop starts. The body may change `xs` (push, pop, assign) without affecting the iteration, and it never runs past the end. * `enumerate(xs)` gives `(index, element)` tuples. * `for c in s` walks the UTF-8 **characters** of a string, each as a one-character `str`. (Indexing a string counts bytes; see [strings](/language/strings/).) * `for k in m` walks a map’s keys in insertion order, also over a snapshot; see [maps](/language/maps/). ## Tuple unpacking in for [Section titled “Tuple unpacking in for”](#tuple-unpacking-in-for) When the elements are tuples, name their parts in the loop header. Patterns nest, and they work the same in comprehensions. ```brine fn main(): let pairs = [("x", 1), ("y", 2)] for name, value in pairs: print(name, value) for i, (name, value) in enumerate(pairs): print(i, name, value) let flat = [f"{k}={v}" for k, v in pairs] print(flat) ``` Output ```text x 1 y 2 0 x 1 1 y 2 ["x=1", "y=2"] ``` Loop variables take annotations too: `for i: int in 0..n:`, or `for name: str, age: int in ages.items():`. ## while, break, continue and pass [Section titled “while, break, continue and pass”](#while-break-continue-and-pass) ```brine fn main(): var k = 0 while k < 10: k += 1 if k % 2 == 0: continue # skip even numbers if k > 7: break # leave the loop print(k, end=" ") print() for i in 0..3: if i == 1: pass # nothing to do here (yet) else: print(i) ``` Output ```text 1 3 5 7 0 2 ``` `break` and `continue` apply to the innermost loop. `pass` is a statement that does nothing, for a block that must not be empty. Loops that the [prover](/proofs/contracts/) should reason about can carry an `invariant`; parallel loops are written `par for` (see [parallelism](/parallelism/overview/)). ## Summary [Section titled “Summary”](#summary) * `if` / `elif` / `else`; conditions must be `bool`. * `a if cond else b` is an expression. * Comparisons chain (`0 <= i < n`); `and` / `or` short-circuit; `not` negates. * `for i in a..b` (exclusive), `a..=b` (inclusive), `range(stop)`, `range(start, stop)`, `range(start, stop, step)`. * `for x in xs` iterates over a snapshot; `enumerate` adds indexes; `for c in s` walks characters; tuple elements unpack in the loop header. * `while`, `break`, `continue`, `pass` work as in Python. # Enums and match > Plain enums, enums whose variants carry data, methods on enums, recursive enums, every kind of match pattern, guards, and the exhaustiveness check. An enum is a type whose value is one of a fixed set of *variants*, each of which may carry its own named fields. You read a variant’s data with `match`, and every `match` must handle every possible value: the compiler checks this and shows you a value you missed ([E0320](/reference/errors/#e0320)). ## Defining enums [Section titled “Defining enums”](#defining-enums) A variant is a plain tag (`Red`) or carries fields (`Circle(radius: float)`). Enums can have methods, defined inside them like a struct’s. ```brine enum Color: Red Green Blue enum Shape: Circle(radius: float) Rect(w: float, h: float) Empty fn area(self) -> float: match self: case Shape.Circle(r): return 3.14 * r * r case Shape.Rect(w, h): return w * h case Shape.Empty: return 0.0 fn main(): let c = Color.Green print(c, c == Color.Red) let shapes = [Shape.Circle(1.0), Shape.Rect(w=2.0, h=3.0), Shape.Empty] for s in shapes: print(s, s.area()) print(shapes[1] == Shape.Rect(2.0, 3.0)) ``` Output ```text Color.Green false Shape.Circle(radius=1.0) 3.14 Shape.Rect(w=2.0, h=3.0) 6.0 Shape.Empty 0.0 true ``` * Variants are always written with the enum’s name: `Color.Red`, `Shape.Circle(1.0)`. (The exception is the built-in `Option` and `Result`, whose `Some`, `None`, `Ok` and `Err` stand alone; see [Option and Result](/language/error-handling/).) * Construct a variant with data positionally or by keyword, like a struct: `Shape.Rect(2.0, 3.0)` or `Shape.Rect(w=2.0, h=3.0)`. * Enum values print as `Enum.Variant(field=value, ...)` and compare with `==` and `!=`. * Methods work as on [structs](/language/structs/#methods): `self`, `ref self`, or no `self` for a static method. An enum’s fields can’t be changed in place; a `ref self` method changes the value by assigning a whole new one (`self = Light.Off`). * Enums are values: copying one copies it. ## Reading variant data [Section titled “Reading variant data”](#reading-variant-data) Variant data can only be read with `match`, never with `.field`, because the field exists only in some variants: ```brine enum Shape: Circle(radius: float) Empty fn main(): let s = Shape.Circle(2.0) print(s.radius) ``` ```text error[E0205]: enum `Shape` has no fields --> main.br:7:13 | 7 | print(s.radius) | ^^^^^^ use `match` to read the data in a variant = run `brine explain E0205` for more about this error error: could not compile due to 1 error ``` ## Recursive enums [Section titled “Recursive enums”](#recursive-enums) A variant may contain its own enum, so trees and expressions need no extra machinery: ```brine enum Expr: Num(value: int) Add(left: Expr, right: Expr) Mul(left: Expr, right: Expr) fn eval(e: Expr) -> int: match e: case Expr.Num(v): return v case Expr.Add(l, r): return eval(l) + eval(r) case Expr.Mul(l, r): return eval(l) * eval(r) fn main(): let e = Expr.Add(Expr.Num(2), Expr.Mul(Expr.Num(3), Expr.Num(4))) print(eval(e)) print(e) ``` Output ```text 14 Expr.Add(left=Expr.Num(value=2), right=Expr.Mul(left=Expr.Num(value=3), right=Expr.Num(value=4))) ``` Enums can be generic too (`enum Tree[T]:`); see [generics](/language/generics/). ## match [Section titled “match”](#match) `match value:` is followed by `case pattern:` blocks. The first case whose pattern matches runs; there is no fall-through. `match` is a statement (it doesn’t produce a value), so assign or `return` inside the cases. ## Patterns [Section titled “Patterns”](#patterns) ```brine enum Shape: Circle(radius: float) Rect(w: float, h: float) Empty enum Expr: Num(value: int) Add(left: Expr, right: Expr) Neg(inner: Expr) fn size(n: int) -> str: match n: case 0: # a literal return "none" case 1 | 2 | 3: # alternatives return "a few" case k if k < 0: # a capture with a guard return f"invalid ({k})" case _: # anything else return "many" fn where(p: (int, int)) -> str: match p: case (0, 0): return "origin" case (0, _) | (_, 0): # alternatives (no names inside) return "on an axis" case (x, y) if x == y: return f"on the diagonal at {x}" case _: return "elsewhere" fn describe(e: Expr) -> str: match e: case Expr.Num(0): # a literal inside a variant return "zero" case Expr.Add(Expr.Num(a), Expr.Num(b)): # nested variants return f"{a} + {b}" case Expr.Neg(Expr.Neg(inner=x)): # keyword field pattern return f"double negation of {x}" case Expr.Add(left=l): # just the fields you need return f"a sum starting with {l}" case _: return "something else" fn tall(s: Shape) -> bool: match s: case Shape.Rect(h=h) if h > 10.0: return true case _: return false fn main(): for n in [0, 2, -5, 99]: print(size(n)) print(where((0, 0)), where((0, 7)), where((3, 0)), where((2, 2)), where((1, 3))) print(describe(Expr.Num(0))) print(describe(Expr.Add(Expr.Num(1), Expr.Num(2)))) print(describe(Expr.Neg(Expr.Neg(Expr.Num(5))))) print(describe(Expr.Add(Expr.Num(1), Expr.Neg(Expr.Num(2))))) print(tall(Shape.Rect(1.0, 20.0)), tall(Shape.Circle(30.0))) match "go": case "stop": print("stopping") case "go": print("going") case _: print("unknown command") ``` Output ```text none a few invalid (-5) many origin on an axis on an axis on the diagonal at 2 elsewhere zero 1 + 2 double negation of Expr.Num(value=5) a sum starting with Expr.Num(value=1) true false going ``` | Pattern | Matches | | -------------------------- | ---------------------------------------------------------------------------------------------------------- | | `_` | anything, and binds nothing | | `name` | anything, and binds it to `name` (a capture) | | `0`, `"go"`, `true` | that integer, string or bool literal | | `(a, 0)` | a tuple whose parts match, element by element | | `p1 \| p2 \| p3` | any of the alternatives; they can’t bind names ([E0321](/reference/errors/#e0321)), so use `_` inside them | | `Shape.Rect(w, _)` | that variant, with its fields matched in order | | `Expr.Add(left=l)` | that variant, with the named fields matched and the rest ignored | | `Expr.Add(Expr.Num(a), _)` | variants nested in variants, to any depth | | `pattern if condition` | the pattern, and then only if the condition (which can use its captures) is true | A capture is a new variable, so it follows the [no-shadowing rule](/language/bindings/#no-shadowing): it can’t reuse the name of a variable that is already in scope. Captures in different `case`s may share a name. A bare variant name is a capture In a pattern, `Red` on its own is a new variable named `Red` that matches anything, not the variant `Color.Red`. Always write the enum’s name: ```brine enum Color: Red Green fn main(): let c = Color.Green match c: case Red: print("red") case Green: print("green") ``` This prints `red`. The compiler warns that the second case can never be reached: ```text warning[W0002]: this case can never be reached --> main.br:10:9 | 10 | case Green: | ^^^^^^^^^^ unreachable | = help: an earlier `case` already matches every value = run `brine explain W0002` for more about this error ``` Write `case Color.Red:` and `case Color.Green:`. ## Exhaustiveness [Section titled “Exhaustiveness”](#exhaustiveness) Every `match` must handle every possible value. When it doesn’t, the error shows a value that no case handles, so a new enum variant can never slip through unnoticed: ```brine enum Shape: Circle(radius: float) Rect(w: float, h: float) Empty fn area(s: Shape) -> float: match s: case Shape.Circle(r): return 3.14 * r * r case Shape.Empty: return 0.0 fn main(): print(area(Shape.Empty)) ``` ```text error[E0320]: this `match` doesn't handle every possible value --> main.br:7:11 | 7 | match s: | ^ of type `Shape` | = note: not handled: `Shape.Rect(_, _)` = help: add `case Shape.Rect(_, _):`, or a catch-all `case _:` = run `brine explain E0320` for more about this error error: could not compile due to 1 error ``` The check looks inside tuples and nested variants too: matching `(true, _)` and `(false, true)` on a `(bool, bool)` reports `(false, false)` as not handled. Cases with a guard don’t count, because the guard may be false. Integers and strings have too many values to list, so a `match` on them always needs `case _:` (or a plain capture, which matches everything): ```brine fn main(): let n = 4 match n: case 0: print("zero") case k if k > 0: print("positive") ``` ```text error[E0320]: this `match` doesn't handle every possible value --> main.br:3:11 | 3 | match n: | ^ of type `int` | = note: cases with an `if` guard don't count, since the guard may be false = help: add a catch-all `case _:` at the end = run `brine explain E0320` for more about this error error: could not compile due to 1 error ``` A case that can never be reached because earlier cases already match everything it would is the warning W0002. ## Summary [Section titled “Summary”](#summary) * `enum Name:` lists variants: plain tags or `Variant(field: Type, ...)`. Enums can have methods, be recursive and be generic. * Write variants with the enum’s name, in expressions and in patterns. * Read variant data with `match`, never `.field`. * Patterns: `_`, captures, literals, tuples, `|` alternatives (no names inside), variants with positional or keyword fields, nested to any depth, and `if` guards. * `match` must be exhaustive (E0320); guarded cases don’t count; integers and strings need `case _:`. # Errors: Option and Result > Option and Result for values that may be missing and operations that may fail, their methods, the ? operator and its rules, main returning Result, file I/O errors as values, and when to panic instead. Brine has no exceptions. A value that may be missing is an `Option[T]`, an operation that may fail returns a `Result[T, E]`, and `x?` passes a `None` or `Err` straight back to the caller. Panics are for bugs, not for failures you expect. ## Option and Result [Section titled “Option and Result”](#option-and-result) Both are generic enums that every program has without an import: ```brine enum Option[T]: Some(value: T) None enum Result[T, E]: Ok(value: T) Err(error: E) ``` Their variants are written on their own: `Some(3)`, `None`, `Ok(x)`, `Err("bad input")`. Handle them with [`match`](/language/enums-and-match/), which makes you deal with both cases: ```brine fn find(names: [str], target: str) -> Option[int]: for i, name in enumerate(names): if name == target: return Some(i) return None fn parse_bit(s: str) -> Result[int, str]: if s == "0": return Ok(0) if s == "1": return Ok(1) return Err(f"not a bit: {s}") fn main(): let names = ["ann", "bob"] match find(names, "bob"): case Some(i): print(f"bob is at {i}") case None: print("no bob") match parse_bit("x"): case Ok(bit): print("bit", bit) case Err(e): print("failed:", e) ``` Output ```text bob is at 1 failed: not a bit: x ``` The error type `E` can be anything: a `str` message, an `int` code, or your own enum of failure kinds. `None` on its own doesn’t say which `Option` it is. Where the context doesn’t either, annotate: `let n: Option[int] = None` (otherwise [E0332](/reference/errors/#e0332)). ## Methods [Section titled “Methods”](#methods) ```brine fn parse_bit(s: str) -> Result[int, str]: if s == "0": return Ok(0) if s == "1": return Ok(1) return Err(f"not a bit: {s}") fn main(): let good = parse_bit("1") let bad = parse_bit("7") print(good.is_ok(), good.is_err(), bad.is_err()) print(good.unwrap(), bad.unwrap_or(-1)) print(good.ok(), bad.ok()) let some: Option[str] = Some("x") let none: Option[str] = None print(some.is_some(), none.is_none()) print(some.unwrap(), none.unwrap_or("default")) ``` Output ```text true false true 1 -1 Some(1) None true true x default ``` | Method | On | Returns | | ------------------------ | -------------- | -------------------------------------------------------- | | `is_some()`, `is_none()` | `Option[T]` | `bool` | | `is_ok()`, `is_err()` | `Result[T, E]` | `bool` | | `unwrap()` | both | the value inside `Some`/`Ok`; **panics** on `None`/`Err` | | `unwrap_or(default)` | both | the value inside, or `default` | | `ok()` | `Result[T, E]` | `Option[T]`: `Some(value)` or `None` (drops the error) | That’s the complete list. To get at an error’s value, or to turn an `Option` into a `Result`, use `match`. `unwrap()` is for when a `None` or `Err` would be a bug. If it happens, the program stops: unwrap.br ```brine fn main(): let port: Option[int] = None print(port.unwrap()) ``` Output ```text panic: unwrap() called on None --> unwrap.br:3:11 ``` ## The ? operator [Section titled “The ? operator”](#the--operator) `x?` unwraps an `Option` or `Result`: it gives the value inside `Some`/`Ok`, or returns the `None`/`Err` from the current function at once. ```brine fn parse_bit(s: str) -> Result[int, str]: if s == "0": return Ok(0) if s == "1": return Ok(1) return Err(f"not a bit: {s}") fn parse_bits(text: str) -> Result[int, str]: var n = 0 for c in text: n = n * 2 + parse_bit(c)? # an Err is returned to the caller here return Ok(n) fn first_even(xs: [int]) -> Option[int]: for x in xs: if x % 2 == 0: return Some(x) return None fn half_first_even(xs: [int]) -> Option[int]: let e = first_even(xs)? # None is returned here return Some(e / 2) fn main(): print(parse_bits("101"), parse_bits("1x1")) print(half_first_even([3, 8]), half_first_even([1, 3])) ``` Output ```text Ok(5) Err("not a bit: x") Some(4) None ``` The rules: * `?` on an `Option` needs the function to return an `Option[...]`. * `?` on a `Result[T, E]` needs the function to return a `Result[..., E]` with the **same** error type `E`. Errors are never converted for you. * `?` can appear anywhere in an expression, as in `n * 2 + parse_bit(c)?`. Breaking a rule is [E0341](/reference/errors/#e0341): ```brine fn first_even(xs: [int]) -> Option[int]: for x in xs: if x % 2 == 0: return Some(x) return None fn main(): let e = first_even([1, 2])? print(e) ``` ```text error[E0341]: `?` needs the function to return Option[...] --> main.br:8:13 | 8 | let e = first_even([1, 2])? | ^^^^^^^^^^^^^^^^^^^ this returns from a function that returns `()` | = help: change the return type to `Option[...]`, or handle the value with `match` = run `brine explain E0341` for more about this error error: could not compile due to 1 error ``` ```brine fn parse_bit(s: str) -> Result[int, str]: if s == "1": return Ok(1) return Err(f"not a bit: {s}") fn checked(s: str) -> Result[int, int]: let b = parse_bit(s)? return Ok(b) fn main(): print(checked("1")) ``` ```text error[E0341]: `?` would return a `str` error from a function whose errors are `int` --> main.br:7:13 | 7 | let b = parse_bit(s)? | ^^^^^^^^^^^^^ the error here is `str` | = help: convert it with `match`, or make the function's error type match = run `brine explain E0341` for more about this error error: could not compile due to 1 error ``` ### Converting between error types [Section titled “Converting between error types”](#converting-between-error-types) Convert with `match` where the types differ. To go from a `Result` to an `Option`, use `.ok()` and then `?`: ```brine fn parse_bit(s: str) -> Result[int, str]: if s == "1": return Ok(1) return Err(f"not a bit: {s}") fn code_of(s: str) -> Result[int, int]: match parse_bit(s): case Ok(b): return Ok(b) case Err(_): return Err(2) fn bit_or_none(s: str) -> Option[int]: let b = parse_bit(s).ok()? # Result to Option, then `?` return Some(b) fn main(): print(code_of("1"), code_of("x"), bit_or_none("1"), bit_or_none("x")) ``` Output ```text Ok(1) Err(2) Some(1) None ``` ## main returning Result [Section titled “main returning Result”](#main-returning-result) `main` may return `Result[(), E]`, which lets it use `?`. An `Err` that reaches the end of `main` prints `error: `and the error to stderr, and the program exits with code 1. ## File I/O errors are values [Section titled “File I/O errors are values”](#file-io-errors-are-values) The file and process built-ins never crash on failure; they return a `Result` (or an `Option` or `bool`) and the failure is a value you handle: | Built-in | Returns | | ------------------------ | ------------------------------------------------------------------------------- | | `read_file(path)` | `Result[str, str]`: the text (must be UTF-8), or the reason it couldn’t be read | | `write_file(path, text)` | `Result[(), str]` | | `file_exists(path)` | `bool` | | `getenv(name)` | `Option[str]` | | `run(cmd)` | `Result[int, str]`: `Ok(exit code)`, or `Err` if it can’t start | Together with `?` and a `main` that returns `Result`, a program reads like the happy path and still handles every failure: lines.br ```brine fn count_lines(path: str) -> Result[int, str]: let text = read_file(path)? return Ok(len(text.lines())) fn main() -> Result[(), str]: write_file("notes.txt", "one\ntwo\nthree\n")? print(count_lines("notes.txt")?) match count_lines("missing.txt"): case Ok(n): print(n) case Err(e): print("skipped:", e) print(count_lines("settings.txt")?) print("not reached") return Ok(()) ``` Output (exit code 1) ```text 3 skipped: cannot read missing.txt: No such file or directory error: cannot read settings.txt: No such file or directory ``` The `os` module’s functions work the same way; see [the os module](/language/os-module/) and [built-ins](/language/builtins/). ## Panics vs expected failures [Section titled “Panics vs expected failures”](#panics-vs-expected-failures) Use `Option` and `Result` for failures a correct program must expect: missing files, bad input, a key that isn’t there. Use a panic for a bug: a state that should be impossible. A panic stops the program with a message, the source location, and exit code 101. You can’t catch one. Panics come from: * the language’s own checks: integer overflow, division by zero, an out-of-bounds index, an out-of-range conversion (see [values and types](/language/values-and-types/#panics-overflow-division-by-zero-bounds)); * `unwrap()` on `None` or `Err`, and parsing bad text with `int(s)` or `float(s)`; * `assert cond`, which checks at run time and also tells the prover the condition holds afterwards (see [contracts](/proofs/contracts/)); * `panic(msg)`, which you call yourself. average.br ```brine fn average(xs: [float]) -> float: if len(xs) == 0: panic("average of an empty array") return sum(x for x in xs) / float(len(xs)) fn main(): print(average([1.0, 2.0])) let none: [float] = [] print(average(none)) ``` Output ```text 1.5 panic: average of an empty array --> average.br:3:9 ``` Better still, turn the bug into something the compiler checks: `requires len(xs) > 0` makes every caller prove the array isn’t empty, at compile time (see [contracts](/proofs/contracts/)). ## Summary [Section titled “Summary”](#summary) * `Option[T]` is `Some(value)` or `None`; `Result[T, E]` is `Ok(value)` or `Err(error)`. Both are always available. * Methods: `is_some`, `is_none`, `is_ok`, `is_err`, `unwrap` (panics), `unwrap_or`, `ok`. Everything else is `match`. * `x?` returns `None`/`Err` early; the function must return `Option[...]`, or `Result[..., E]` with the same `E` (E0341). * `main` may return `Result[(), E]`: an `Err` prints `error: ...` and exits with 1. * File and process built-ins return failures as values. * Panics (exit 101) are for bugs and can’t be caught. # Functions > Defining functions, parameter and return types, ref and mut parameters, local references, why references never dangle, what main may return, and the evaluation-order rule. Functions are declared with `fn`, with a type on every parameter and the return type after `->`. Arguments are passed by value; a function changes the caller’s variable only through a `ref` parameter, and the caller writes `ref` too, so every change is visible at the call site. ## Defining and calling functions [Section titled “Defining and calling functions”](#defining-and-calling-functions) ```brine fn area(w: float, h: float) -> float: return w * h fn greet(name: str): print(f"hello, {name}") fn divmod(a: int, b: int) -> (int, int): return (a / b, a % b) fn main(): print(area(2.0, 3.0)) print(area(w=2.0, h=3.0)) greet("ann") let (q, r) = divmod(17, 5) print(q, r) print(is_even(10)) fn is_even(n: int) -> bool: # order doesn't matter; recursion is fine if n == 0: return true return not is_even(n - 1) ``` Output ```text 6.0 6.0 hello, ann 3 2 true ``` * Parameter types are required. Local variable types are inferred. * A function without `-> T` returns nothing (its type is `()`); `return` on its own leaves it early. * Return several values as a tuple and unpack them at the call. * Arguments can be named, in the parameters’ order: `area(w=2.0, h=3.0)`. There are no default values. * Functions can be declared in any order, and may call each other recursively. They are declared at the top level of a module (or as methods); there are no nested functions. A function with a return type must return on every path: ```brine fn sign(x: int) -> int: if x > 0: return 1 elif x < 0: return -1 fn main(): print(sign(5)) ``` ```text error[E0306]: function `sign` may reach the end without returning a value --> main.br:1:4 | 1 | fn sign(x: int) -> int: | ^^^^ expected to return `int` | = help: add a `return` statement at the end of the function = run `brine explain E0306` for more about this error error: could not compile due to 1 error ``` Functions can be generic (`fn first[T](xs: [T]) -> Option[T]`); see [generics](/language/generics/). They can also carry `requires` and `ensures` contracts that the compiler proves; see [contracts](/proofs/contracts/). ## Parameters are read-only [Section titled “Parameters are read-only”](#parameters-are-read-only) A plain parameter is a read-only copy of the argument. Assigning to it is an error that points you at the two alternatives, `mut` and `ref`: ```brine fn dec(n: int) -> int: n -= 1 return n fn main(): print(dec(3)) ``` ```text error[E0403]: cannot assign to parameter `n` --> main.br:2:5 | 2 | n -= 1 | ^ parameters are read-only 1 | fn dec(n: int) -> int: | - declared here | = help: to change a local copy, declare it `mut n: ...`; to change the caller's variable, declare it `ref n: ...` and call with `ref` = run `brine explain E0403` for more about this error error: could not compile due to 1 error ``` ## mut parameters [Section titled “mut parameters”](#mut-parameters) `mut` makes the parameter a local copy that the function may change. The caller’s value is untouched. ```brine fn countdown(mut n: int) -> [int]: var out: [int] = [] while n > 0: out.push(n) n -= 1 return out fn main(): let start = 3 print(countdown(start), start) ``` Output ```text [3, 2, 1] 3 ``` This works for arrays too, and it’s cheap: the array is copied only when the function first changes it (copy-on-write). ```brine fn add_all(mut xs: [int], k: int) -> [int]: for i in 0..len(xs): xs[i] += k return xs fn main(): let base = [1, 2, 3] let more = add_all(base, 10) print(base, more) ``` Output ```text [1, 2, 3] [11, 12, 13] ``` ## ref parameters [Section titled “ref parameters”](#ref-parameters) A `ref` parameter is the caller’s variable itself: changes to it change the caller’s variable. The caller writes `ref` before the argument, so you can see at the call site which variables a call may change. ```brine fn fill(ref xs: [int], v: int): for i in 0..len(xs): xs[i] = v fn swap(ref a: int, ref b: int): let t = a a = b b = t fn main(): var data = [0] * 4 fill(ref data, 7) print(data) var x = 1 var y = 2 swap(ref x, ref y) print(x, y) var grid = [[0, 0], [0, 0]] fill(ref grid[1], 5) # an element or a field works too print(grid) ``` Output ```text [7, 7, 7, 7] 2 1 [[0, 0], [5, 5]] ``` The argument must be a `var` (or an element or field of one). Leaving out `ref` at the call is an error: ```brine fn fill(ref xs: [int], v: int): for i in 0..len(xs): xs[i] = v fn main(): var data = [0] * 3 fill(data, 7) ``` ```text error[E0402]: parameter `xs` is a `ref`; pass the argument with `ref` --> main.br:7:10 | 7 | fill(data, 7) | ^^^^ the callee may modify this | = help: passing by reference is always visible at the call site in brine = run `brine explain E0402` for more about this error error: could not compile due to 1 error ``` Methods that change their receiver take `ref self`; see [structs](/language/structs/#methods). ## Local references [Section titled “Local references”](#local-references) `let d = ref data` makes `d` a second name for the variable `data`. Reading or changing `d` reads or changes `data`. ```brine fn main(): var data = [1, 2, 3] let d = ref data # `d` is another name for `data` d[0] = 100 d.push(4) print(data) ``` Output ```text [100, 2, 3, 4] ``` The target must be a whole variable (`ref data`, not `ref data[0]`). ## References never dangle [Section titled “References never dangle”](#references-never-dangle) References in Brine are *second-class*: you can create them only as parameters and local names, never store them in a struct, an array or a map, and never return them. `ref` isn’t part of any type, so `-> ref int` or a field `r: ref int` doesn’t parse. Storing `d` from above in an array stores a copy of the value. Because of that, a reference can’t outlive the variable it names, and the compiler always knows which variable each reference points to. There is no lifetime syntax and no borrow checker to satisfy. ### No aliasing between ref arguments [Section titled “No aliasing between ref arguments”](#no-aliasing-between-ref-arguments) During a call, a `ref` argument must be the only way to reach that variable. Passing the same variable as `ref` twice (even two different elements of one array) is an error: ```brine fn swap(ref a: int, ref b: int): let t = a a = b b = t fn main(): var xs = [1, 2] swap(ref xs[0], ref xs[1]) ``` ```text error[E0405]: `xs` is passed as `ref` twice --> main.br:8:25 | 8 | swap(ref xs[0], ref xs[1]) | ^^^^^ second reference here | ----- passed as `ref` here | = note: a `ref` argument must be the only access to that variable during the call = help: copy the value first: `let xs_copy = ...` = run `brine explain E0405` for more about this error error: could not compile due to 1 error ``` Swap two elements of one array in place instead: `let t = xs[0]`, `xs[0] = xs[1]`, `xs[1] = t`. Passing the same variable once by `ref` and once by value is fine: the by-value argument is the value from before the call. ## Evaluation order [Section titled “Evaluation order”](#evaluation-order) An expression that changes a variable in place (a `ref` argument, a `ref self` method such as `xs.pop()`, `m.remove(k)`) can’t also read that variable somewhere else in the same expression, or change it a second time. The result would then depend on the order the parts are evaluated in, so Brine rules it out and the order never matters. ```brine fn main(): var xs = [1, 2, 3] print(xs.pop(), xs) ``` ```text error[E0405]: `xs` is changed and also read in the same expression --> main.br:3:11 | 3 | print(xs.pop(), xs) | ^^^^^^^^ changed here | -- read here | = help: make the change in its own statement first, then use the result = run `brine explain E0405` for more about this error error: could not compile due to 1 error ``` Make the change in its own statement first. Reading the variable in the changing call’s own receiver and arguments is fine, because they are evaluated before the call. So are the two sides of `and`/`or`, which run one after the other. ```brine fn bump(ref x: int) -> int: x += 1 return x fn main(): var xs = [1, 2] xs.push(len(xs)) # fine: the argument is read before the call let last = xs.pop() # change first... print(xs, last) # ...then read var n = 1 let after = bump(ref n) print(after + n) ``` Output ```text [1, 2] 2 4 ``` ## main [Section titled “main”](#main) `main` takes no parameters (command-line arguments come from `args()`) and returns one of three things: * nothing: the program exits with code 0; * an `int`: the exit code; * a `Result[(), E]`: `Ok(())` exits with 0, and an `Err(e)` prints `error: `and the error to stderr and exits with 1. This lets `main` use the `?` operator. config.br ```brine fn parse_port(s: str) -> Result[int, str]: if not s.is_digit(): return Err(f"not a port number: {s}") return Ok(int(s)) fn main() -> Result[(), str]: let port = parse_port("80x")? print(f"listening on {port}") return Ok(()) ``` Output (stderr, exit code 1) ```text error: not a port number: 80x ``` See [Option and Result](/language/error-handling/) for `?`. A panic exits with code 101, and `exit(code)` ends the program at once from anywhere. Caution In Brine 0.4.0, `brine check` doesn’t check `main`’s signature. A `main` that returns something else (a `str`, `Result[int, str]`) passes `brine check` and is rejected by `brine run` and `brine build` with [E0706](/reference/errors/#e0706). ## Functions aren’t values (yet) [Section titled “Functions aren’t values (yet)”](#functions-arent-values-yet) A function name can only be called. It can’t be stored in a variable, passed as an argument or returned; first-class functions are planned. ```brine fn square(x: int) -> int: return x * x fn main(): let f = square print(f(3)) ``` ```text error[E0310]: `square` is a function; functions can't be used as values yet --> main.br:5:13 | 5 | let f = square | ^^^^^^ call it with `(...)` = run `brine explain E0310` for more about this error error[E0303]: `f` is a variable of type `?`, not a function --> main.br:6:11 | 6 | print(f(3)) | ^ not callable = run `brine explain E0303` for more about this error error: could not compile due to 2 errors ``` To choose behavior at run time, use an [enum](/language/enums-and-match/) and `match` on it. ## Rules [Section titled “Rules”](#rules) * Every parameter has a type; the return type follows `->` (none means `()`). * Parameters are read-only. `mut p: T` is a changeable local copy; `ref p: T` is the caller’s variable, and the caller writes `ref` too. * `let r = ref v` makes `r` another name for the variable `v`. * References can’t be stored or returned, so they never dangle. * A variable can’t be passed as `ref` twice in one call. * An expression that changes a variable can’t read it elsewhere or change it again. * `main` returns nothing, an `int` exit code, or `Result[(), E]`. * Functions aren’t values. # Generics > Generic functions, structs and enums, how type arguments are inferred or written out, methods with their own type parameters, and how generic code is specialized and checked per use. Functions, structs and enums can take type parameters in square brackets: `fn first[T](xs: [T]) -> Option[T]`. Each use with new type arguments makes a specialized copy of the code (*monomorphization*), which is checked, proven and compiled like hand-written code, so generics cost nothing at run time. ## Generic functions [Section titled “Generic functions”](#generic-functions) ```brine fn first[T](xs: [T]) -> Option[T]: if len(xs) == 0: return None return Some(xs[0]) fn swap[A, B](p: (A, B)) -> (B, A): return (p.1, p.0) fn main(): print(first([3, 4]), first(["a", "b"])) print(swap((1, "one"))) let empty: [float] = [] print(first(empty)) print(first[int]([])) ``` Output ```text Some(3) Some("a") ("one", 1) None None ``` Type parameters go in brackets after the name, and can be used anywhere a type can in the signature and the body. ## Generic structs and enums [Section titled “Generic structs and enums”](#generic-structs-and-enums) ```brine struct Pair[A, B]: first: A second: B fn swapped(self) -> Pair[B, A]: return Pair(first=self.second, second=self.first) enum Tree[T]: Leaf Node(left: Tree[T], value: T, right: Tree[T]) fn count[T](t: Tree[T]) -> int: match t: case Tree.Leaf: return 0 case Tree.Node(l, _, r): return count(l) + 1 + count(r) fn main(): let p = Pair(first=1, second="one") # Pair[int, str], inferred let q = Pair[int, str](first=2, second="two") # written out print(p, q, p.swapped()) let t = Tree.Node(Tree.Leaf, "m", Tree.Node(Tree.Leaf, "z", Tree.Leaf)) print(count(t)) let none: Option[int] = None var stack: [Pair[str, int]] = [] stack.push(Pair("x", 1)) print(none, stack) ``` Output ```text Pair(first=1, second="one") Pair(first=2, second="two") Pair(first="one", second=1) 2 None [Pair(first="x", second=1)] ``` In a type, write the arguments in brackets: `Pair[str, int]`, `[Pair[str, int]]`, `Option[int]`. The built-in [`Option[T]` and `Result[T, E]`](/language/error-handling/) are generic enums like `Tree[T]`. ## Inference and explicit type arguments [Section titled “Inference and explicit type arguments”](#inference-and-explicit-type-arguments) Type arguments are inferred from the arguments and from the type the context expects. When neither says what a type parameter is, write it out after the name: `make[int]()`, `Pair[int, str](...)`, `first[int]([])`. ```brine fn make[T]() -> [T]: return [] fn main(): let xs = make() print(xs) ``` ```text error[E0332]: can't infer type parameter `T` of `make` --> main.br:5:14 | 5 | let xs = make() | ^^^^^^ type unknown here | = help: give the type explicitly, e.g. `make[int](...)` = run `brine explain E0332` for more about this error error: could not compile due to 1 error ``` Here `let xs: [int] = make()` works too, because the annotation is the expected type. The same goes for `None` on its own: `let n: Option[int] = None`. Each type parameter stands for one type, so `fn same[T](a: T, b: T)` called as `same(1, "one")` is a type mismatch ([E0301](/reference/errors/#e0301)). ## Methods with their own type parameters [Section titled “Methods with their own type parameters”](#methods-with-their-own-type-parameters) A method can have type parameters of its own, next to its type’s: ```brine struct Box[T]: v: T fn pair[U](self, u: U) -> (T, U): return (self.v, u) fn main(): let b = Box(v=1) print(b.pair("x"), b.pair[float](2.5)) ``` Output ```text (1, "x") (1, 2.5) ``` `b.pair[float](2.5)` gives `U` explicitly; `2.5` alone would also have inferred it. ## Monomorphization: checked per use [Section titled “Monomorphization: checked per use”](#monomorphization-checked-per-use) There are no interfaces or trait bounds to declare. Instead, a generic function’s body is checked again for each set of type arguments it’s used with. `largest` compares with `>`, so it works for any type that supports `>`: ```brine fn largest[T](xs: [T]) -> T: var best = xs[0] for x in xs: if x > best: best = x return best fn main(): print(largest([3, 9, 2])) print(largest(["pear", "apple", "fig"])) print(largest([1.5, -0.5])) ``` Output ```text 9 pear 1.5 ``` Using it with a struct, which can’t be ordered, is an error in the body, with a note saying which use caused it: ```brine struct Point: x: int y: int fn largest[T](xs: [T]) -> T: var best = xs[0] for x in xs: if x > best: best = x return best fn main(): print(largest([3, 9, 2])) print(largest([Point(1, 2), Point(3, 4)])) ``` ```text error[E0305]: values of type `Point` can't be ordered --> main.br:8:12 | 8 | if x > best: | ^^^^^^^^ only numbers and strings support `<` | = note: in `largest` with T = Point = run `brine explain E0305` for more about this error error: could not compile due to 1 error ``` Each specialized copy is also proven like ordinary code: a generic function can carry [contracts](/proofs/contracts/) (`requires len(xs) > 0`), and every call must satisfy them. ## Summary [Section titled “Summary”](#summary) * Type parameters in brackets: `fn f[T](x: T)`, `struct Pair[A, B]`, `enum Tree[T]`, and on methods: `fn pair[U](self, u: U)`. * Type arguments are inferred from arguments and the expected type, or written out: `make[int]()`, `Pair[int, str](...)`. * Each new set of type arguments makes a specialized copy, checked and proven like ordinary code. Operations a type doesn’t support are errors at that use. # Maps > brine maps ({K: V}) are hash maps that remember insertion order: reading, inserting, removing, iterating, allowed key types and value semantics. A map `{K: V}` stores values by key and remembers the order keys were first inserted. Reading a key that isn’t there **panics**, so test with `in` or use `m.get(k)`, which returns an `Option`. Like arrays, maps are values: `let b = a` copies. ```brine fn main(): var ages = {"ann": 31, "bob": 27} ages["cy"] = 40 ages["ann"] += 1 print(ages, len(ages)) if "bob" in ages: print(ages["bob"]) print(ages.get("zed"), ages.get("zed", 0)) ``` Output ```text {"ann": 32, "bob": 27, "cy": 40} 3 27 None 0 ``` ## Making maps [Section titled “Making maps”](#making-maps) Write `key: value` pairs in braces. The key and value types are inferred from the first pair. An empty map needs a type annotation: ```brine fn main(): let squares = {1: 1, 2: 4, 3: 9} var counts: {str: int} = {} for w in ["a", "b", "a", "c", "a"]: if w in counts: counts[w] += 1 else: counts[w] = 1 print(squares, counts) ``` Output ```text {1: 1, 2: 4, 3: 9} {"a": 3, "b": 1, "c": 1} ``` `let m = {}` with no type is error [E0308](/reference/errors/#e0308). A map type can appear anywhere a type can: `fn count(words: [str]) -> {str: int}`, a struct field, a map of arrays `{str: [int]}`. ## Reading and writing [Section titled “Reading and writing”](#reading-and-writing) * `m[k]` reads the value for `k`. A missing key panics. * `m[k] = v` inserts `k`, or replaces its value if it’s already there. Replacing keeps the key’s original position. * `m[k] += 1` and the other compound assignments change a value in place. The key must exist. * `k in m` and `k not in m` test for a key. * `m.get(k)` returns `Some(value)` or `None`; `m.get(k, default)` returns the value or `default`. * `m.remove(k)` removes `k` and returns `true` if it was there. ```brine fn main(): var m: {str: int} = {} m["z"] = 1 m["a"] = 2 m["z"] = 3 print(m) let first = m.remove("z") let second = m.remove("z") print(first, second, m) m["z"] = 4 print(m) ``` Output ```text {"z": 3, "a": 2} true false {"a": 2} {"a": 2, "z": 4} ``` A key that was removed and inserted again goes to the end. ### Missing keys [Section titled “Missing keys”](#missing-keys) ```brine fn main(): let ages = {"ann": 31} print(ages["zed"]) ``` Output ```text panic: key "zed" is not in the map --> main.br:3:11 ``` A missing key usually means a bug or bad input. When it’s expected, say so with `get`: ```brine fn main(): let prices = {"apple": 3, "pear": 4} let order = ["apple", "kiwi", "pear"] var total = 0 for item in order: match prices.get(item): case Some(p): total += p case None: print("no price for", item) print(total, prices.get("kiwi", 0)) ``` Output ```text no price for kiwi 7 0 ``` ## Looping over a map [Section titled “Looping over a map”](#looping-over-a-map) `for k in m` walks the keys in insertion order. `m.items()` gives `(key, value)` pairs to unpack: ```brine fn main(): var stock = {"apple": 3, "pear": 0, "fig": 7} for name in stock: if stock[name] == 0: stock.remove(name) for name, n in stock.items(): print(name, n) print(stock.keys(), stock.values(), stock.items()) ``` Output ```text apple 3 fig 7 ["apple", "fig"] [3, 7] [("apple", 3), ("fig", 7)] ``` The loop walks a snapshot of the map taken when it starts, so the body may insert and remove keys. Keys added in the body don’t show up in that loop. `keys()`, `values()` and `items()` return new arrays, in insertion order. ## Key types [Section titled “Key types”](#key-types) Keys can be integers, `bool`, `str`, enums, and arrays, tuples and structs made of those. Floats can’t be keys: ```brine struct Cell: row: int col: int enum Color: Red Green fn main(): var grid: {(int, int): str} = {} grid[(0, 1)] = "x" let cells = {Cell(row=1, col=2): 5} let lists = {Color.Red: [1, 2], Color.Green: []} print(grid, cells[Cell(row=1, col=2)], lists[Color.Red]) ``` Output ```text {(0, 1): "x"} 5 [1, 2] ``` ```brine fn main(): let m: {float: str} = {} ``` ```text error[E0335]: `float` can't be used as a map key --> main.br:2:12 | 2 | let m: {float: str} = {} | ^^^^^^^^^^^^ invalid key type | = note: map keys must be integers, `bool`, `str`, enums, or arrays, tuples and structs of those = help: floats aren't allowed because `nan != nan`; use an integer key instead = run `brine explain E0335` for more about this error error: could not compile due to 1 error ``` A key must be equal to itself for lookups to work, and `nan` isn’t. To key by a measurement, round it to an integer first (for example, millimetres instead of metres). ## Maps are values [Section titled “Maps are values”](#maps-are-values) Assigning a map or passing it to a function copies it. The copy is lazy (copy-on-write), so it costs nothing until one side changes: ```brine fn main(): var a = {"x": 1} let b = a a["x"] = 2 a["y"] = 3 print(a, b) print({"p": 1, "q": 2} == {"q": 2, "p": 1}) ``` Output ```text {"x": 2, "y": 3} {"x": 1} true ``` `==` compares keys and values and ignores order. To let a function change the caller’s map, take it as `ref`: `fn add(ref m: {str: int}, k: str)`. See [functions](/language/functions/). `par` bodies may read maps but not change outer ones. See [parallelism](/parallelism/overview/). ## Reference [Section titled “Reference”](#reference) | Operation | Result | Notes | | ---------------------- | ----------- | ----------------------------------------- | | `{k1: v1, k2: v2}` | `{K: V}` | empty: `var m: {K: V} = {}` | | `len(m)` | `int` | number of keys | | `m[k]` | `V` | panics if `k` is missing | | `m[k] = v` | | insert or replace | | `k in m`, `k not in m` | `bool` | | | `m.get(k)` | `Option[V]` | | | `m.get(k, default)` | `V` | | | `m.remove(k)` | `bool` | `true` if `k` was there | | `m.keys()` | `[K]` | insertion order | | `m.values()` | `[V]` | insertion order | | `m.items()` | `[(K, V)]` | insertion order | | `for k in m` | | keys, in insertion order, over a snapshot | | `m == n` | `bool` | same keys and values, any order | ## Rules [Section titled “Rules”](#rules) * Maps remember insertion order; replacing a value keeps the key’s place. * `m[k]` panics on a missing key. Use `in` or `get` when a key may be absent. * Keys can’t be floats, or contain floats. * `let b = a` copies; changing `b` never changes `a`. * A loop over a map sees a snapshot, so the body may change the map. Related: [arrays](/language/arrays/), [strings](/language/strings/), [generics](/language/generics/) (maps work with type parameters: `fn invert[K, V](m: {K: V}) -> {V: K}`), [error handling](/language/error-handling/). # Modules > How Brine splits a program across files with import, import ... as and from ... import, where module files live, and the rules for imported names. A module is a `.br` file. `import geometry` loads `geometry.br` from the main file’s directory, and `import util.strings` loads `util/strings.br`. You then write `geometry.area(...)`, or bring names in directly with `from geometry import area`. There are no visibility modifiers: every top-level item in a module can be imported. ```brine import geometry # geometry.br import util.strings as text # util/strings.br, used as `text` from geometry import Point, area # use these two names directly fn main(): let q = geometry.Point(x=3.0, y=4.0) print(Point(x=0.0, y=0.0).dist(q), area(2.0, 3.0), text.shout("hi")) ``` ## A project with modules [Section titled “A project with modules”](#a-project-with-modules) In a project made by `brine new`, the main file is `src/main.br`, so modules live under `src/`: ```text shapes/ ├── brine.toml ├── AGENTS.md ├── README.md └── src/ ├── main.br ├── geometry.br └── util/ └── strings.br ``` A module holds the same items as any file: functions, structs, enums, constants and tests. It has no `main`: src/geometry.br ```brine # Points, rectangles and areas. const ORIGIN_X = 0.0 struct Point: x: float y: float fn dist(self, other: Point) -> float: let dx = self.x - other.x let dy = self.y - other.y return sqrt(dx * dx + dy * dy) struct Rect: w: float h: float fn area(self) -> float: return area(self.w, self.h) fn area(w: float, h: float) -> float: return w * h test "a 2 by 3 rectangle": assert Rect(w=2.0, h=3.0).area() == 6.0 ``` src/util/strings.br ```brine # Small text helpers. fn shout(s: str) -> str: return s.upper() + "!" fn pad_left(s: str, width: int) -> str: if len(s) >= width: return s return " ".repeat(width - len(s)) + s ``` src/main.br ```brine import geometry import util.strings as text from geometry import Point, area fn main(): let p = Point(x=0.0, y=0.0) let q = geometry.Point(x=3.0, y=4.0) let r = geometry.Rect(w=2.0, h=1.5) print(p.dist(q), area(2.0, 3.0), r.area(), geometry.ORIGIN_X) print(text.shout("done"), "[" + text.pad_left("7", 3) + "]") test "points are 5 apart": assert Point(x=0.0, y=0.0).dist(Point(x=3.0, y=4.0)) == 5.0 ``` ```sh brine run -q ``` Output ```text 5.0 6.0 3.0 0.0 DONE! [ 7] ``` ```sh brine test ``` Output ```text Built tests [dev] in 0.09s test points are 5 apart ... ok test geometry: a 2 by 3 rectangle ... ok test result: ok. 2 passed; 0 failed; finished in 0.02s ``` `brine test` runs the tests in the main file and in every module it imports. Tests from a module are named with the module first (`geometry: ...`). Outside a project, `brine run app.br` works the same way: modules are found next to `app.br`. ## `import` [Section titled “import”](#import) ```brine import geometry # use as geometry.area(...), geometry.Point(...) import util.strings # use as strings.shout(...) import util.strings as s # use as s.shout(...) import geometry as g # an alias works for any module ``` * `import a.b.c` loads `a/b/c.br` and binds the last part, `c`. Add `as name` to pick another name. * Everything in the module is reached through the name: functions (`geometry.area(1.0, 2.0)`), constructors (`geometry.Point(x=1.0, y=2.0)`), types in annotations (`let ps: [geometry.Point] = []`), static methods, enum variants (`geometry.Shape.Circle(1.0)`, also in `match` patterns) and constants (`geometry.ORIGIN_X`). * Paths are always relative to the **main file’s** directory, also in imports written inside other modules: in `src/util/strings.br`, `import geometry` loads `src/geometry.br`. * Modules may import each other, including in a cycle. ## `from ... import` [Section titled “from ... import”](#from--import) `from m import a, b` makes items of `m` usable without the `m.` prefix: ```brine from geometry import Point, Rect, area, ORIGIN_X import geometry # you can import the module too fn main(): let p = Point(x=1.0, y=2.0) print(area(2.0, 3.0), geometry.area(1.0, 1.0), p, ORIGIN_X) ``` Two rules keep names unambiguous: 1. `from m import x` only imports items **defined in `m` itself**. If `m` imported `x` from somewhere else, import it from there. 2. A name can’t be both imported and defined in the same module. ```brine from geometry import Point, area fn area(r: Point) -> float: return r.x * r.y fn main(): print(area(Point(x=2.0, y=3.0))) ``` ```text error[E0202]: `area` is imported but this module already has that name --> src/main.br:1:29 | 1 | from geometry import Point, area | ^^^^ imported here 3 | fn area(r: Point) -> float: | ---- also defined here | = help: rename one of them, or use `import geometry` and write `geometry.area` = run `brine explain E0202` for more about this error error: could not compile due to 1 error ``` A misspelled name is [E0201](/reference/errors/#e0201), with a suggestion: ```text error[E0201]: module `geometry` has no item `Pointt` --> src/main.br:1:22 | 1 | from geometry import Pointt | ^^^^^^ not found | = help: did you mean `Point`? = run `brine explain E0201` for more about this error error: could not compile due to 1 error ``` ## Missing modules [Section titled “Missing modules”](#missing-modules) ```brine import geometry import shape fn main(): print(geometry.area(1.0, 2.0)) ``` ```text error[E0701]: module `shape` not found --> src/main.br:2:1 | 2 | import shape | ^^^^^^^^^^^^ expected a file at `src/shape.br` | = help: modules are `.br` files next to the main file; `import a.b` loads `a/b.br` = run `brine explain E0701` for more about this error error: could not compile due to 1 error ``` ## Checking one module [Section titled “Checking one module”](#checking-one-module) `brine check src/geometry.br` type-checks and proves a module on its own, and `brine test src/geometry.br` runs its tests. `brine run` needs a file with `fn main` (error E0703 otherwise). `brine fmt` formats every `.br` file in the project. ## Rules [Section titled “Rules”](#rules) * One file is one module. `import a.b` loads `a/b.br` relative to the main file’s directory and binds `b` (or the `as` name). * Every top-level item is importable; there is no `pub` or `private`. * `from m import x` imports only items defined in `m`. * A name can’t be both imported and defined in one module. * Each module’s tests run with `brine test` once the main file imports it. Related: [structs](/language/structs/), [testing](/language/testing/), [the CLI](/tooling/cli/), [functions](/language/functions/). # The os module > Reference for Brine's built-in os module, covering files and directories, paths, system information, and starting, polling, killing and capturing child processes. `os` is built in, so there’s nothing to import: write `os.cwd()`. Every function that can fail returns a `Result` whose `Err` holds the operating system’s message, so a missing file is a value you handle, never a crash. Functions that change things outside the program can’t be called inside `par`. ```brine fn main() -> Result[(), str]: let dir = os.temp_dir() + "/os-demo-" + str(os.pid()) os.make_dir(dir + "/logs")? write_file(dir + "/b.txt", "bee")? write_file(dir + "/a.txt", "ay")? print(os.list_dir(dir)?) print(os.is_dir(dir + "/logs"), os.is_file(dir + "/a.txt"), os.is_file(dir + "/nope")) os.rename(dir + "/a.txt", dir + "/c.txt")? os.remove_file(dir + "/b.txt")? print(os.list_dir(dir)?) print(os.remove_file(dir + "/b.txt")) return Ok(()) ``` Output (Windows) ```text ["a.txt", "b.txt", "logs"] true true false ["c.txt", "logs"] Err("The system cannot find the file specified. (os error 2)") ``` The error text comes from the operating system, so it differs between platforms (on Linux: `No such file or directory (os error 2)`). Match on `Ok`/`Err`, not on the message. ## Files and directories [Section titled “Files and directories”](#files-and-directories) | Function | Result | What it does | | ---------------------------------------- | -------------------- | --------------------------------------------------------------------- | | `os.list_dir(path: str)` | `Result[[str], str]` | entry names (not full paths), sorted, without `.` and `..` | | `os.is_dir(path: str)` | `bool` | `true` if `path` is a directory | | `os.is_file(path: str)` | `bool` | `true` if `path` is a regular file | | `os.make_dir(path: str)` | `Result[(), str]` | create a directory and any missing parents; `Ok` if it already exists | | `os.remove_file(path: str)` | `Result[(), str]` | delete a file | | `os.rename(src: str, dst: str)` | `Result[(), str]` | move or rename; replaces `dst` if it exists | | `os.write_bytes(path: str, bytes: [u8])` | `Result[(), str]` | write a binary file, byte for byte | * `is_dir` and `is_file` return `false` for a path that doesn’t exist. * `make_dir` over an existing file is an `Err`. * There is no `remove_dir`; run a program for that, or leave temporary directories under `os.temp_dir()`. * For text files use the built-ins `read_file`, `write_file` and `file_exists` ([built-in functions](/language/builtins/#files-environment-and-processes)). `read_file` needs UTF-8 text, so `os.write_bytes` is the way to write binary data. There is no binary read yet. `list_dir` and `is_dir` are enough to walk a tree: ```brine fn walk(dir: str, ref out: [str]): match os.list_dir(dir): case Ok(names): for name in names: let path = dir + "/" + name if os.is_dir(path): walk(path, ref out) elif name.ends_with(".txt"): out.push(path) case Err(e): eprint("skipping", dir, ":", e) fn main() -> Result[(), str]: let root = os.temp_dir() + "/walk-demo-" + str(os.pid()) os.make_dir(root + "/a/b")? write_file(root + "/one.txt", "1")? write_file(root + "/a/b/two.txt", "2")? write_file(root + "/a/skip.md", "3")? var found: [str] = [] walk(root, ref found) for p in found: print(p[len(root):]) return Ok(()) ``` Output ```text /a/b/two.txt /one.txt ``` ## Paths and the working directory [Section titled “Paths and the working directory”](#paths-and-the-working-directory) | Function | Result | What it does | | ------------------------ | ------------------ | ------------------------------------------------------------ | | `os.cwd()` | `str` | the current working directory | | `os.set_cwd(path: str)` | `Result[(), str]` | change the working directory | | `os.temp_dir()` | `str` | the system’s temporary directory, with no trailing separator | | `os.exe_path()` | `str` | the absolute path of the running program | | `os.abs_path(path: str)` | `Result[str, str]` | the canonical absolute path; `Err` if it doesn’t exist | On Windows, `abs_path` returns the extended form `\\?\C:\...`. `/` works as a separator on every platform, so `os.temp_dir() + "/name"` is fine on Windows too. ## System information [Section titled “System information”](#system-information) | Function | Result | What it does | | -------------------------- | -------------------- | ------------------------------------------------------------------------- | | `os.name()` | `str` | `"windows"`, `"macos"`, `"linux"`, `"freebsd"`, … | | `os.arch()` | `str` | `"x86_64"`, `"aarch64"`, … | | `os.pid()` | `int` | this process’s id | | `os.cpu_count()` | `int` | logical CPUs, at least 1 | | `os.sleep(seconds: float)` | | pause this thread | | `os.is_terminal(fd: int)` | `bool` | is file descriptor `fd` (0 stdin, 1 stdout, 2 stderr) a terminal? | | `os.enable_ansi()` | | on Windows, let the console show ANSI color codes; does nothing elsewhere | | `os.free_ports(n: int)` | `Result[[int], str]` | `n` distinct TCP ports free on localhost | `is_terminal(1)` is how a program decides whether to print colors: it’s `false` when the output goes to a file or a pipe. ```brine fn main(): let color = os.is_terminal(1) if color: os.enable_ansi() let ok = "\u{1b}[32mok\u{1b}[0m" if color else "ok" print("build", ok, "on", os.name(), os.arch()) ``` Output (Windows, piped to a file) ```text build ok on windows x86_64 ``` ## Processes [Section titled “Processes”](#processes) `os.spawn` starts a program and returns at once with a handle. Use the handle to wait for the program, check on it, stop it, or collect its output. spawn.br ```brine fn main() -> Result[(), str]: let a = args() if len(a) > 0 and a[0] == "child": print("hello from the child,", getenv("GREETING").unwrap_or("?")) eprint("a warning") exit(7) # run this same program again, as a child let p = os.spawn([os.exe_path(), "child"], {"GREETING": "hi"}, true, false)? let code = os.wait(p) let (out, err) = os.output(p) print("exit code:", code) print("stdout:", out.strip()) print("stderr:", err.strip()) return Ok(()) ``` Output ```text exit code: 7 stdout: hello from the child, hi stderr: a warning ``` | Function | Result | What it does | | ------------------------------------------------------------------- | ------------------ | ---------------------------------------------------------------- | | `os.spawn(cmd: [str], env: {str: str}, capture: bool, stdin: bool)` | `Result[int, str]` | start `cmd`; `Ok(handle)`, or `Err` if it can’t start | | `os.wait(h: int)` | `int` | wait for the process to exit; its exit code | | `os.poll(h: int)` | `Option[int]` | `Some(exit code)` if it has exited, `None` if it’s still running | | `os.kill(h: int)` | | stop the process | | `os.output(h: int)` | `(str, str)` | captured stdout and stderr, after it has exited | The arguments of `os.spawn`: * `cmd`: the program and its arguments. `cmd[0]` is searched on `PATH`. Arguments go to the program as they are, with no shell in between, so spaces and quotes need no escaping. An empty list is an `Err`. * `env`: variables to add to (or replace in) this process’s environment for the child. Pass `{}` for none. * `capture`: `true` collects the child’s stdout and stderr for `os.output`. `false` lets the child write straight to this program’s terminal. * `stdin`: `true` shares this program’s stdin with the child. `false` gives it an empty stdin, so it can never wait for input. More details: * `os.wait` can be called again after the process exits; it returns the same code. A process stopped by a signal (on Linux and macOS) has exit code 101; on Windows, a process stopped by `os.kill` has exit code 1. * `os.output` panics if the process is still running (call `os.wait` or check `os.poll` first). Without `capture`, both strings are `""`. * Captured output is collected while the child runs, so a child that prints a lot can’t block. * A handle that didn’t come from `os.spawn` panics: `os.wait: 12345 is not a process handle from os.spawn`. * To run a program and just wait for it, sharing the terminal, the built-in `run(cmd)` is shorter. Several children at once, polled until all finish: ```brine fn main() -> Result[(), str]: let a = args() if len(a) > 0: let n = int(a[0]) os.sleep(0.05 * float(n)) print(f"job {n} done") exit(n) var handles: [int] = [] for n in 1..4: handles.push(os.spawn([os.exe_path(), str(n)], {}, true, false)?) var codes = [-1] * len(handles) var left = len(handles) while left > 0: for i in 0..len(handles): if codes[i] < 0: match os.poll(handles[i]): case Some(code): codes[i] = code left -= 1 case None: pass os.sleep(0.01) for i, h in enumerate(handles): let (out, _) = os.output(h) print(codes[i], out.strip()) return Ok(()) ``` Output ```text 1 job 1 done 2 job 2 done 3 job 3 done ``` ## Inside `par` [Section titled “Inside par”](#inside-par) These functions change the world outside the program, so the order they run in would matter. They are compile errors inside a `par` body ([E0503](/reference/errors/#e0503)): `os.spawn`, `os.wait`, `os.poll`, `os.kill`, `os.make_dir`, `os.remove_file`, `os.rename`, `os.set_cwd`, `os.write_bytes`, `os.sleep`, `os.enable_ansi` The rest (`list_dir`, `is_dir`, `is_file`, `cwd`, `temp_dir`, `exe_path`, `abs_path`, `name`, `arch`, `pid`, `cpu_count`, `is_terminal`, `free_ports`, `output`) only read, and are allowed: ```brine fn main(): par for i in 0..4: os.sleep(0.1) ``` ```text error[E0503]: `os.sleep` can't be called inside a `par` body --> main.br:3:9 | 3 | os.sleep(0.1) | ^^^^^^^^^^^^^ not allowed here | = help: its effects would happen in an unpredictable order; do it before or after the `par` = run `brine explain E0503` for more about this error error: could not compile due to 1 error ``` ## `os` in `world` programs [Section titled “os in world programs”](#os-in-world-programs) In a program run as several processes ([`world`](/parallelism/overview/)), every rank must make the same `world` calls in the same order. The results of `os` functions can differ from rank to rank (each rank has its own pid, and ranks may run on different machines), so the compiler treats them as rank-dependent, like `world.rank()`. Only `os.name()` and `os.arch()` count as the same on every rank. ```brine fn main(): if os.cpu_count() > 4: world.barrier() ``` ```text error[E0510]: some ranks might not reach this communication --> main.br:3:9 | 3 | world.barrier() | ^^^^^^^^^^^^^^^ calls `world.barrier` 2 | if os.cpu_count() > 4: | ------------------ this may differ between ranks | = note: every rank must make the same `world` calls in the same order, otherwise the program could hang = help: move the call out of the rank-dependent branch or loop; compute rank-specific values first, then communicate on every rank = run `brine explain E0510` for more about this error error: could not compile due to 1 error ``` To agree on a value, reduce it first: `world.min(os.cpu_count())` is the same on every rank, so it can decide a branch. See [E0510](/reference/errors/#e0510). Related: [built-in functions](/language/builtins/), [testing](/language/testing/) (tests that start processes), [error handling](/language/error-handling/), [parallelism](/parallelism/overview/). # Strings > Brine strings are immutable UTF-8; this page covers byte indexing and slicing, every string method, f-strings and format specs, escapes, and conversions to and from numbers. A `str` is immutable UTF-8 text. `len(s)`, `s[i]` and slices count **bytes**, not characters; `for c in s` and `s.chars()` walk characters. Methods never change a string; they return a new one. ```brine fn main(): let s = "hello, brine" let name = "brine" print(s.upper(), len(s), s[7:], s.find("brine")) print(f"{name} has {len(name)} letters, pi is {3.14159:.2f}") ``` Output ```text HELLO, BRINE 12 brine Some(7) brine has 5 letters, pi is 3.14 ``` ## String literals [Section titled “String literals”](#string-literals) Strings use double quotes (single quotes work too). These escapes are the only ones: | Escape | Meaning | | ---------------------- | ------------------------------------------------ | | `\n`, `\t`, `\r`, `\0` | newline, tab, carriage return, NUL | | `\\`, `\"`, `\'` | backslash, quotes | | `\{`, `\}` | literal braces | | `\u{XXXX}` | a Unicode code point in hex: `"\u{e9}"` is `"é"` | Anything else, such as `\x41`, is error E0101. ## Bytes and characters [Section titled “Bytes and characters”](#bytes-and-characters) ```brine fn main(): let s = "héllo" print(len(s), len(s.chars())) print(s[0], s[1:3], s[3:]) print(s.chars()) print(s.bytes()) for c in s: print(c, end=" ") print() ``` Output ```text 6 5 h é llo ["h", "é", "l", "l", "o"] [104, 195, 169, 108, 108, 111] h é l l o ``` * `len(s)` is the number of bytes. `é` takes two bytes in UTF-8, so `"héllo"` has length 6. * `s[i]` is a one-byte `str`. For ASCII text that’s the character at `i`. Inside a multi-byte character it’s a single byte, which isn’t valid text on its own, so for non-ASCII text use `s.chars()` or `for c in s`. * `s[a:b]` is the bytes from `a` up to `b`. Indexes outside the string and `a > b` panic. There are no negative indexes: write `s[len(s) - 1]`. * `s.chars()` is a `[str]` of characters; `s.bytes()` is an `[int]` of byte values. Why bytes? Byte indexing is constant time and is what parsers and file formats need. Python’s character indexing costs either memory (fixed-width storage) or time. Strings can’t be changed in place: ```brine fn main(): var s = "abc" s[0] = "x" ``` ```text error[E0404]: cannot assign to a character of a string --> main.br:3:5 | 3 | s[0] = "x" | ^^^^ strings can't be changed in place | = help: build a new string, e.g. `s = s[:i] + "x" + s[i + 1:]` = run `brine explain E0404` for more about this error error: could not compile due to 1 error ``` ## Joining strings [Section titled “Joining strings”](#joining-strings) `+` joins two strings. `+=` on a `var` appends, growing the string in place when nothing else holds it, so building a string in a loop is fast: ```brine fn main(): var out = "" for i in 0..5: out += str(i) out += " " let line = "[" + out.rstrip() + "]" print(line, "-".join(["a", "b", "c"]), "ab".repeat(3)) ``` Output ```text [0 1 2 3 4] a-b-c ababab ``` `+` needs two strings. `"n = " + 5` is error E0305: convert with `str(5)` or use an f-string. `"ab" * 3` is an error too; write `"ab".repeat(3)`. Strings compare with `==`, `!=`, `<`, `<=`, `>` and `>=` (byte order), and `x in s` tests for a substring. ## Methods [Section titled “Methods”](#methods) ```brine fn main(): let s = "hello, brine" print(s.find("brine"), s.find("zzz"), s.count("l")) print(s.starts_with("he"), s.ends_with("!"), "ri" in s, s.contains("ri")) print(s.split(", "), "a b c".split(), "a,,b".split(",")) print(s.replace("l", "L"), "-".join(["x", "y"]), "ab".repeat(2)) print("[" + " x ".strip() + "]", "[" + " x ".lstrip() + "]", "[" + " x ".rstrip() + "]") print("straße".upper(), "ÉCOLE".lower()) print("one\ntwo\r\nthree\n".lines()) ``` Output ```text Some(7) None 2 true false true true ["hello", "brine"] ["a", "b", "c"] ["a", "", "b"] heLLo, brine x-y abab [x] [x ] [ x] STRASSE école ["one", "two", "three"] ``` | Method | Result | What it does | | --------------------------------------- | ------------- | ---------------------------------------------------- | | `s.find(sub)` | `Option[int]` | byte index of the first `sub`, or `None` | | `s.contains(sub)`, `sub in s` | `bool` | substring test | | `s.count(sub)` | `int` | non-overlapping occurrences | | `s.starts_with(p)`, `s.ends_with(p)` | `bool` | | | `s.split(sep)` | `[str]` | split on every `sep`; keeps empty parts | | `s.split()` | `[str]` | split on runs of whitespace; no empty parts | | `sep.join(xs)` | `str` | join a `[str]` with `sep` between | | `s.replace(old, new)` | `str` | replace every `old` | | `s.repeat(n)` | `str` | `n` copies | | `s.strip()`, `s.lstrip()`, `s.rstrip()` | `str` | remove whitespace from both ends, the start, the end | | `s.upper()`, `s.lower()` | `str` | Unicode case conversion | | `s.lines()` | `[str]` | split on `\n` or `\r\n`; no trailing empty line | | `s.chars()` | `[str]` | each character | | `s.bytes()` | `[int]` | each byte (0 to 255) | | `s.is_digit()` | `bool` | non-empty and only `0`-`9` | | `s.is_alpha()`, `s.is_alnum()` | `bool` | Unicode letters (and digits) | | `s.is_space()` | `bool` | Unicode whitespace | | `len(s)` | `int` | length in bytes | * `find`, `split(sep)`, `replace` and `count` panic when the pattern is empty (`split() separator is empty`). `find("")` is `Some(0)`. * `strip()` and its two variants take no arguments; they remove whitespace only. * Case conversion and `is_alpha`/`is_alnum`/`is_space` follow Unicode, as in Python: `"straße".upper()` is `"STRASSE"`. `is_digit` accepts only `0`-`9`, the digits `int()` reads, so `"٣".is_digit()` is `false`. ### Characters and code points [Section titled “Characters and code points”](#characters-and-code-points) ```brine fn main(): print(ord("a"), ord("é"), chr(97), chr(0x1F600)) print("42".is_digit(), "-4".is_digit(), "".is_digit(), "é".is_alpha(), " \t".is_space()) ``` Output ```text 97 233 a 😀 true false false true true ``` `ord(c)` needs exactly one character and panics otherwise (`ord() needs exactly one character, got a string of length 2`). `chr(n)` panics for negative numbers and other values that aren’t Unicode scalar values. ## f-strings [Section titled “f-strings”](#f-strings) An `f` before the quote makes an f-string: each `{expr}` is replaced by the value of the expression, printed the way `print` shows it. `{{` and `}}` are literal braces. ```brine fn main(): let name = "brine" let xs = [1, 2] let n = 42 print(f"{name.upper()} {n + 1} {xs} {n > 40} {{braces}}") ``` Output ```text BRINE 43 [1, 2] true {braces} ``` An expression inside an f-string can’t contain a string literal (`f"{"a"}"` is an error); put the string in a variable first. ### Format specs [Section titled “Format specs”](#format-specs) Add `:spec` after the expression to format a number or pad a value. The spec is `[<|>][0][width][.precision][kind]`: ```brine fn main(): let pi = 3.14159265 let n = 42 let name = "brine" print(f"[{pi:.2f}] [{pi:.0f}] [{pi:e}] [{pi:.3e}] [{pi:.2}]") print(f"[{n:5}] [{n:<5}] [{n:>5}] [{n:05}] [{-n:05}]") print(f"[{n:x}] [{n:b}] [{255:08b}] [{n:.1f}]") print(f"[{name:8}] [{name:<8}] [{pi:10.3f}] [{pi:<10.3f}] [{pi:08.2f}]") ``` Output ```text [3.14] [3] [3.141593e+00] [3.142e+00] [3.14] [ 42] [42 ] [ 42] [00042] [-0042] [2a] [101010] [11111111] [42.0] [ brine] [brine ] [ 3.142] [3.142 ] [00003.14] ``` | Spec | Meaning | Example | | ---------- | ---------------------------------- | ------------------------ | | `.Nf` | fixed point, `N` decimals | `{pi:.2f}` → `3.14` | | `.N` | the same as `.Nf` | `{pi:.2}` → `3.14` | | `e`, `.Ne` | scientific (6 decimals by default) | `{pi:.3e}` → `3.142e+00` | | `x` | hexadecimal, integers only | `{255:x}` → `ff` | | `b` | binary, integers only | `{5:b}` → `101` | | `N` | minimum width `N`, right-aligned | `{n:5}` → `42` | | `N` | right-align (the default) | `{n:>5}` → `42` | | `0N` | pad with zeros after the sign | `{n:05}` → `00042` | * Every value is right-aligned by default, strings too (Python left-aligns strings). Use `<` for left-aligned columns of text. * `f`, `e` and `.N` need a number; an integer is printed as a float (`{42:.1f}` is `42.0`). `x` and `b` need an integer. * There is no centering (`^`), no fill character other than `0`, and no `,` thousands separator. An unsupported spec is error E0318: ```brine fn main(): let n = 7 print(f"{n:^5}") ``` ```text error[E0318]: invalid format spec `^5` --> main.br:3:14 | 3 | print(f"{n:^5}") | ^ in this f-string field | = help: supported: `.2f` (fixed), `.3e` (scientific), `8` (width), `<8` (left-align), `08` (zero-pad), `x` (hex), `b` (binary) = run `brine explain E0318` for more about this error error: could not compile due to 1 error ``` ## Converting to and from strings [Section titled “Converting to and from strings”](#converting-to-and-from-strings) `str(x)` converts any printable value. `int(s)` and `float(s)` parse text, and so do the sized types (`i32("7")`, `u8("200")`, `f32("1.5")`): ```brine fn main(): print(str(42) + "!", str(2.5), str(true), str([1, 2]), str(Some(3))) print(int("42") + 1, int("-7"), int(" 42"), float("2.5"), float("1e3")) print(str(1.0), str(0.1 + 0.2), str(1e20), str(1e-7)) ``` Output ```text 42! 2.5 true [1, 2] Some(3) 43 -7 42 2.5 1000.0 1.0 0.30000000000000004 1e+20 1e-07 ``` Bad input panics. Text from outside the program (files, arguments) should be checked first, for example with `is_digit()`: ```brine fn main(): let text = "4x2" print(int(text)) ``` Output ```text panic: invalid integer: "4x2" --> main.br:3:11 ``` | Call | Panics when | | -------------------- | -------------------------------------------------------------------------------------------------- | | `int(s)` | `s` isn’t a decimal integer (`"3.5"`, `"0x1f"` and `"1_000"` all panic) or doesn’t fit in an `int` | | `u8(s)`, `i32(s)`, … | the same, or the number is out of range for the type | | `float(s)` | `s` isn’t a number; `"inf"` and `"nan"` are accepted | | `ord(c)` | `c` isn’t exactly one character | | `chr(n)` | `n` isn’t a valid code point | Surrounding whitespace is ignored when parsing (`int(" 42\n")` is 42). Floats print in the shortest form that reads back as the same value, always with a decimal point or exponent: `1.0`, `0.30000000000000004`, `1e+20`. ## Rules [Section titled “Rules”](#rules) * Strings are immutable UTF-8. Methods return new strings. * `len`, `s[i]` and slices count bytes; `for c in s` and `s.chars()` count characters. * No negative indexes. Out-of-range indexes and slices panic. * `+` joins strings only; convert other values with `str()` or an f-string. * `int()` and `float()` panic on bad input. Related: [arrays](/language/arrays/), [built-in functions](/language/builtins/), [values and types](/language/values-and-types/), [error handling](/language/error-handling/). # Structs > Defining structs, constructing them with positional or keyword fields, reading and changing fields, methods with self and ref self, static methods, printing, equality, and value semantics. A struct is a named record of fields, with methods defined inside it. Structs are values like everything else in Brine: copying one copies its fields, `==` compares fields, and `print` shows them. A method that changes its struct takes `ref self`. ## Defining and constructing [Section titled “Defining and constructing”](#defining-and-constructing) ```brine struct Point: x: int y: int struct Line: start: Point end: Point label: str fn main(): let a = Point(x=1, y=2) # keyword: any order let b = Point(3, 4) # positional: declaration order var line = Line(label="diagonal", start=a, end=b) line.end.y = 40 # nested fields print(a.x + b.y) print(line) print(f"from {line.start} to {line.end}") print(a == Point(1, 2), a != b) ``` Output ```text 5 Line(start=Point(x=1, y=2), end=Point(x=3, y=40), label="diagonal") from Point(x=1, y=2) to Point(x=3, y=40) true true ``` * Each field has a name and a type. Fields have no default values. * Construct with all fields positionally, in declaration order (`Point(3, 4)`), or all by keyword, in any order (`Point(y=4, x=3)`). Don’t mix the two. * Read and assign fields with a dot. Assigning needs a `var`; a `let` struct can’t change at all. Every field needs a value: ```brine struct Point: x: int y: int fn main(): let p = Point(x=1) print(p) ``` ```text error[E0302]: missing field `y` in `Point(...)` --> main.br:6:13 | 6 | let p = Point(x=1) | ^^^^^^^^^^ every field needs a value = run `brine explain E0302` for more about this error error: could not compile due to 1 error ``` ## Methods [Section titled “Methods”](#methods) Methods are functions declared inside the struct. The first parameter decides what a method may do: * `self`: the method reads its receiver (a read-only copy, like any parameter). * `ref self`: the method can change its receiver. It can only be called on something you could pass as `ref`: a `var`, or an element or field of one. * no `self`: a static method, called on the type: `Particle.at_rest(1.0)`. Use these for constructors. ```brine struct Particle: pos: float vel: float fn energy(self) -> float: # reads its receiver return 0.5 * self.vel * self.vel fn step(ref self, dt: float): # changes its receiver self.pos += self.vel * dt fn at_rest(pos: float) -> Particle: # no self: a static method return Particle(pos=pos, vel=0.0) fn main(): var p = Particle(pos=0.0, vel=2.0) p.step(0.5) p.step(0.5) print(p, p.energy()) let q = Particle.at_rest(1.0) print(q) var ps = [Particle(0.0, 1.0), Particle(5.0, -1.0)] for i in 0..len(ps): ps[i].step(1.0) # on an array element print(ps) ``` Output ```text Particle(pos=2.0, vel=2.0) 2.0 Particle(pos=1.0, vel=0.0) [Particle(pos=1.0, vel=1.0), Particle(pos=4.0, vel=-1.0)] ``` The call `p.step(0.5)` doesn’t need a `ref` at the call site: the method’s name already says what it does, and the compiler checks that `p` can be changed. ```brine struct Counter: n: int fn bump(ref self): self.n += 1 fn main(): let c = Counter(n=0) c.bump() ``` ```text error[E0401]: cannot call `ref self` method `bump` on `c` because it was declared with `let` --> main.br:9:5 | 9 | c.bump() | ^ immutable 8 | let c = Counter(n=0) | - declared here | = help: declare it with `var c = ...` to make it mutable = run `brine explain E0401` for more about this error error: could not compile due to 1 error ``` In a plain `self` method, `self` is read-only, and assigning to one of its fields is [E0403](/reference/errors/#e0403). `mut self` isn’t allowed: it would only change a copy. To return a changed copy, build a new value: `return Particle(pos=self.pos + 1.0, vel=self.vel)`. Methods may have type parameters of their own; see [generics](/language/generics/#methods-with-their-own-type-parameters). ## Printing and equality [Section titled “Printing and equality”](#printing-and-equality) `print`, `str(...)` and f-strings show a struct as its name and its fields, nested values included: `Point(x=1, y=2)`. Strings inside are quoted (`label="diagonal"`). `==` and `!=` compare two structs of the same type field by field. Structs can’t be ordered: `<` on two `Point`s is [E0305](/reference/errors/#e0305). A struct whose fields are all valid map keys (integers, `bool`, `str`, enums, and arrays, tuples and structs of those) can be a map key too. ## Value semantics [Section titled “Value semantics”](#value-semantics) A struct is a value. `var q = p` copies `p`, and passing a struct to a function passes a copy, so changing one never changes the other. (The copy is cheap: arrays and strings inside are copy-on-write.) ```brine struct Point: x: int y: int fn moved(p: Point, dx: int) -> Point: var q = p # a copy q.x += dx return q fn main(): let a = Point(1, 2) let b = moved(a, 10) var c = a c.y = 0 print(a, b, c) ``` Output ```text Point(x=1, y=2) Point(x=11, y=2) Point(x=1, y=0) ``` To let a function change a struct the caller owns, use a `ref self` method or a `ref` parameter (see [functions](/language/functions/#ref-parameters)). ## Recursive structs [Section titled “Recursive structs”](#recursive-structs) A struct can’t contain itself directly, because it would have infinite size. Put the recursive part in an array (`children: [Node]`) or use a recursive [enum](/language/enums-and-match/#recursive-enums). ```brine struct Node: value: int next: Node fn main(): print(1) ``` ```text error[E0309]: struct `Node` contains itself and would have infinite size --> main.br:1:1 | 1 | struct Node: | ^^^^^^ recursive struct | = help: store the recursive part inside an array or an enum, e.g. `children: [Node]` = run `brine explain E0309` for more about this error error: could not compile due to 1 error ``` ## Summary [Section titled “Summary”](#summary) * `struct Name:` followed by `field: Type` lines and `fn` methods. * Construct positionally (`Point(1, 2)`) or by keyword (`Point(x=1, y=2)`), not mixed; every field needs a value. * `self` methods read, `ref self` methods change the receiver (which must be a `var`), methods without `self` are static (`Type.method()`). * Structs print as `Name(field=value, ...)` and compare with `==` field by field. * Copies are independent. Generic structs: see [generics](/language/generics/). # Testing > Write test blocks with assert next to your code and run them with brine test, which runs each test in its own process, in parallel, with plain or JSON output. A `test "name":` block is a function with no parameters that passes when it finishes and fails when it panics. Put tests in the same file as the code they test, and run them all with `brine test`. Each test runs in its own process, in parallel with the others. stats.br ```brine fn mean(xs: [float]) -> float: return sum(x for x in xs) / float(len(xs)) fn main(): print(mean([1.0, 2.0, 6.0])) test "mean of three numbers": assert mean([1.0, 2.0, 6.0]) == 3.0 test "mean of one number": assert mean([4.5]) == 4.5 ``` ```sh brine test stats.br ``` Output ```text Built tests [dev] in 0.09s test mean of three numbers ... ok test mean of one number ... ok test result: ok. 2 passed; 0 failed; finished in 0.02s ``` ## Writing tests [Section titled “Writing tests”](#writing-tests) * `test "name":` goes at the top level of any `.br` file, next to the functions it tests. The name is any string. * The body is ordinary code. It can call any function in the file or in imported modules, use `var`s, loops and `par`, and read files. * `assert cond` panics when `cond` is false. `assert cond, message` adds a message (any `str`, often an f-string). * Any panic fails the test: a failed `assert`, an out-of-range index, a missing map key, an `unwrap()` of `None`, or `panic(...)`. * Tests aren’t part of the program: `brine run` and `brine build` leave them out. `assert` is not only for tests. In normal code it’s a runtime check that always runs, and the prover treats the condition as a known fact after it. See [contracts](/proofs/overview/). ## Failing tests [Section titled “Failing tests”](#failing-tests) stats.br ```brine fn mean(xs: [float]) -> float: return sum(x for x in xs) / float(len(xs)) fn main(): print(mean([1.0, 2.0, 6.0])) test "mean of three numbers": assert mean([1.0, 2.0, 6.0]) == 3.0 test "mean of one number": assert mean([4.5]) == 4.5 test "mean of nothing": let empty: [float] = [] print("about to divide by", len(empty)) let m = mean(empty) assert m == 0.0, f"mean([]) was {m}" ``` brine test stats.br ```text Built tests [dev] in 0.09s test mean of three numbers ... ok test mean of one number ... ok test mean of nothing ... FAILED ---- mean of nothing ---- about to divide by 0 panic: assertion failed: m == 0.0 message: mean([]) was nan --> stats.br:17:5 test result: FAILED. 2 passed; 1 failed; finished in 0.02s ``` * What a test prints is captured. It’s shown only when the test fails, in that test’s section, followed by the panic message, the failed condition and its location. * `brine test` exits with 0 when every test passes and 1 when any fails. * Results are listed in the order the tests appear, whichever finishes first. ## Running tests [Section titled “Running tests”](#running-tests) ```sh brine test # every test in the project brine test "mean" # only tests whose name contains "mean" brine test stats.br # the tests in one file (and the modules it imports) brine test stats.br "nothing" # one file, filtered brine test --release # build the tests with optimizations brine test --json # one JSON object per test ``` * In a project, `brine test` builds `src/main.br` and every module it imports, and runs all their tests. A module’s tests are named with the module first: `geometry: a 2 by 3 rectangle`. See [modules](/language/modules/). * The filter is a case-sensitive substring of the test name (including the `module: `part). When nothing matches, `brine test` says so and exits with 0. * Tests run in parallel, as many at a time as you have cores. Each one is a separate process, so a panic in one test can’t affect another. Each test process runs `par` code on 2 threads. ## Tests that run the compiler [Section titled “Tests that run the compiler”](#tests-that-run-the-compiler) While tests run, the environment variable `BRINE` holds the path of the `brine` that is running them. A test can use it to check that a program compiles, or that it doesn’t: compiler_test.br ```brine # Check that a program with a known mistake is rejected with the right error. fn check_output(source: str) -> Result[str, str]: let path = os.temp_dir() + "/brine-test-" + str(os.pid()) + ".br" write_file(path, source)? let cmd = [getenv("BRINE").unwrap(), "check", path, "--color", "never"] let p = os.spawn(cmd, {}, true, false)? os.wait(p) let (out, err) = os.output(p) os.remove_file(path)? return Ok(out + err) test "reading and changing in one expression is rejected": let src = "fn main():\n var xs = [1, 2]\n print(xs.pop(), xs)\n" let out = check_output(src).unwrap() assert "error[E0405]" in out, out ``` brine test compiler_test.br ```text Built tests [dev] in 0.10s test reading and changing in one expression is rejected ... ok test result: ok. 1 passed; 0 failed; finished in 0.04s ``` The file name includes `os.pid()` because tests run at the same time; each test process has its own id. See [the os module](/language/os-module/) for `os.spawn`. ## JSON output [Section titled “JSON output”](#json-output) With `--json`, `brine test` prints one JSON object per test, in order, and nothing else: brine test stats.br --json ```text {"test":"mean of three numbers","ok":true,"seconds":0.022,"output":""} {"test":"mean of one number","ok":true,"seconds":0.026,"output":""} {"test":"mean of nothing","ok":false,"seconds":0.029,"output":"about to divide by 0\n\npanic: assertion failed: m == 0.0\n message: mean([]) was nan\n --> stats.br:17:5\n"} ``` | Field | Type | Meaning | | --------- | ------ | ----------------------------------------------------------------------- | | `test` | string | the test’s name | | `ok` | bool | whether it passed | | `seconds` | number | how long its process ran | | `output` | string | everything it printed to stdout and stderr, including the panic message | The exit code is the same as without `--json`. Agents and CI scripts can read the lines one at a time. See [the CLI](/tooling/cli/). ## Rules [Section titled “Rules”](#rules) * Tests live next to the code, in `test "name":` blocks. * A test fails if it panics; `assert cond, message` is the usual way. * `brine test [file] [filter]` runs every matching test, each in its own process, in parallel. * Output is shown only for failing tests (and always in `--json`). * `$BRINE` is the compiler running the tests. Related: [modules](/language/modules/), [error handling](/language/error-handling/), [built-in functions](/language/builtins/), [the CLI](/tooling/cli/). # Values and types > Brine's built-in types, how literals are written, explicit conversions, what happens on overflow, division and out-of-bounds access, and value semantics. Every value in Brine has one static type, known at compile time, and Brine never converts between types for you: an `int` times a `float` is an error until you write `float(n)`. Values are **values**: assigning or passing one makes a copy (a cheap one), so changing a copy never changes the original. ## The types [Section titled “The types”](#the-types) ```brine struct Point: x: int y: int fn main(): let n = 42 # int (64-bit signed) let small: u8 = 200 # the literal adapts to u8 let ratio = 0.75 # float (64-bit) let ok = true # bool let name = "brine" # str (UTF-8) let xs = [1, 2, 3] # [int] let ages = {"ann": 31, "bob": 27} # {str: int} let pair = (1, "one") # (int, str) let p = Point(x=1, y=2) # a struct print(n, small, ratio, ok, name) print(xs, ages, pair, p) print(pair.0, pair.1, p.x) ``` Output ```text 42 200 0.75 true brine [1, 2, 3] {"ann": 31, "bob": 27} (1, "one") Point(x=1, y=2) 1 one 1 ``` | Type | What it is | Literals | | ----------------------------- | ------------------------------------------------------- | ------------------------------------------------- | | `int` | 64-bit signed integer (the same type as `i64`) | `42`, `-7`, `1_000_000`, `0xff` | | `i8` `i16` `i32` `i64` `i128` | signed integers of that many bits | any integer literal that fits | | `u8` `u16` `u32` `u64` `u128` | unsigned integers | any integer literal that fits | | `float` | 64-bit IEEE float (the same type as `f64`) | `3.14`, `1e-9`, `2.5e3` | | `f32` | 32-bit IEEE float | any number literal, in an `f32` context | | `bool` | `true` or `false` | `true`, `false` | | `str` | immutable UTF-8 text | `"hi\n"`, `f"x = {x}"` | | `[T]` | array of `T` | `[1, 2, 3]`, `[0.0] * n`, `[i * i for i in 0..n]` | | `{K: V}` | map from `K` to `V`, in insertion order | `{"ann": 31}` | | `(A, B, ...)` | tuple | `(1, "one")`; fields are `t.0`, `t.1` | | structs | your own record types | `Point(x=1, y=2)` or `Point(1, 2)` | | enums | tagged unions, including `Option[T]` and `Result[T, E]` | `Shape.Circle(1.0)`, `Some(3)`, `Ok(x)` | Arrays, strings and maps have their own pages: [arrays](/language/arrays/), [strings](/language/strings/) and [maps](/language/maps/). Structs and enums are covered in [structs](/language/structs/) and [enums and match](/language/enums-and-match/). Tuples are indexed with a number after a dot (`pair.0`), not with brackets, and you can unpack them: `let (n, s) = pair` (see [bindings](/language/bindings/#tuple-unpacking)). ## Literals [Section titled “Literals”](#literals) ```brine fn main(): print(1_000_000, 0xff, 0o17, 0b1010) print(3.14, 1e-9, 2.5e3, 1.0) let b: u8 = 0b1111_0000 let f: f32 = 1 let big: i128 = 10_000_000_000_000_000_000_000 let max: u128 = 340282366920938463463374607431768211455 print(b, f, big, max) print("tab\there", "quote: \"", "brace: \{", "\u{e9}") ``` Output ```text 1000000 255 15 10 3.14 1e-09 2500.0 1.0 240 1.0 10000000000000000000000 340282366920938463463374607431768211455 tab here quote: " brace: { é ``` * Integers may use `_` separators anywhere between digits, and the prefixes `0x` (hex), `0o` (octal) and `0b` (binary). * A number with a `.` or an exponent is a float: `1.0`, `1e-9`. * String escapes: `\n` `\t` `\r` `\0` `\\` `\"` `\'` `\{` `\}` and `\u{XXXX}`. The braces need escaping because any string can become an f-string; see [strings](/language/strings/). ### Literals adapt to context [Section titled “Literals adapt to context”](#literals-adapt-to-context) A literal has no fixed type of its own. It takes the type the context expects, so `let b: u8 = 200` is a `u8` and `let f: f32 = 1` is an `f32` (an integer literal can become a float). With no context, an integer literal is an `int` and a float literal is a `float`. The compiler checks that the literal fits: ```brine fn main(): let b: u8 = 300 print(b) ``` ```text error[E0315]: literal `300` is out of range for `u8` --> main.br:2:17 | 2 | let b: u8 = 300 | ^^^ `u8` holds 0..=255 = run `brine explain E0315` for more about this error error: could not compile due to 1 error ``` Integer literals go up to `u128`’s maximum, 2^128 - 1. A literal above `i128`’s maximum fits only in a `u128`. ## No implicit conversions [Section titled “No implicit conversions”](#no-implicit-conversions) Brine never converts a value to another type on its own, not even `i32` to `int` or `int` to `float`. Mixing types is a compile error that tells you what to write: ```brine fn main(): let count = 3 let price = 2.5 print(count * price) ``` ```text error[E0305]: cannot multiply `int` and `float` --> main.br:4:11 | 4 | print(count * price) | ^^^^^^^^^^^^^ `*` with mismatched operands | = help: brine never converts numbers implicitly; use `float(x)` or `int(x)` = run `brine explain E0305` for more about this error error: could not compile due to 1 error ``` The same goes for two integer types: ```brine fn main(): let small: i32 = 5 let n: int = small print(n) ``` ```text error[E0301]: mismatched types --> main.br:3:18 | 3 | let n: int = small | ^^^^^ expected `int`, found `i32` | = help: convert explicitly with `int(...)` (checked at runtime) = run `brine explain E0301` for more about this error error: could not compile due to 1 error ``` Why so strict: implicit conversions are where precision is lost and signs flip without anyone noticing. In Brine every conversion is visible in the code, and every narrowing one is checked. ## Explicit conversions [Section titled “Explicit conversions”](#explicit-conversions) Convert by calling the type’s name like a function: ```brine fn main(): let i = 7 let x = 2.9 print(float(i) / 2.0) # int to float print(int(x), int(-x)) # float to int: truncates toward zero let small: i32 = i32(i) # checked: panics if out of range print(small, int(small)) print(str(i) + "!", str(x)) # anything printable to str print(int("42") + 1, float("2.5") * 2.0) # parse text ``` Output ```text 3.5 2 -2 7 7 7! 2.9 43 5.0 ``` | Conversion | Behavior | | ------------------------------ | ------------------------------------------------------------------------------------- | | `float(n)`, `f32(n)` | integer to float | | `int(x)`, `i32(x)`, … | float to integer, truncating toward zero; panics if the value is out of range or NaN | | `i32(n)`, `u8(n)`, `int(n)`, … | integer to integer; panics if the value doesn’t fit | | `str(v)` | any printable value to its text, as `print` shows it | | `int(s)`, `float(s)` | parse a `str`; panics on bad input, so check text from outside first (`s.is_digit()`) | ## Panics: overflow, division by zero, bounds [Section titled “Panics: overflow, division by zero, bounds”](#panics-overflow-division-by-zero-bounds) Integer overflow, an out-of-range conversion, division (or `%`) by zero and an out-of-bounds index all stop the program with a clear message and exit code 101. This is a *panic*. overflow\.br ```brine fn main(): var level: u8 = 250 for _ in 0..10: level += 1 print(level) ``` Output ```text panic: integer overflow in addition --> overflow.br:4:9 ``` bounds.br ```brine fn main(): let xs = [10, 20, 30] let i = len(args()) + 3 # 3 when the program gets no arguments print(xs[i]) ``` Output ```text panic: index 3 is out of bounds for an array of length 3 --> bounds.br:4:11 ``` These checks are always on, in every build, and there is no flag to turn them off. Instead, the compiler removes each check it can **prove** can never fail. In `for i in 0..len(xs): total += xs[i]` the index is always in range, so that bounds check is gone from the program. `brine check --report` lists the checks that remain, and [contracts](/proofs/contracts/) let you give the prover the facts it needs to remove more. Floats don’t panic: they follow IEEE rules, so `1.0 / 0.0` is `inf` and `0.0 / 0.0` is `nan`. ## Integer division and remainder [Section titled “Integer division and remainder”](#integer-division-and-remainder) Integer `/` rounds down, toward negative infinity, like Python’s `//`. `%` takes the sign of the right-hand side, so `a == (a / b) * b + a % b` always holds. ```brine fn main(): print(7 / 2, 7 % 2) print(-7 / 2, -7 % 2) print(7 / -2, 7 % -2) print(float(7) / float(2)) ``` Output ```text 3 1 -4 1 -4 -1 3.5 ``` For a fractional result, convert first: `float(a) / float(b)`. There is no `//` and no `**`; use `/` and `pow(x, y)`. Bitwise operators work on integers: `&`, `|`, `^`, `~`, `<<` and `>>`. ## Value semantics [Section titled “Value semantics”](#value-semantics) Assigning a value or passing it to a function makes a copy. Changing the copy never changes the original, for every type: numbers, arrays, strings, maps, tuples, structs and enums. ```brine struct Point: x: int y: int fn main(): let a = [1, 2, 3] var b = a # a copy (cheap: shared until one side changes) b.push(4) b[0] = 100 print(a, b) let p = Point(x=1, y=2) var q = p q.x = 10 print(p, q) ``` Output ```text [1, 2, 3] [100, 2, 3, 4] Point(x=1, y=2) Point(x=10, y=2) ``` Copies are cheap. Arrays, strings and maps are reference counted and **copy-on-write**: `var b = a` shares the data, and only the first change to `b` copies it. A function that only reads its argument never copies it. When you want a function to change the caller’s variable, say so with `ref`, at both ends (see [functions](/language/functions/#ref-parameters)). Because nothing is shared behind your back, there is no aliasing to reason about, and [`par`](/parallelism/overview/) can run code in parallel without data races. ## i128 and u128 [Section titled “i128 and u128”](#i128-and-u128) `i128` and `u128` work like the other integer types, with two caveats: * They need a 64-bit target (they’re a GCC/Clang feature in the generated code). * The prover can’t remove their overflow checks, so those checks always run. ```brine fn main(): let big: i128 = 170141183460469231731687303715884105727 let huge: u128 = 2 * u128(big) + 1 print(big, huge) let wide = i128(9_223_372_036_854_775_807) * 4 print(wide) ``` Output ```text 170141183460469231731687303715884105727 340282366920938463463374607431768211455 36893488147419103228 ``` ## Summary [Section titled “Summary”](#summary) * Types: `int` (= `i64`), `i8`..`i128`, `u8`..`u128`, `float` (= `f64`), `f32`, `bool`, `str`, `[T]`, `{K: V}`, tuples, structs and enums. * Literals adapt to the expected type and must fit in it. * No implicit conversions. Convert with `float(n)`, `int(x)`, `i32(n)`, `str(v)`, `int(s)`; narrowing ones are checked. * Overflow, division by zero, bad conversions and bad indexes panic, unless the compiler proved they can’t happen. * Integer `/` rounds down; `%` has the sign of the right side. * Everything is a value. Copies are cheap; `ref` shares on purpose. # Deadlock freedom > How Brine proves at compile time that every rank of a world program makes the same communication calls in the same order, the uniform and varying values behind it, errors E0510, E0511 and E0512, and what the check allows. A `world` program can’t deadlock. Every `world` call is collective, so a program could only hang if some ranks skipped a call that others made. The compiler rules that out: a communication call may not depend on anything that can differ between ranks, such as `world.rank()`, and the arguments that steer communication must be the same on every rank. ## Why collectives are enough [Section titled “Why collectives are enough”](#why-collectives-are-enough) In MPI-style code, deadlocks come from unmatched messages: rank 0 waits in `recv` for a message rank 1 never sends, or both ranks wait for each other. Brine has no `send` and `recv` ([E0512](/reference/errors/#e0512)). Every call in the [`world` API](/parallelism/world/#the-api) involves every rank, and point-to-point messages are written with `shift` and `exchange`, which are collectives too. So if every rank makes the same sequence of `world` calls, every receive has a matching send: all ranks run the same program, and the static types make the messages agree. The compiler’s job is to make sure the sequence can’t differ. ## Uniform and varying values [Section titled “Uniform and varying values”](#uniform-and-varying-values) The check, in `compiler/uniform.br`, classifies every value: * **Uniform:** the same on every rank. Constants, `world.size()`, `args()`, `os.name()`, `os.arch()`, and the results of `world.sum`, `min`, `max`, `broadcast` and `gather_all` (every rank gets the same result). * **Varying:** may differ between ranks. The sources are: * `world.rank()`; * the results of `world.shift` and `world.exchange` (each rank receives something different); * `clock()`; * files and processes: `read_file`, `file_exists`, `getenv`, `run`; * the `os` functions, except `os.name()` and `os.arch()`; * C functions declared with `extern fn`. Anything computed from a varying value is varying, and so is a variable assigned under a varying condition (`if world.rank() == 1: root = 1` makes `root` varying). Then two rules: 1. **A communication call can’t be controlled by a varying value** ([E0510](/reference/errors/#e0510)): not by an `if`, `while`, `for` or `match`, not by `and`/`or` or a conditional expression, and not by an early `return`, `break` or `continue` before it. 2. **Arguments that steer communication must be uniform** ([E0511](/reference/errors/#e0511)): the root of `world.broadcast`, the distance of `world.shift`, and any parameter that decides whether a function communicates. The *values* sent can vary freely: `world.sum(local)` with a different `local` on each rank is the point of the call. ## Communication under a rank-dependent branch [Section titled “Communication under a rank-dependent branch”](#communication-under-a-rank-dependent-branch) branch.br ```brine fn main(): if world.rank() == 0: let total = world.sum(1) print(total) ``` ```text error[E0510]: some ranks might not reach this communication --> branch.br:3:21 | 3 | let total = world.sum(1) | ^^^^^^^^^^^^ calls `world.sum` 2 | if world.rank() == 0: | ----------------- this may differ between ranks | = note: every rank must make the same `world` calls in the same order, otherwise the program could hang = help: move the call out of the rank-dependent branch or loop; compute rank-specific values first, then communicate on every rank = run `brine explain E0510` for more about this error error: could not compile due to 1 error ``` Rank 0 would wait in `world.sum` for ranks that never call it. Compute the rank-specific part first, then communicate on every rank, and branch on the rank only for what doesn’t communicate: ```brine fn main(): let mine = 1 if world.rank() == 0 else 0 let total = world.sum(mine) if world.rank() == 0: print(total) ``` brine run --ranks 3 ```text 1 ``` ## Loops whose length varies [Section titled “Loops whose length varies”](#loops-whose-length-varies) A loop whose condition or range depends on the rank runs a different number of times on each rank: loop.br ```brine fn main(): let rank = world.rank() var steps = 0 while steps < rank: world.barrier() steps += 1 ``` ```text error[E0510]: some ranks might not reach this communication --> loop.br:5:9 | 5 | world.barrier() | ^^^^^^^^^^^^^^^ calls `world.barrier` 4 | while steps < rank: | ------------ this may differ between ranks | = note: every rank must make the same `world` calls in the same order, otherwise the program could hang = help: move the call out of the rank-dependent branch or loop; compute rank-specific values first, then communicate on every rank = run `brine explain E0510` for more about this error error: could not compile due to 1 error ``` The same goes for `for i in 0..n` with a varying `n`, and for a `continue` or `break` under a varying condition before the call. ## Short-circuits and other sources [Section titled “Short-circuits and other sources”](#short-circuits-and-other-sources) `and`, `or` and conditional expressions decide whether their right side runs, so they count as branches. `clock()` differs between processes: timer.br ```brine fn main(): let t = clock() if t > 1.0 and world.sum(1) > 0: print("ok") ``` ```text error[E0510]: some ranks might not reach this communication --> timer.br:3:20 | 3 | if t > 1.0 and world.sum(1) > 0: | ^^^^^^^^^^^^ calls `world.sum` | ------- this may differ between ranks | = note: every rank must make the same `world` calls in the same order, otherwise the program could hang = help: move the call out of the rank-dependent branch or loop; compute rank-specific values first, then communicate on every rank = run `brine explain E0510` for more about this error error: could not compile due to 1 error ``` ## Branching on received values [Section titled “Branching on received values”](#branching-on-received-values) `shift` and `exchange` give each rank a different value, so their results are varying too: received.br ```brine fn main(): let got = world.shift(world.rank(), 1) if got == 0: world.barrier() ``` ```text error[E0510]: some ranks might not reach this communication --> received.br:4:9 | 4 | world.barrier() | ^^^^^^^^^^^^^^^ calls `world.barrier` 3 | if got == 0: | -------- this may differ between ranks | = note: every rank must make the same `world` calls in the same order, otherwise the program could hang = help: move the call out of the rank-dependent branch or loop; compute rank-specific values first, then communicate on every rank = run `brine explain E0510` for more about this error error: could not compile due to 1 error ``` To decide together, turn the local fact into a uniform one with a reduction. Every rank then gets the same answer and takes the same branch: ```brine fn main(): let got = world.shift(world.rank(), 1) # ask every rank, and get the same answer everywhere let someone_got_zero = world.max(1 if got == 0 else 0) == 1 if someone_got_zero: world.barrier() if world.rank() == 0: print(f"rank 0 got {got}, someone got 0: {someone_got_zero}") ``` brine run --ranks 3 ```text rank 0 got 2, someone got 0: true ``` ## What’s allowed [Section titled “What’s allowed”](#whats-allowed) Results of `world.sum`, `min`, `max`, `broadcast` and `gather_all` are uniform, so conditions built from them are fine. That covers the usual iterative patterns: loop until the largest error on any rank is small, let rank 0 choose a value and broadcast it, take turns as the root: allowed.br ```brine fn main(): let rank = world.rank() let size = world.size() # every rank keeps going until the largest error is small var x = float(rank + 1) * 100.0 var rounds = 0 while world.max(abs(x - 1.0)) > 0.001: x = (x + 1.0) / 2.0 rounds += 1 # a value chosen on rank 0, then the same everywhere let seed = world.broadcast(0, rank * 7 + 3) if seed > 0: world.barrier() # each rank takes a turn as the root: `r` is uniform var total = 0 for r in 0..size: total += world.broadcast(r, rank * 10) if rank == 0: print(f"{size} ranks: {rounds} rounds, seed {seed}, total {total}") ``` brine run allowed.br --ranks 4 ```text 4 ranks: 19 rounds, seed 3, total 60 ``` Branching on the rank is fine when no communication depends on it: printing on rank 0, doing rank-specific work before a collective, or returning early *after* the last call. ## Functions that communicate [Section titled “Functions that communicate”](#functions-that-communicate) The check is modular. For every function it records a summary: whether the function communicates, and which of its parameters must be uniform because the function branches on them around a communication. Call sites are checked against the summary, so the rules hold across calls, modules and recursion. Calling a communicating function under a varying condition, here an early `return` before it, is E0510, and the error shows where the function communicates: early.br ```brine fn report(x: int): let total = world.sum(x) print(total) fn main(): if world.rank() == 0: return report(1) ``` ```text error[E0510]: some ranks might not reach this communication --> early.br:8:5 | 8 | report(1) | ^^^^^^^^^ calls `report`, which communicates with other ranks 6 | if world.rank() == 0: | ----------------- this may differ between ranks 2 | let total = world.sum(x) | ------------ communicates here | = note: every rank must make the same `world` calls in the same order, otherwise the program could hang = help: move the call out of the rank-dependent branch or loop; compute rank-specific values first, then communicate on every rank = run `brine explain E0510` for more about this error error: could not compile due to 1 error ``` A parameter that controls communication inside the function must get a uniform argument: rounds.br ```brine fn rounds(n: int) -> int: var total = 0 for i in 0..n: total += world.sum(i) return total fn main(): print(rounds(world.rank())) ``` ```text error[E0511]: argument `n` of `rounds` must be the same on every rank --> rounds.br:8:18 | 8 | print(rounds(world.rank())) | ^^^^^^^^^^^^ might differ between ranks | = help: compute it from values that are the same everywhere, e.g. `world.size()`, constants, or the result of `world.broadcast` = run `brine explain E0511` for more about this error error: could not compile due to 1 error ``` Make the argument uniform first, for example with `world.broadcast`: ```brine fn rounds(n: int) -> int: var total = 0 for i in 0..n: total += world.sum(i) return total fn main(): let n = world.broadcast(0, world.rank() + 3) let r = rounds(n) if world.rank() == 0: print(f"{world.size()} ranks, {n} rounds: {r}") ``` brine run --ranks 3 ```text 3 ranks, 3 rounds: 9 ``` A parameter that is only *sent* may vary. Here `v` differs on every rank and `levels` is uniform, and the recursion is fine: ```brine fn tree_sum(v: int, levels: int) -> int: if levels == 0: return v return tree_sum(world.sum(v), levels - 1) fn main(): let r = tree_sum(world.rank(), 3) if world.rank() == 0: print(r) ``` brine run --ranks 4 ```text 96 ``` ## Roots and distances [Section titled “Roots and distances”](#roots-and-distances) `world.broadcast(root, v)` needs every rank to agree on the root, and `world.shift(v, by)` on the distance: root.br ```brine fn main(): let root = world.rank() let v = world.broadcast(root, 42) print(v) ``` ```text error[E0511]: the root rank of `world.broadcast` must be the same on every rank --> root.br:3:29 | 3 | let v = world.broadcast(root, 42) | ^^^^ might differ between ranks 2 | let root = world.rank() | ------------ because this depends on which rank is running | = help: compute it from values that are the same everywhere, e.g. `world.size()`, constants, or the result of `world.broadcast` = run `brine explain E0511` for more about this error error: could not compile due to 1 error ``` ## No send or recv [Section titled “No send or recv”](#no-send-or-recv) recv.br ```brine fn main(): let x = world.recv(0) ``` ```text error[E0512]: `world.recv` doesn't exist: unmatched sends and receives can deadlock --> recv.br:2:19 | 2 | let x = world.recv(0) | ^^^^ not available | = note: run `brine explain E0512` for how to express point-to-point patterns = help: every rank takes part in each communication: `world.shift(v, 1)` sends `v` to the next rank and returns the value from the previous one; `world.exchange(xs)` sends `xs[j]` to rank `j` = run `brine explain E0512` for more about this error error: could not compile due to 1 error ``` “Rank 0 sends a message to rank 1” is an `exchange` in which every other slot is empty. Filling the outbox under a rank-dependent `if` is fine, since the `if` doesn’t communicate: ```brine fn main(): var outbox = [""] * world.size() if world.rank() == 0 and world.size() > 1: outbox[1] = "hello" let inbox = world.exchange(outbox) if world.rank() == 1: print(f"rank 1 got {inbox[0]}") ``` brine run --ranks 4 ```text rank 1 got hello ``` A ring (each rank to its neighbour) is `world.shift(v, 1)`; a halo exchange in both directions is `shift(v, 1)` and `shift(v, -1)`. ## Communication inside par [Section titled “Communication inside par”](#communication-inside-par) `world` calls can’t appear in a `par` body ([E0503](/reference/errors/#e0503)): iterations run in no fixed order, so the ranks’ calls couldn’t line up. inpar.br ```brine fn main(): let total = par sum(world.sum(i) for i in 0..10) print(total) ``` ```text error[E0503]: `world` operations can't be used inside a `par` body --> inpar.br:2:25 | 2 | let total = par sum(world.sum(i) for i in 0..10) | ^^^^^^^^^^^^ not allowed here | = help: communicate before or after the parallel section = run `brine explain E0503` for more about this error error: could not compile due to 1 error ``` Reduce locally with `par`, then communicate once: `world.sum(par sum(f(i) for i in lo..hi))`. See [combining world with par](/parallelism/world/#combining-world-with-par). ## What the check doesn’t cover [Section titled “What the check doesn’t cover”](#what-the-check-doesnt-cover) The compiler proves that ranks can’t disagree about which calls to make. It can’t make a rank reach the next call: * **A rank that crashes** never sends its next message. The others stop with an error naming it instead of waiting forever (`panic (rank 0): rank 1 exited before sending the expected message`); see [when a rank fails](/parallelism/world/#when-a-rank-fails). * **A rank that loops forever** before a collective keeps the others waiting, as a slow rank would. * **Message sizes** can differ: `gather_all` of a string or an array works with a different length on each rank. `exchange` needs exactly `world.size()` elements on every rank, and panics otherwise. ## Rules [Section titled “Rules”](#rules) * Every `world` call except `rank()` and `size()` is collective. * No communication under a condition, loop, short-circuit or early exit that depends on a varying value ([E0510](/reference/errors/#e0510)). * `broadcast` roots, `shift` distances and parameters that steer communication must be uniform ([E0511](/reference/errors/#e0511)). * No `send` or `recv` ([E0512](/reference/errors/#e0512)); use `shift` and `exchange`. * No communication in `par` ([E0503](/reference/errors/#e0503)). * Make a local fact uniform with `world.sum`, `min`, `max`, `broadcast` or `gather_all`, then branch on it. # Deterministic reductions > The exact order in which par sum, product, min, max, any and all combine their elements, why it gives the same bits on any thread count or GPU, how integer overflow follows it, and how it differs from a sequential sum. A `par` reduction combines its elements in one fixed order that depends only on how many elements there are, never on the number of threads or the device. So a float `par sum` prints the same bits on 1 thread, 32 threads or a GPU. The order is a tree, not left to right, so it can differ from a sequential `sum` in the last bits, usually by being closer to the exact result. ## The order [Section titled “The order”](#the-order) For `par sum`, `par product`, `par min`, `par max`, `par any` and `par all`: 1. **Leaves.** The elements are split into leaves of 16 consecutive elements: elements 0-15 are leaf 0, 16-31 are leaf 1, and so on. The last leaf may be shorter. 2. **Inside a leaf, left to right.** Each leaf is folded from its first element to its last, starting from the reducer’s start value (`0` for `sum`, `1` for `product`): `((((0 + x0) + x1) + x2) + ... + x15)`. 3. **Between leaves, a balanced tree.** The leaf results are combined level by level: leaves 0 and 1, 2 and 3, 4 and 5, …; then those results in pairs; and so on until one value is left. A node without a partner at the end of a level moves up a level unchanged. 4. **Filters.** With `if`, elements that don’t pass are skipped inside their leaf. A leaf whose elements were all filtered out keeps its place in the tree but has no value, so its partner moves up unchanged. Every step depends only on positions, so the result depends only on the elements and their count. ## A picture [Section titled “A picture”](#a-picture) `par sum(x for x in xs)` with 80 elements makes 5 leaves: ```text elements 0..15 16..31 32..47 48..63 64..79 | | | | | leaves L0 L1 L2 L3 L4 each folded left to right \ / \ / | level 1 (L0 + L1) (L2 + L3) L4 L4 has no partner: moves up \ / | level 2 ((L0 + L1) + (L2 + L3)) L4 \ / level 3 (((L0 + L1) + (L2 + L3)) + L4) the result ``` A sequential `sum(x for x in xs)` is one long left fold instead: `(((0 + x0) + x1) + ...) + x79`. ## Why a fixed order [Section titled “Why a fixed order”](#why-a-fixed-order) Floating-point addition isn’t associative: `(a + b) + c` and `a + (b + c)` can round differently. A reduction that let each thread add up its own share and then combined the shares as they finished would print different bits depending on the thread count, the machine, and timing. Tests that compare output would fail for no reason, and a bug would be hard to tell from noise. Brine fixes the tree instead. Threads (and GPU blocks) only decide *who* computes each subtree, never *which* elements go in it, so: * the result is the same on 1 thread, 32 threads, a 128-core server or a GPU; * the result is the same from run to run; * dev, `--release` and native and C backends agree. The tree also loses less precision. In a left-to-right sum of `n` terms the rounding errors can grow with `n`; in a balanced tree they grow with the height, about `log2(n)`. Adding small terms to a large running total, the usual source of lost bits, mostly turns into adding numbers of similar size. ## Same bits everywhere: a demonstration [Section titled “Same bits everywhere: a demonstration”](#same-bits-everywhere-a-demonstration) This program adds up the first ten million terms of the harmonic series (1 + 1/2 + 1/3 + …), once with `par sum` and once with a sequential `sum`: harmonic.br ```brine fn main(): let n = 10_000_000 let tree = par sum(1.0 / float(i) for i in 1..=n) let left = sum(1.0 / float(i) for i in 1..=n) print(f"par sum: {tree}") print(f"sum: {left}") ``` Built with `--release` and run on a Ryzen 9 9950X3D and an RTX 4090 with different thread counts, with the GPU off and on: ```sh brine build harmonic.br --release -o harmonic BRINE_THREADS=1 BRINE_GPU=off ./harmonic BRINE_THREADS=2 BRINE_GPU=off ./harmonic BRINE_THREADS=8 BRINE_GPU=off ./harmonic BRINE_THREADS=32 BRINE_GPU=off ./harmonic BRINE_GPU=on BRINE_GPU_LOG=1 ./harmonic ``` Every run prints the same two lines: Output (all five runs) ```text par sum: 16.69531136585985 sum: 16.695311365857272 ``` The GPU run also logs where the reduction ran: stderr, BRINE_GPU=on BRINE_GPU_LOG=1 ```text brine gpu: NVIDIA GeForce RTX 4090 ready in 88 ms brine gpu: harmonic.br:3:16 par reduction n=10000000 -> gpu 1.252 ms (kernels 0.321 ms, copies 0.930 ms, 0.0 MB; cpu estimate 0.000 ms) ``` The exact sum is 16.6953113658598518153991… The `par sum` result is the closest `float` to it (it is off by 7.2 × 10⁻¹⁶, a fifth of a unit in the last place). The sequential sum is off by 2.6 × 10⁻¹², about 730 units in the last place, because by the end it adds terms near 10⁻⁷ to a total near 16. The two lines differ, and they always will: they’re different orders. What never changes is each line. ## Integers and overflow [Section titled “Integers and overflow”](#integers-and-overflow) Integer `par sum` and `par product` check every addition and multiplication, like all integer arithmetic in Brine, and the checks happen in the tree order. Whether a reduction overflows is decided by the partial results of *that* order, so it is the same on every machine, but it can differ from a sequential `sum`. The tree can overflow where the left fold doesn’t. Here leaf 1 holds two copies of the largest `int`, and folding that leaf overflows, while the left fold’s running total never leaves the range: ov.br ```brine fn main(): let big = 9_223_372_036_854_775_807 var xs = [0] * 32 xs[0] = -big xs[16] = big xs[17] = big print(sum(x for x in xs)) print(par sum(x for x in xs)) ``` Output ```text 9223372036854775807 panic: integer overflow in addition --> ov.br:8:11 ``` And the other way round: here the left fold passes through `big + 5`, while the tree combines `big - 5` with `10 - 10` and never leaves the range: ov2.br ```brine fn main(): let big = 9_223_372_036_854_775_807 var xs = [0] * 64 xs[0] = big xs[16] = -5 xs[32] = 10 xs[48] = -10 print(par sum(x for x in xs)) print(sum(x for x in xs)) ``` Output ```text 9223372036854775802 panic: integer overflow in addition --> ov2.br:9:11 ``` When the exact total fits, both orders give the same integer, since integer addition is associative as long as nothing overflows. If partial results may come near the limits, use a wider type (`i128`) or `float`. `min`, `max`, `any` and `all` never overflow. `min` and `max` compare the way the sequential versions do: a later value replaces the current one only if it is strictly smaller (or larger). ## par sum and sum [Section titled “par sum and sum”](#par-sum-and-sum) | | `sum(...)` | `par sum(...)` | | ---------------------------- | ----------------------- | ---------------------------------------- | | order | left to right, from `0` | leaves of 16, then a balanced tree | | threads | one | the pool, and the GPU when it pays | | float result | the left fold’s bits | the tree’s bits, the same on any machine | | integer overflow | of the running total | of any partial result in the tree | | accuracy for long float sums | error can grow with `n` | error grows with `log2(n)` | Both are deterministic. They’re just different orders, so pick one and keep it. If you need a result that matches another language’s plain loop bit for bit, use the sequential `sum` (or a `for` loop). ## The 0.4.0 change [Section titled “The 0.4.0 change”](#the-040-change) Up to Brine 0.3.0, a `par` reduction split its elements into at most 256 chunks, summed each chunk left to right and added the chunk results in order. That was also independent of the thread count, but a GPU needs thousands of independent partial results, so 0.4.0 changed to the leaves and tree on this page. It is a breaking change (see the [changelog](/reference/changelog/)): * `par sum` and `par product` of floats can differ from 0.3.0 in the last bits, usually closer to the exact result; * whether an integer `par sum` or `par product` overflows is decided by the new order. Programs that need the old bits should sum sequentially. ## Related pages [Section titled “Related pages”](#related-pages) * [Parallelism overview](/parallelism/overview/#par-reductions): the reducers and filters * [GPU offload](/parallelism/gpu/#same-results): how the GPU runs the same tree * [Multiple processes: world](/parallelism/world/#combining-world-with-par): `world.sum` combines ranks in rank order # GPU offload > Which par constructs and bodies run on an NVIDIA (CUDA) or Apple (Metal) GPU, how the runtime decides when, why the results are the same bits as on the CPU, BRINE_GPU and BRINE_GPU_LOG, benchmarks, and tips for GPU-friendly code. A `par for`, `par [...]` or `par` reduction can run on an NVIDIA GPU (through CUDA) or an Apple GPU (through Metal) with no annotation and no change in meaning. The compiler decides which constructs *can* run there, the runtime decides for each launch whether that is *faster*, and either way the results are the same bits as on the CPU. A check that fails on the device panics exactly as it would on the CPU. ## What you need [Section titled “What you need”](#what-you-need) * **NVIDIA** on Windows or Linux: the normal display driver. The runtime loads `nvcuda.dll` or `libcuda.so.1` when it first needs it. No CUDA toolkit. * **Apple silicon** (M1 and later) on macOS: nothing. Metal is reached through the Objective-C runtime. * Nothing at build time. The program carries its kernels (PTX for CUDA, Metal Shading Language on macOS), and the driver compiles them for the device it finds. Without a driver or a device, every construct runs on the CPU, as before. AMD and Intel GPUs aren’t supported yet; see [the roadmap](#roadmap). ## A first look [Section titled “A first look”](#a-first-look) The [Collatz example](/parallelism/overview/#threads) runs one `par max` over ten million numbers. Repeat it four times and ask the runtime what it did: gpu_demo.br ```brine fn collatz_len(n: int) -> int: var x = n var steps = 0 while x != 1: x = x / 2 if x % 2 == 0 else 3 * x + 1 steps += 1 return steps fn main(): let n = 10_000_000 for round in 0..4: let t0 = clock() let longest = par max(collatz_len(i) for i in 1..=n) let ms = (clock() - t0) * 1000.0 print(f"round {round}: longest chain {longest} steps, {ms:.1f} ms") ``` ```sh brine build gpu_demo.br --release -o gpu_demo BRINE_GPU_LOG=1 ./gpu_demo ``` On a Ryzen 9 9950X3D with an RTX 4090 (stderr first, then stdout): Output ```text brine gpu: gpu_demo.br:13:23 par reduction n=10000000 -> cpu 68.040 ms (starting the GPU) brine gpu: NVIDIA GeForce RTX 4090 ready in 105 ms brine gpu: gpu_demo.br:13:23 par reduction n=10000000 -> cpu 71.043 ms (starting the GPU) brine gpu: gpu_demo.br:13:23 par reduction n=10000000 -> gpu 5.323 ms (kernels 4.106 ms, copies 1.217 ms, 0.0 MB; cpu estimate 69.542 ms) brine gpu: gpu_demo.br:13:23 par reduction n=10000000 -> gpu 3.996 ms (kernels 3.968 ms, copies 0.028 ms, 0.0 MB; cpu estimate 69.542 ms) round 0: longest chain 685 steps, 68.7 ms round 1: longest chain 685 steps, 71.4 ms round 2: longest chain 685 steps, 5.9 ms round 3: longest chain 685 steps, 4.6 ms ``` The first round would be faster on the GPU, but not by enough to wait for the device to start, so the runtime starts it in the background and runs the round on all 32 threads. The second round starts before the device is quite ready. From the third round on, the reduction runs on the GPU, about 15 times faster. Every round prints the same answer, and `BRINE_GPU=off` prints it too, in about 63 ms per round. ## Which constructs can run on a GPU [Section titled “Which constructs can run on a GPU”](#which-constructs-can-run-on-a-gpu) The compiler decides this, when it builds the program: * **`par for`, `par [...]` and `par` reductions** (`sum`, `product`, `min`, `max`, `any`, `all`, with or without a filter) can. * **`par(a, b)` forks don’t**, nor do constructs nested inside another `par`, nor a construct whose body contains another `par` construct. A construct can run on a GPU when its body, and every function it calls, uses only: * `int`, `float` and the other integer and float types up to 64 bits, and `bool`: arithmetic, comparisons, conversions, shifts; * branches, loops, `match` on numbers and enums without payloads, local variables, structs and tuples; * reads of captured scalars and of arrays, including arrays of arrays and arrays of structs; * writes to `out[i]` (in `par for`); * `sqrt`, `abs`, `floor`, `ceil`, `round`, `min` and `max`; * calls to functions that follow the same rules (recursion is fine on CUDA); * checks that can fail: overflow, bounds, division by zero, `assert`, `panic`, contracts. Any of these keeps it on the CPU: * anything that allocates: strings, f-strings, maps, enums with payloads, new arrays (a body that returns an array or a string, for example); * `print`, `eprint`, file and process I/O, `world`; * calling a function value; * `sin`, `cos`, `tan`, `exp`, `log`, `pow` and the like, and float `%`: their last bit depends on the C library, and the GPU’s versions round differently; * 128-bit integers; * on Apple GPUs, `float` (f64), because they have none, recursion, because Metal has none, and subnormal `f32` constants. Use `f32` for Metal. `BRINE_GPU_LOG=1` says why each construct that can’t run on a GPU stayed on the CPU. These are real lines from a test program: ```text brine gpu: elig.br:13:13 par reduction n=1000000 -> cpu 0.329 ms (not GPU-eligible: uses sin) brine gpu: elig.br:14:13 par map n=1000000 -> cpu 11.836 ms (not GPU-eligible: its context holds strings, maps or enums with payloads) brine gpu: elig.br:26:14 par reduction n=1000000 -> cpu 0.509 ms (not GPU-eligible: uses pow) brine gpu: pr.br:8:9 par for n=100000 -> cpu 0.371 ms (not GPU-eligible: calls brine_sb_init) brine gpu: elig.br:21:13 par map n=10000 -> cpu 6.136 ms (not GPU-eligible: calls brine_par_reduce) ``` The last two name runtime functions: `brine_sb_init` builds a string (here, for `print`), and `brine_par_reduce` is a nested `par` reduction. Reading a map in the body gives the “strings, maps or enums” reason too. ## When the runtime uses it [Section titled “When the runtime uses it”](#when-the-runtime-uses-it) For each launch of an eligible construct, the runtime compares the two sides: 1. **Too small or nested:** constructs with fewer than 4096 elements, and constructs inside another `par`, run on the CPU. 2. **The CPU’s time** is estimated by running the first elements on one thread for about 100 µs, then measured on every later launch. 3. **The device’s time** is guessed from that (f64 code counts as much slower on most cards), then measured after its first run. To it the runtime adds the copies, at the bandwidth it has measured, and the cost of each launch. 4. **It uses the device when that is at least 25% faster** (the device’s time, copies included, is under 75% of the CPU’s). 5. **Warm-up.** Getting a device ready takes 0.1 to 0.7 s (about 0.1 s on Windows, 0.4 s on Linux). If the device isn’t ready, its start-up time counts against it. A construct that would gain, but not enough to cover the wait, starts the device in the background and runs on the CPU until it is ready. That’s what happened to rounds 0 and 1 above. The thread count matters too. Run the Collatz program once with one thread and the CPU estimate becomes 1.2 s, so waiting 91 ms for the device pays at once: BRINE_THREADS=1 BRINE_GPU_LOG=1 ./collatz ```text brine gpu: NVIDIA GeForce RTX 4090 ready in 91 ms brine gpu: collatz.br:12:19 par reduction n=10000000 -> gpu 9.359 ms (kernels 8.440 ms, copies 0.919 ms, 0.0 MB; cpu estimate 1221.429 ms) longest chain below 10000000: 685 steps (101 ms) ``` A short program that runs a construct only once may end before the device is ready, and then everything runs on the CPU. That is the right choice for it. ## Same results [Section titled “Same results”](#same-results) The kernels use the CPU’s semantics, not the device’s defaults: * every float operation rounds to nearest even, and none is fused into a multiply-add (FMA); * `/` and `sqrt` are exactly rounded; * subnormal numbers are kept (on NVIDIA); * integer division, `%`, conversions and shifts follow Brine’s rules, not the device’s. A reduction runs the same [leaves and tree](/parallelism/deterministic-reductions/) as on the CPU: each launch reduces an aligned subtree of up to 2²⁴ leaves, and the CPU combines the subtrees’ results in the same tree. So `par sum` of floats gives the same bits on the GPU as on one CPU thread. **Checks.** Overflow, bounds, division by zero, `assert` and contract checks set a flag on the device instead of panicking. When the flag is set, the device’s results are dropped, and the construct runs again on the CPU, which panics with the usual message: chk.br ```brine fn main(): let n = 1_000_000 var xs = [1] * n xs[777_777] = 9_223_372_036_854_775_807 var out = [0] * n par for i in 0..n: out[i] = xs[i] * 2 print(out[0]) ``` BRINE_GPU=on BRINE_GPU_LOG=1 ```text brine gpu: NVIDIA GeForce RTX 4090 ready in 96 ms brine gpu: chk.br:6:9 par for n=1000000: a check failed on the GPU, running it on the CPU panic: integer overflow in multiplication --> chk.br:7:18 ``` Rerunning is safe: results are copied back only after a launch succeeded, and a body can only write `out[i]`, so the CPU starts from the same state. **Subnormals on Apple GPUs.** Apple GPUs flush subnormal `f32` values to zero. On Metal, a subnormal operand or result sets the flag too, and the construct reruns on the CPU, so you still get the CPU’s bits. ## Copies [Section titled “Copies”](#copies) Every launch copies the arrays the body reads to the device, and the arrays it writes back. Arrays are values, so nothing else can see them in between. Keeping arrays on the device from one construct to the next is [future work](#roadmap), so a construct that does little work per byte (a plain sum of a big array, a light map) stays on the CPU, where memory is faster than the PCIe bus. The `MB` in the log is what a launch copied. ## BRINE_GPU and BRINE_GPU_LOG [Section titled “BRINE_GPU and BRINE_GPU_LOG”](#brine_gpu-and-brine_gpu_log) | Variable | Value | Effect | | --------------- | ---------------- | ------------------------------------------------------------------------------------------------- | | `BRINE_GPU` | `auto` (default) | the runtime picks, as above | | | `on` (or `1`) | use the GPU for every construct that can run there, at any size, waiting for the device if needed | | | `off` (or `0`) | never use a GPU | | `BRINE_GPU_LOG` | `1` | print to stderr where each construct ran, how long it took, and why | The program reads them itself, so they work with `brine run` and with a built binary. Each log line names the construct by file, line and column, its kind and element count, then the side it ran on: ```text brine gpu: :: n= -> gpu ms (kernel(s) , copies , MB; cpu estimate ) brine gpu: :: n= -> cpu ms () ``` Reasons for the CPU are `faster on the CPU`, `starting the GPU`, `small`, `BRINE_GPU=off`, `a check failed on the GPU`, `too big for the GPU's memory`, `the GPU is busy` (another thread is using it), or `not GPU-eligible: ...`. `cpu estimate` is `0.000` with `BRINE_GPU=on`, which doesn’t measure the CPU. See [environment variables](/tooling/environment-variables/) for the others, including `BRINE_DUMP_PTX=file`, which writes the kernel module when building. ## Benchmarks [Section titled “Benchmarks”](#benchmarks) From `bench/gpu` in the Brine repository: Windows 11, Ryzen 9 9950X3D (16 cores, 32 threads), RTX 4090, Brine 0.3.0 plus the GPU work, `--release`. Times in ms for the construct alone; *cold* is the first run in the process (for the GPU, including getting the device ready), *warm* the best later run. The GPU was shared with a running game, so GPU times varied by up to 1.5x. | Benchmark | CPU warm | GPU cold | GPU warm | auto warm | auto picks | | --------------------------------------------------------------- | -------- | -------- | -------- | --------- | ------------------------------------- | | `vecmap` f64: 16M elements, 16 multiply-adds and divisions each | 6.7 | 122-146 | 29-36 | 6.8 | CPU | | `nbody` f64: 16384 bodies, all pairs | 41-44 | 139-147 | 37-50 | 42-45 | CPU | | `nbody` f32 | 35-40 | 8-10 | 4.5 | 4.5-7.5 | GPU | | `mandelbrot` f64: 4096 x 4096, up to 512 iterations | 107-117 | 149-170 | 46-70 | 48-72 | CPU, then GPU | | sum of 64M `float`s | 7.5-8.4 | 154-171 | 43-47 | 8 | CPU | | sum of 64M `f32`s | 3.9 | 23-28 | 21-26 | 4.1 | CPU | | sum of a computed series, 256M terms | 14-16 | 6.4-12 | 5.8-10 | 15 | CPU (ends before the device is ready) | Re-run for this page with Brine 0.4.0 on the same machine, the n-body step (`bench/gpu/nbody.br`) printed the same checksum in all three modes: | `BRINE_GPU` | f64 cold | f64 warm | f32 cold | f32 warm | | ----------- | -------- | -------- | -------- | -------- | | `off` | 40.2 | 39.9 | 33.9 | 33.3 | | `on` | 149.0 | 30.8 | 4.6 | 3.7 | | `auto` | 42.6 | 40.4 | 34.5 | 3.7 | And `examples/find_signal.br` (32 matched-filter searches over a million `f32` samples) took 933 ms with `BRINE_GPU=off`, 233 ms on `auto` and 173 ms with `on`. On an M1 Mac mini (8-core GPU), the f32 n-body step took 58 ms on the CPU and 63 ms warm on the GPU, so `auto` kept it on the CPU. Larger Apple GPUs (M1 Pro and up) should move it over. What the numbers say: * **Work per byte decides.** The f32 n-body step copies 0.5 MB and does 268 million interactions: 8x faster on the GPU. A sum or a light map of a big array is bound by the copy over PCIe (8-10 GB/s) while the CPU reads memory at 60+ GB/s, so it stays on the CPU. * **f64 is slow on GeForce cards** (1/64 of the f32 rate). Mandelbrot in f64 still gains 1.6-2.3x; the n-body step in f64 runs at about the CPU’s speed. * **Exact rounding costs a little.** Correctly rounded `/` and `sqrt` and no FMA are slower than the device’s defaults. That is the price of the same bits. ## Writing GPU-friendly code [Section titled “Writing GPU-friendly code”](#writing-gpu-friendly-code) * **Do a lot per element.** Loops inside the body (an inner `for` over a row, an iteration count) are where a GPU wins. A body that reads one number and adds one is copy-bound. * **Prefer `f32` when its precision is enough.** It is many times faster on GeForce cards, and it is the only float type Apple GPUs have. * **Keep the body allocation-free.** Return numbers, tuples or structs, not arrays or strings. Pass in the arrays you read; don’t build them in the body. * **Keep the transcendental math out of the hot body.** `sin`, `exp`, `log` and `pow` keep a construct on the CPU. Compute a table before the construct, or rewrite with `sqrt` and arithmetic where you can. * **Don’t print in the body.** Collect results and print after. * **Reuse a construct.** A construct that runs many times (in a time step loop, or once per input) gets measured, and from then on runs on the faster side. The device’s start-up is paid once per process. * **Prove the indexes.** Checks the prover removes cost nothing on either side. [`examples/find_signal.br`](https://github.com/brinelang/brine/blob/main/examples/find_signal.br) states its ranges with `requires`, and no runtime check is left in its search loop. See [runtime checks](/proofs/runtime-checks/). * **Look.** `BRINE_GPU_LOG=1` tells you where each construct ran and why; `BRINE_GPU=on` shows what the GPU would do. ## Roadmap [Section titled “Roadmap”](#roadmap) * **Arrays kept on the device** between constructs, so a sequence of `par` constructs over the same data copies it once. * **`par(...)` forks and nested constructs** on the GPU. * **AMD and Intel GPUs** through Vulkan compute with SPIR-V kernels (the driver loaded at run time, as CUDA is), with exactly rounded `/` and `sqrt`, a capability check for subnormals and f64, and a self-test at start-up that turns the GPU off if its results differ from the CPU’s. This waits for real devices to test on. It includes the AMD and Intel GPUs in Intel Macs; Metal is used only on Apple GPUs for now. ## Related pages [Section titled “Related pages”](#related-pages) * [Parallelism overview](/parallelism/overview/) * [Deterministic reductions](/parallelism/deterministic-reductions/) * [Environment variables](/tooling/environment-variables/) # Parallelism overview > The four par constructs (par for, par [...], par reductions, par(a, b)), how many threads they use, what a par body may contain, nesting, and which construct to pick. Parallelism in Brine is syntax: put `par` in front of a loop, a comprehension, a reduction or a pair of expressions, and it runs on every core (and on the GPU, when that’s faster). The compiler rejects any `par` body that could race, and reductions give the same bits on any number of threads, so adding `par` never changes what a correct program prints. ## The four constructs [Section titled “The four constructs”](#the-four-constructs) | Construct | What it does | Result | | ----------------------------- | ---------------------------------------------------------------- | ------------------------------ | | `par for i in lo..hi:` | runs the body once per `i`, in parallel | nothing; writes go to `out[i]` | | `par [e for x in xs]` | parallel map: one result per element | an array, in order | | `par sum(e for x in xs if c)` | parallel reduction: `sum`, `product`, `min`, `max`, `any`, `all` | one value | | `par(f(a), g(b))` | fork-join: evaluates the expressions at the same time | a tuple | ## A complete example [Section titled “A complete example”](#a-complete-example) overview\.br ```brine fn collatz_len(n: int) -> int: var x = n var steps = 0 while x != 1: x = x / 2 if x % 2 == 0 else 3 * x + 1 steps += 1 return steps fn main(): let n = 1_000_000 # par [...]: one result per element, in order let lens = par [collatz_len(i) for i in 1..=n] # par reductions, with and without a filter let longest = par max(l for l in lens) let total = par sum(l for l in lens) let long_ones = par sum(1 for l in lens if l > 300) # par for: each iteration writes only its own element of `hist` var hist = [0] * 8 par for b in 0..8: hist[b] = sum(1 for l in lens if l / 100 == b) # par(a, b): independent expressions, run at the same time let (a, b) = par(collatz_len(27), collatz_len(97)) print(f"longest: {longest}, average: {float(total) / float(n):.2f}, over 300: {long_ones}") print(f"by hundreds: {hist}") print(f"27 takes {a} steps, 97 takes {b}") ``` ```sh brine run overview.br ``` Output ```text longest: 524, average: 131.43, over 300: 5617 by hundreds: [344030, 530366, 119920, 5574, 106, 4, 0, 0] 27 takes 111 steps, 97 takes 118 ``` The output is the same on every run and every machine, whatever the number of threads. ## par for [Section titled “par for”](#par-for) `par for` runs its body once for every value of the loop variable, spread over the thread pool. The iterations run in no particular order, so the body can’t change variables from outside the loop, with one exception: an outer array may be written at the loop index. ```brine fn main(): let n = 8 var out = [0.0] * n par for i in 0..n: out[i] = sqrt(float(i)) print(out) ``` Output ```text [0.0, 1.0, 1.4142135623730951, 1.7320508075688772, 2.0, 2.23606797749979, 2.449489742783178, 2.6457513110645907] ``` * The loop can be over a range (`lo..hi`, `lo..=hi`, `range(a, b, step)`), an array, a map, or `enumerate(xs)`. Only a loop over a range can write `out[i]`; the others can read outer values but not write them. * `out[i]` must be exactly the loop variable. `out[2 * i]` and `out[i + 1]` are errors, and once the body writes `out[i]` it may not read `out` any other way (`out[i - 1]`, `len(out)`). See [race freedom](/parallelism/race-freedom/). * `continue` ends the current iteration. `break` and `return` are errors ([E0505](/reference/errors/#e0505)): other iterations are already running. * A `par for` can’t have an `invariant` ([E0507](/reference/errors/#e0507)): there is no state between iterations. ## par \[…] [Section titled “par \[…\]”](#par-) A parallel map evaluates the expression for every element and returns the results as an array, in the order of the input. It is the usual way to compute one value per element. ```brine fn main(): let words = ["fig", "pear", "plum", "banana"] let lengths = par [len(w) for w in words] let squares = par [i * i for i in 0..6] print(lengths, squares) ``` Output ```text [3, 4, 4, 6] [0, 1, 4, 9, 16, 25] ``` `par [...]` can’t have an `if` filter ([E0508](/reference/errors/#e0508)), because each element’s place in the result has to be known before the elements are computed. Filter with a sequential comprehension afterwards, or count and combine with a filtered reduction. ## par reductions [Section titled “par reductions”](#par-reductions) `par sum`, `par product`, `par min`, `par max`, `par any` and `par all` combine one value per element into a single result. A reduction may filter with `if`. ```brine fn main(): let total = par sum(i for i in 0..10 if i % 2 == 0) let p = par product(i for i in 1..6) let lo = par min(i * 3 - 7 for i in 0..10) let hi = par max(i for i in 0..10 if i < 5) let a = par any(i == 7 for i in 0..10) let b = par all(i < 10 for i in 0..10) print(total, p, lo, hi, a, b) ``` Output ```text 20 120 -7 4 true true ``` * Over no elements, `sum` is `0`, `product` is `1`, `any` is `false` and `all` is `true`. `min` and `max` of nothing panic (`max() of an empty sequence`). * Integer `sum` and `product` stop the program on overflow, as every integer operation does. * The elements are combined in one fixed order that depends only on how many there are, so a float `par sum` gives the same bits on 1 thread, 32 threads or a GPU. That order isn’t left to right, so it can differ from a sequential `sum` in the last bits (usually by being closer to the exact result). See [deterministic reductions](/parallelism/deterministic-reductions/). ## par(a, b) [Section titled “par(a, b)”](#para-b) A fork-join evaluates two or more independent expressions at the same time and returns their values as a tuple. ```brine fn fib(n: int) -> int: if n < 2: return n return fib(n - 1) + fib(n - 2) fn main(): let (a, b, c) = par(fib(30), fib(31), fib(32)) print(a, b, c) ``` Output ```text 832040 1346269 2178309 ``` The expressions follow the same rules as any `par` body: they can read variables but not change them, so `par(xs.pop(), len(xs))` and `par(bump(ref n), 5)` are errors. `par(e)` with one expression is [E0113](/reference/errors/#e0113). ## Threads [Section titled “Threads”](#threads) `par` constructs share one thread pool per process. It has one thread per core by default; change it with `--threads` or `BRINE_THREADS`: ```sh brine run collatz.br --release --threads 8 BRINE_THREADS=8 brine run collatz.br --release # the same; also works for built binaries ``` collatz.br ```brine fn collatz_len(n: int) -> int: var x = n var steps = 0 while x != 1: x = x / 2 if x % 2 == 0 else 3 * x + 1 steps += 1 return steps fn main(): let n = 10_000_000 let t0 = clock() let longest = par max(collatz_len(i) for i in 1..=n) let t1 = clock() print(f"longest chain below {n}: {longest} steps ({(t1 - t0) * 1000.0:.0f} ms)") ``` With the GPU turned off (`BRINE_GPU=off`) on a Ryzen 9 9950X3D (16 cores, 32 threads), `--release`: | `BRINE_THREADS` | 1 | 2 | 4 | 8 | 16 | 32 | | --------------- | ---- | --- | --- | --- | --- | -- | | time (ms) | 1355 | 710 | 360 | 181 | 102 | 63 | Every run prints `longest chain below 10000000: 685 steps`. With the GPU left on `auto`, the same program on an RTX 4090 takes 4-5 ms per run once the device is ready; see [GPU offload](/parallelism/gpu/). With [`--ranks N`](/parallelism/world/), every rank is its own process with its own pool. On one machine, give each rank its share of the cores (`--ranks 4 --threads 8` on 32 threads). See also [environment variables](/tooling/environment-variables/). ## What a par body can contain [Section titled “What a par body can contain”](#what-a-par-body-can-contain) A `par` body is ordinary Brine with one restriction: it can’t change anything that other iterations can see. In practice: | Allowed | Not allowed | | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | reading any outer variable, array or map | assigning an outer variable ([E0501](/reference/errors/#e0501)) | | `out[i] = ...` in `par for i in` a range | `out[j]` for any other index, or reading `out` elsewhere in that loop ([E0502](/reference/errors/#e0502)) | | local `let`/`var`, local arrays, `push` on them | `push`, `pop`, `sort` and other changing methods on outer arrays; changing maps ([E0501](/reference/errors/#e0501)) | | calling functions, `ref` arguments to *local* variables | `ref` arguments to outer variables ([E0501](/reference/errors/#e0501)) | | `print`, `eprint`, `clock`, `read_file`, `getenv` | `write_file`, `run`, `exit`, changing `os` calls, `world` ([E0503](/reference/errors/#e0503)) | | `continue`, `assert`, panics | `break`, `return` ([E0505](/reference/errors/#e0505)), `?` ([E0342](/reference/errors/#e0342)) | `print` is allowed, but the lines come out in whatever order the iterations finish (each `print` writes its whole line at once). The rules and the errors are on the [race freedom](/parallelism/race-freedom/) page. ## Nesting [Section titled “Nesting”](#nesting) A `par` construct inside another one is allowed. The inner one runs on the thread that runs the outer iteration, sequentially, so nesting never starts more threads than the pool has: ```brine fn main(): let rows = par [par sum(i * j for j in 0..4) for i in 0..4] print(rows) ``` Output ```text [0, 6, 12, 18] ``` Usually it’s best to put `par` on the outermost loop that has enough iterations to keep every core busy, and leave the inner ones sequential. Nested constructs don’t run on the GPU, and neither does a construct whose body contains one. ## Which one to use [Section titled “Which one to use”](#which-one-to-use) | You want | Use | | ---------------------------------------------------- | ---------------------------------------------------------------------- | | one new value per element | `par [f(x) for x in xs]` | | one number from many (a total, a count, a maximum) | `par sum(...)`, `par max(...)`, … with `if` to filter | | a filtered array | a sequential `[x for x in xs if p(x)]`, or `par [...]` of flags first | | to fill an existing array, or several arrays at once | `par for i in 0..n:` writing `a[i]`, `b[i]` | | two or three unrelated pieces of work | `par(f(), g())` | | work spread over processes or machines | the [`world` module](/parallelism/world/), with `par` inside each rank | `par` pays off when a construct does enough work to cover handing it to the pool. Loops over a few dozen cheap elements are fine to leave sequential. ## Related pages [Section titled “Related pages”](#related-pages) * [Race freedom](/parallelism/race-freedom/): the rule and every error it gives * [Deterministic reductions](/parallelism/deterministic-reductions/): the order of a `par sum` * [GPU offload](/parallelism/gpu/): which constructs run on a GPU, and when * [Multiple processes: world](/parallelism/world/) and [deadlock freedom](/parallelism/deadlock-freedom/) # Race freedom > The compile-time rule that makes every par body free of data races, why it is enough, every error it reports (E0501, E0502, E0503, E0505), and how to restructure code that breaks it. A data race in Brine is a compile error. Inside a `par` body, variables from outside are read-only, with one exception: `par for i in ...` may write an outer array at exactly `[i]`, and then may not touch that array any other way. Because Brine values never alias, that rule is all it takes. ## The rule [Section titled “The rule”](#the-rule) Inside the body of `par for`, `par [...]`, a `par` reduction or `par(a, b)`: 1. **Outer variables are read-only.** You can’t assign them, call a method that changes them (`push`, `pop`, `sort`, `insert`, …), change a map, or pass them as `ref`. 2. **The exception: `out[i]`.** In `par for i in lo..hi`, an array declared outside the loop may be written at `out[i]`, where the index is exactly the loop variable. The element may be a struct or an array itself (`out[i].x = ...`, `grid[i][j] = ...`). 3. **An array written at `out[i]` isn’t accessed any other way in that loop.** No `out[i - 1]`, no `len(out)`, no passing `out` to a function. 4. **No effects that depend on order.** `write_file`, `run`, `exit`, the `os` calls that change things outside the program, and `world` communication aren’t allowed. `print` is. 5. **Every iteration runs to its end.** No `break`, no `return`, no `?`. `continue` is fine. Variables declared inside the body are local to one iteration, and you can do anything with them. ```brine fn fill(ref xs: [int]): for k in 0..len(xs): xs[k] = k fn main(): let base = [10, 20, 30, 40] var out = [0] * 4 par for i in 0..4: var local = [0] * 3 # local to this iteration fill(ref local) # `ref` to a local is fine local.push(base[i]) # reading outer values is fine out[i] = len(local) + local[3] print(out) ``` Output ```text [14, 24, 34, 44] ``` ## Why this is enough [Section titled “Why this is enough”](#why-this-is-enough) A data race needs two threads touching the same memory at the same time, one of them writing. In most languages the compiler can’t rule that out, because two names can point at the same memory. In Brine they can’t: * **Values, not shared objects.** `let b = a` copies, and arguments are passed by value. Arrays and strings share memory only until one copy is changed (copy-on-write), and the reference counts are atomic. Nothing you can write reaches another variable’s memory. * **References are second-class.** A `ref` can’t be stored in a struct or an array, or returned, so the compiler always knows which variable a `ref` names. `let r = ref out` inside a `par` body counts as `out`. * **Functions can only change what they are given as `ref`.** A function called from a `par` body gets copies of outer values, and rule 1 stops it from getting a `ref` to one. So the only memory several iterations can reach is the outer variables. Rule 1 makes them read-only. Rule 2 lets iteration `i` write element `i`, which no other iteration writes, and rule 3 makes sure no other iteration reads it while it changes. With no writes in common and no reads of another iteration’s writes, there is no race, and the result can’t depend on timing. The rules are checked on the body as written, not by analysis of what the indexes might be, so the errors are predictable: the same code is accepted or rejected on every machine. ## Assigning an outer variable [Section titled “Assigning an outer variable”](#assigning-an-outer-variable) race.br ```brine fn main(): let xs = [1, 2, 3, 4] var total = 0 par for i in 0..len(xs): total += xs[i] print(total) ``` ```text error[E0501]: cannot assign to `total` inside a `par` body --> race.br:5:9 | 5 | total += xs[i] | ^^^^^ would be a data race | = note: `total` is shared by all parallel iterations = help: a `par for` may only write `total[i]` (its own element); to combine values use `par sum(...)`, `par [...]`, or a sequential loop = run `brine explain E0501` for more about this error error: could not compile due to 1 error ``` Combining values from every iteration is what reductions are for: ```brine fn main(): let xs = [1, 2, 3, 4] let total = par sum(x for x in xs) print(total) ``` Output ```text 10 ``` A reduction is both race-free and [deterministic](/parallelism/deterministic-reductions/), and with a filter it also covers counting (`par sum(1 for x in xs if x > 0)`), finding the largest (`par max`), and testing (`par any`, `par all`). ## Writing another element [Section titled “Writing another element”](#writing-another-element) `out[i]` must use exactly the loop variable. Any other index could be the one another iteration writes: stride.br ```brine fn main(): let n = 5 var out = [0] * (2 * n) par for i in 0..n: out[2 * i] = i * i print(out) ``` ```text error[E0501]: cannot assign to `out` inside a `par` body --> stride.br:5:9 | 5 | out[2 * i] = i * i | ^^^^^^^^^^ would be a data race | = note: `out` is shared by all parallel iterations = help: a `par for` may only write `out[i]` (its own element); to combine values use `par sum(...)`, `par [...]`, or a sequential loop = run `brine explain E0501` for more about this error error: could not compile due to 1 error ``` Loop over the elements you write instead, and work out the input from the output index: ```brine fn main(): let n = 5 var out = [0] * (2 * n) par for j in 0..2 * n: if j % 2 == 0: out[j] = (j / 2) * (j / 2) print(out) ``` Output ```text [0, 0, 1, 0, 4, 0, 9, 0, 16, 0] ``` ## Reading the array you write [Section titled “Reading the array you write”](#reading-the-array-you-write) Once a loop writes `xs[i]`, every other use of `xs` in it is an error, reads included, because the neighbouring element may be changing at that moment: smooth.br ```brine fn main(): var xs = [0.0, 3.0, 9.0, 3.0, 0.0, 6.0] par for i in 1..len(xs) - 1: xs[i] = (xs[i - 1] + xs[i] + xs[i + 1]) / 3.0 print(xs) ``` ```text error[E0502]: `xs` is written at `xs[i]` in this `par for`, so it can't be accessed any other way --> smooth.br:4:18 | 4 | xs[i] = (xs[i - 1] + xs[i] + xs[i + 1]) / 3.0 | ^^^^^^^^^ accessed here | = note: other iterations may be writing to the other elements at the same time = help: only use `xs[i]` inside the loop, or copy the data you need before the loop = run `brine explain E0502` for more about this error error: could not compile due to 1 error ``` Read from a copy made before the loop. The copy costs nothing until the loop writes to `xs` (copy-on-write), and the result no longer depends on which neighbour was updated first: ```brine fn main(): var xs = [0.0, 3.0, 9.0, 3.0, 0.0, 6.0] let old = xs par for i in 1..len(old) - 1: xs[i] = (old[i - 1] + old[i] + old[i + 1]) / 3.0 print(xs) ``` Output ```text [0.0, 4.0, 5.0, 4.0, 3.0, 6.0] ``` A computation where each element needs the *new* value of the one before it (a running total, for example) is sequential by nature. Write it as a sequential loop. ## Changing an outer array [Section titled “Changing an outer array”](#changing-an-outer-array) `push`, `pop`, `insert`, `sort` and the other changing methods count as writes: collect.br ```brine fn is_special(i: int) -> bool: return i % 7 == 0 and i % 2 == 1 fn main(): var found: [int] = [] par for i in 0..100: if is_special(i): found.push(i) print(found) ``` ```text error[E0501]: cannot push to `found` inside a `par` body --> collect.br:8:13 | 8 | found.push(i) | ^^^^^ would be a data race | = note: `found` is shared by all parallel iterations = help: a `par for` may only write `found[i]` (its own element); to combine values use `par sum(...)`, `par [...]`, or a sequential loop = run `brine explain E0501` for more about this error error: could not compile due to 1 error ``` Compute the expensive part in parallel, one result per element, then collect sequentially. This also keeps the results in order, which pushing from many threads wouldn’t: ```brine fn is_special(i: int) -> bool: return i % 7 == 0 and i % 2 == 1 fn main(): let n = 100 let keep = par [is_special(i) for i in 0..n] let found = [i for i in 0..n if keep[i]] let count = par sum(1 for i in 0..n if keep[i]) print(found, count) ``` Output ```text [7, 21, 35, 49, 63, 77, 91] 7 ``` ## Passing an outer variable as ref [Section titled “Passing an outer variable as ref”](#passing-an-outer-variable-as-ref) A `ref` argument lets the function change the variable, so an outer variable can’t be passed as `ref` from a `par` body: log.br ```brine fn record(ref log: [str], msg: str): log.push(msg) fn main(): var log: [str] = [] par for i in 0..4: record(ref log, f"step {i}") print(log) ``` ```text error[E0501]: cannot pass as `ref` `log` inside a `par` body --> log.br:7:20 | 7 | record(ref log, f"step {i}") | ^^^ would be a data race | = note: `log` is shared by all parallel iterations = help: a `par for` may only write `log[i]` (its own element); to combine values use `par sum(...)`, `par [...]`, or a sequential loop = run `brine explain E0501` for more about this error error: could not compile due to 1 error ``` Return the value instead and let `par [...]` collect it: ```brine fn main(): let log = par [f"step {i}" for i in 0..4] print(log) ``` Output ```text ["step 0", "step 1", "step 2", "step 3"] ``` ## Changing a map [Section titled “Changing a map”](#changing-a-map) Maps can be read in a `par` body but not changed, not even at the loop index: squares.br ```brine fn main(): var squares: {int: int} = {} par for i in 0..5: squares[i] = i * i print(squares) ``` ```text error[E0501]: cannot assign to `squares` inside a `par` body --> squares.br:4:9 | 4 | squares[i] = i * i | ^^^^^^^^^^ would be a data race | = note: `squares` is shared by all parallel iterations = help: maps can't be changed inside `par`; compute the values with `par [...]`, then fill the map in a sequential loop = run `brine explain E0501` for more about this error error: could not compile due to 1 error ``` ```brine fn main(): let vals = par [i * i for i in 0..5] var squares: {int: int} = {} for i in 0..5: squares[i] = vals[i] print(squares) ``` Output ```text {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} ``` ## Effects that depend on order [Section titled “Effects that depend on order”](#effects-that-depend-on-order) Writing files and starting processes change the world outside the program, and the order of those changes would depend on timing. They’re [E0503](/reference/errors/#e0503), as are `exit`, `world` communication, and the `os` functions that change things (`os.spawn`, `os.wait`, `os.poll`, `os.kill`, `os.make_dir`, `os.remove_file`, `os.rename`, `os.sleep`, `os.enable_ansi`): files.br ```brine fn main(): par for i in 0..4: let r = write_file(f"part{i}.txt", f"part {i}\n") ``` ```text error[E0503]: `write_file` can't be called inside a `par` body --> files.br:3:17 | 3 | let r = write_file(f"part{i}.txt", f"part {i}\n") | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not allowed here | = help: its effects would happen in an unpredictable order; do it before or after the `par` = run `brine explain E0503` for more about this error error: could not compile due to 1 error ``` Build the contents in parallel, then write them in order: ```brine fn main(): let texts = par [f"part {i}\n" for i in 0..4] for i in 0..4: match write_file(f"part{i}.txt", texts[i]): case Ok(_): pass case Err(e): eprint(e) print("wrote", len(texts), "files") ``` Output ```text wrote 4 files ``` Reading is allowed: `read_file`, `file_exists`, `getenv` and `clock` can be called in a `par` body. So can `print` and `eprint`. Each call writes its whole line at once, but the lines of different iterations come out in whatever order the iterations run, so don’t rely on that order. ## Leaving the loop early [Section titled “Leaving the loop early”](#leaving-the-loop-early) first.br ```brine fn main(): let xs = [3, 8, 1, 9] par for i in 0..len(xs): if xs[i] > 5: break ``` ```text error[E0505]: `break` is not allowed inside a `par for` --> first.br:5:13 | 5 | break | ^^^^^ parallel iterations can't stop each other | = help: use `continue` to skip an iteration, or a sequential `for` loop = run `brine explain E0505` for more about this error error: could not compile due to 1 error ``` `return` is rejected the same way, and `?` (which may return) is [E0342](/reference/errors/#e0342). To find the first match, ask with a reduction: ```brine fn main(): let xs = [3, 8, 1, 9, 4] if par any(x > 5 for x in xs): let first = par min(i for i in 0..len(xs) if xs[i] > 5) print(first) ``` Output ```text 1 ``` ## Restructuring patterns [Section titled “Restructuring patterns”](#restructuring-patterns) | Sequential habit | Race-free version | | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `total += f(x)` in a loop | `par sum(f(x) for x in xs)` | | counting matches | `par sum(1 for x in xs if p(x))` | | tracking the best so far | `par max(...)`, `par min(...)`; for the index, `par min(i for i in 0..n if xs[i] == best)` | | `found.push(x)` when `p(x)` | `let keep = par [p(x) for x in xs]`, then `[xs[i] for i in 0..n if keep[i]]` | | updating an array in place from its neighbours | copy first (`let old = xs`), read `old`, write `xs[i]` | | filling a map | `par [...]` for the values, then a sequential loop for the map | | a histogram | `par for b in 0..bins:` with `hist[b] = sum(1 for x in xs if bucket(x) == b)`, or one `par sum` per bin | | writing results to files | `par [...]` for the contents, then write sequentially | | `break` on the first match | `par any(...)`, then `par min(i ... if ...)` | ## Rules [Section titled “Rules”](#rules) * In a `par` body, outer variables are read-only; `par for i in` a range may write `out[i]` with exactly the loop variable ([E0501](/reference/errors/#e0501)). * An array written at `out[i]` can’t be used any other way in that loop ([E0502](/reference/errors/#e0502)). * No `write_file`, `run`, `exit`, changing `os` calls or `world` in `par` ([E0503](/reference/errors/#e0503)); `print` and reads are allowed. * No `break` or `return` ([E0505](/reference/errors/#e0505)); `continue` is fine. * `par for` has no invariants ([E0507](/reference/errors/#e0507)) and `par [...]` has no `if` filter ([E0508](/reference/errors/#e0508)). See also: [parallelism overview](/parallelism/overview/), [deterministic reductions](/parallelism/deterministic-reductions/), [references](/language/functions/). # Multiple processes: world > The built-in world module for MPI-style programs across processes and machines, with every call and its signature, messageable types, brine run --ranks, multi-machine setup, the TCP transport, and combining world with par. The built-in `world` module runs one program as several cooperating processes (*ranks*), on one machine or many, in the style of MPI. Every `world` call is collective: all ranks make it, in the same order, and the compiler checks that they do, so a Brine program can’t deadlock waiting for a message. Every program is also a valid one-rank program, so the same code runs with or without `--ranks`. ## A first program [Section titled “A first program”](#a-first-program) world.br ```brine fn main(): let rank = world.rank() let size = world.size() # each rank sums its own slice of the numbers var local = 0 for i in 0..1_000_000: if i % size == rank: local += i let total = world.sum(local) # every rank contributes one value; everyone gets the whole list let all = world.gather_all(rank * rank) if rank == 0: print(f"{size} ranks, total = {total}, squares = {all}") # pass a message around the ring: each rank sends to rank + 1 var token = "token" for step in 0..size: token = world.shift(token + f"->{rank}", 1) if rank == 0: print(f"after one lap: {token}") # all-to-all: element j of the array goes to rank j let outbox = [[rank, j] for j in 0..size] let inbox = world.exchange(outbox) if rank == size - 1: print(f"rank {rank} received {inbox}") ``` ```sh brine run world.br --ranks 4 ``` Output ```text 4 ranks, total = 499999500000, squares = [0, 1, 4, 9] after one lap: token->0->1->2->3 rank 3 received [[0, 3], [1, 3], [2, 3], [3, 3]] ``` Without `--ranks`, the same program runs as one rank: brine run world.br ```text 1 ranks, total = 499999500000, squares = [0] after one lap: token->0 rank 0 received [[0, 0]] ``` ## The API [Section titled “The API”](#the-api) Every call except `rank()` and `size()` communicates, and every rank must make it. `T` is a [messageable type](#messageable-types). | Call | Returns | What it does | Same on every rank? | | ---------------------------------- | --------------- | ------------------------------------------------------------------------------------------ | ------------------- | | `world.rank()` | `int` | this process’s rank, `0 .. size() - 1` | no | | `world.size()` | `int` | the number of ranks (`1` without `--ranks`) | yes | | `world.barrier()` | nothing | waits until every rank has reached it | | | `world.broadcast(root: int, v: T)` | `T` | every rank gets rank `root`’s `v` | yes | | `world.sum(v)` | the type of `v` | the sum of every rank’s `v` (`int` or `float`) | yes | | `world.min(v)` | the type of `v` | the smallest `v` (`int` or `float`) | yes | | `world.max(v)` | the type of `v` | the largest `v` (`int` or `float`) | yes | | `world.gather_all(v: T)` | `[T]` | every rank’s `v`, in rank order | yes | | `world.shift(v: T, by: int)` | `T` | sends `v` to rank `rank + by` and returns the value from rank `rank - by`, wrapping around | no | | `world.exchange(xs: [T])` | `[T]` | all-to-all: `xs[j]` goes to rank `j`; element `j` of the result came from rank `j` | no | * `world.sum`, `min` and `max` combine on rank 0 in rank order, then send the result to everyone, so they are deterministic for a given number of ranks. Integer `world.sum` stops the program on overflow. * `broadcast`’s root and `shift`’s distance must be the same on every rank ([E0511](/reference/errors/#e0511)). * `exchange` needs exactly one element per rank. Any other length panics: `world.exchange needs one element per rank: got 3 elements, but the world size is 2`. * There is no `send` or `recv` ([E0512](/reference/errors/#e0512)). Point-to-point messages are written with `shift` and `exchange`; see [deadlock freedom](/parallelism/deadlock-freedom/#no-send-or-recv). Every call at once, on three ranks: api.br ```brine fn main(): let rank = world.rank() let size = world.size() world.barrier() let root = world.broadcast(0, f"hello from rank {rank}") let lo = world.min(float(rank) * 1.5) let hi = world.max(rank * 10) let all = world.gather_all([rank, rank * rank]) let flags = world.gather_all(rank % 2 == 0) let left = world.shift(rank, 1) let right = world.shift(rank, -1) let inbox = world.exchange([f"{rank}->{j}" for j in 0..size]) print(f"rank {rank}: root={root} lo={lo} hi={hi} left={left} right={right} inbox={inbox}") if rank == 0: print(all, flags) ``` brine run api.br --ranks 3 ```text rank 2: root=hello from rank 0 lo=0.0 hi=20 left=1 right=0 inbox=["0->2", "1->2", "2->2"] rank 1: root=hello from rank 0 lo=0.0 hi=20 left=0 right=2 inbox=["0->1", "1->1", "2->1"] rank 0: root=hello from rank 0 lo=0.0 hi=20 left=2 right=1 inbox=["0->0", "1->0", "2->0"] [[0, 0], [1, 1], [2, 4]] [true, false, true] ``` Each rank’s lines are in order, but lines from different ranks come out in whatever order the ranks get there (rank 2 printed first in this run). Print results from one rank, usually 0, when the order matters. ## Messageable types [Section titled “Messageable types”](#messageable-types) Values sent between ranks can be `int`, `float`, `bool`, `str`, and arrays of those, nested to any depth (`[int]`, `[[float]]`, `[str]`, …). `world.sum`, `min` and `max` take `int` or `float` only. Anything else is [E0319](/reference/errors/#e0319): point.br ```brine struct P: x: int fn main(): let p = world.broadcast(0, P(x=1)) print(p) ``` ```text error[E0319]: values of type `P` can't be sent between processes yet --> point.br:5:32 | 5 | let p = world.broadcast(0, P(x=1)) | ^^^^^^ not messageable | = note: messageable types: int, float, bool, str, and arrays of those (e.g. [int], [[float]], [str]) = run `brine explain E0319` for more about this error error: could not compile due to 1 error ``` Send a struct as its fields (or an array of them), and a map as its keys and values. ## Running with –ranks [Section titled “Running with –ranks”](#running-with-ranks) ```sh brine run world.br --ranks 4 # 4 processes on this machine brine run world.br --ranks 4 --threads 8 # each with 8 threads for par ``` `brine run --ranks N` builds the program once, reserves `N` free local ports, and starts `N` copies with the [environment variables](#multiple-machines) set. Then: * all ranks print to the same terminal; * only rank 0 reads standard input (the others get an empty one); * `brine run` waits for all of them and exits with the lowest rank’s nonzero exit code (or 0); * if a rank fails, the others are stopped half a second later. Without `--ranks` (or with `--ranks 1`), `world.size()` is `1`, `world.rank()` is `0`, and every call returns at once: `sum(v)` is `v`, `gather_all(v)` is `[v]`, `shift(v, by)` is `v`. ## When a rank fails [Section titled “When a rank fails”](#when-a-rank-fails) A rank that panics or exits early never sends its next message. The ranks waiting for it stop with an error instead of hanging: crash.br ```brine fn main(): let rank = world.rank() let weights = [1.5] let mine = weights[rank] let total = world.sum(mine) print(f"rank {rank}: total = {total}") ``` brine run crash.br --ranks 2 ```text panic (rank 1): index 1 is out of bounds for an array of length 1 --> crash.br:4:16 panic (rank 0): rank 1 exited before sending the expected message --> crash.br:5:17 ``` The exit code is 101, as for any panic. Panic messages name the rank. ## Combining world with par [Section titled “Combining world with par”](#combining-world-with-par) `world` splits the work between processes; `par` splits each process’s share between its cores (and GPU). A common shape: each rank takes a block, reduces it with `par`, and `world.sum` adds up the ranks. harmonic_world.br ```brine fn main(): let rank = world.rank() let size = world.size() let n = 100_000_000 # each rank takes a contiguous block and sums it on all of its cores let lo = n / size * rank let hi = n if rank == size - 1 else n / size * (rank + 1) let local = par sum(1.0 / (float(i) + 1.0) for i in lo..hi) let total = world.sum(local) if rank == 0: print(f"{size} ranks: sum of 1/i for i in 1..={n} = {total}") ``` \--ranks 1, 2 and 4 ```text 1 ranks: sum of 1/i for i in 1..=100000000 = 18.9978964138539 2 ranks: sum of 1/i for i in 1..=100000000 = 18.9978964138539 4 ranks: sum of 1/i for i in 1..=100000000 = 18.997896413853898 ``` Each line is the same on every run and any thread count. The last digit changed at 4 ranks because the blocks changed: each rank’s `par sum` has its own [tree](/parallelism/deterministic-reductions/), and `world.sum` adds the ranks’ results left to right. A float total is reproducible for a given number of ranks, not across different numbers of ranks. If you need the same bits for any rank count, have each rank compute whole, fixed-size blocks and combine them in a fixed order (for example `world.gather_all` of the block sums, then a sum on every rank). Communication can’t happen inside a `par` body ([E0503](/reference/errors/#e0503)): call `world` before or after the construct. On one machine, give each rank its share of the cores with `--threads`, or the ranks’ thread pools compete for them. ## Multiple machines [Section titled “Multiple machines”](#multiple-machines) Each rank is an ordinary process configured by three environment variables. To run across machines, start the built program on each machine with: | Variable | Meaning | | ------------------ | ---------------------------------------------------------- | | `BRINE_RANK` | this process’s rank, `0 .. size - 1` | | `BRINE_WORLD_SIZE` | the number of ranks | | `BRINE_PEERS` | `host:port` for every rank, comma-separated, in rank order | Every rank gets the same `BRINE_PEERS`; entry `r` is where rank `r` listens. For two machines: ```sh brine build world.br --release -o world # on 10.0.0.1: BRINE_RANK=0 BRINE_WORLD_SIZE=2 BRINE_PEERS=10.0.0.1:7101,10.0.0.2:7101 ./world # on 10.0.0.2: BRINE_RANK=1 BRINE_WORLD_SIZE=2 BRINE_PEERS=10.0.0.1:7101,10.0.0.2:7101 ./world ``` The same works on one machine with `127.0.0.1` and different ports, which is what `--ranks` does. Run that way with two ranks, the program above printed rank 0’s lines in one terminal and rank 1’s in the other: rank 0 ```text 2 ranks, total = 499999500000, squares = [0, 1] after one lap: token->0->1 ``` rank 1 ```text rank 1 received [[0, 1], [1, 1]] ``` Without all three variables (or with `BRINE_WORLD_SIZE=1`), a program runs as a single rank. Ranks wait up to 30 seconds for the lower-numbered ranks to start listening, so the machines don’t have to start at the same moment. A `brine launch --hosts` command that does this for you is on the roadmap. ## Transport [Section titled “Transport”](#transport) * **Full-mesh TCP.** Every pair of ranks has one connection: each rank connects to the ranks below it and accepts the ranks above it. * **Sends never block.** Each rank runs a background receiver thread per peer that queues incoming messages, so a send completes even if the receiver is busy. * **FIFO.** Messages between a pair of ranks arrive in the order they were sent. * **Loopback when local.** When every peer is `127.x.x.x` or `localhost`, ranks listen on the loopback interface only: nothing outside the machine can reach them, and firewalls don’t ask. Otherwise they listen on all interfaces. * **No encryption or authentication.** Messages are plain TCP. Run multi-machine jobs on a network you trust. ## Related pages [Section titled “Related pages”](#related-pages) * [Deadlock freedom](/parallelism/deadlock-freedom/): the compile-time check, and what it allows * [Parallelism overview](/parallelism/overview/): `par` inside each rank * [Environment variables](/tooling/environment-variables/) # Contracts > The syntax and meaning of requires, ensures (with result), loop invariants, prove and assert; where each may appear, what it obliges callers and bodies to do, how it works in par, and the errors it reports. A contract is a claim the compiler must prove. `requires` is proven at every call, `ensures` at every `return`, `invariant` around every loop iteration and `prove` where it stands; if one can’t be proven, the program doesn’t compile. `assert` is the exception: it is checked at run time, and the prover treats it as true afterwards. ## At a glance [Section titled “At a glance”](#at-a-glance) | Contract | Where it goes | Proven | At run time | | ------------- | ----------------------------------------------- | --------------------------------------------- | ---------------------- | | `requires e` | first lines of a function body | at every call | nothing | | `ensures e` | first lines of a function body, with `requires` | at every `return` | nothing | | `invariant e` | first lines of a `while` or `for` body | before the first iteration and after each one | nothing | | `prove e` | any statement in a body | where it stands | nothing | | `assert e` | any statement in a body | not required | checked, unless proven | Only `assert` produces code, and only when the prover can’t show it’s always true. The others cost nothing at run time. ## requires [Section titled “requires”](#requires) A precondition. Every caller must prove it, and inside the function it’s a known fact: clamp.br ```brine fn clamp(x: int, lo: int, hi: int) -> int: requires lo <= hi ensures lo <= result <= hi if x < lo: return lo if x > hi: return hi return x fn main(): print(clamp(15, 0, 10), clamp(-3, 0, 10), clamp(4, 0, 10)) ``` Output ```text 10 0 4 ``` A call the prover can’t show satisfies it is [E0601](/reference/errors/#e0601). Adding `print(clamp(1, 10, 0))` to `main` gives: ```text error[E0601]: cannot prove the precondition of `clamp` --> clamp.br:12:11 | 12 | print(clamp(1, 10, 0)) | ^^^^^^^^^^^^^^^ this call might violate `requires lo <= hi` 2 | requires lo <= hi | -------- precondition declared here | = note: with the values used here, this is always false = help: add an `assert` before the call to check it at runtime, or a `requires` to this function so its callers must prove it = run `brine explain E0601` for more about this error error: could not compile due to 1 error ``` Write several `requires` lines, or join claims with `and`; they mean the same. Methods take contracts too, and can talk about fields: range.br ```brine struct Span: lo: int hi: int fn width(self) -> int: requires self.lo <= self.hi ensures result >= 0 return self.hi - self.lo fn main(): let s = Span(lo=2, hi=9) print(s.width()) let bad = Span(lo=5, hi=1) print(bad.width()) ``` ```text error[E0601]: cannot prove the precondition of `width` --> range.br:14:11 | 14 | print(bad.width()) | ^^^^^^^^^^^ this call might violate `requires self.lo <= self.hi` 6 | requires self.lo <= self.hi | ------------------ precondition declared here | = note: with the values used here, this is always false = help: add an `assert` before the call to check it at runtime, or a `requires` to this function so its callers must prove it = run `brine explain E0601` for more about this error error: could not compile due to 1 error ``` A `requires` moves a check from inside the function to its callers, where more is known. A caller that knows the fact (a literal, a guard, its own `requires`) proves it for free; one that doesn’t has to establish it. That is how a `requires` removes runtime checks: see the [worked example](/proofs/overview/#a-worked-example). ## ensures [Section titled “ensures”](#ensures) A postcondition. `result` names the returned value. It’s proven at every `return`, and every caller gets it as a fact. Without an `ensures`, a caller knows nothing about what a function returns, not even something as simple as this: half.br ```brine fn half(n: int) -> int: return n / 2 fn main(): let xs = [1, 2, 3, 4] print(xs[half(len(xs))]) ``` ```sh brine check half.br --report ``` ```text half.br:6:11: index bounds check `xs[half(len(xs))]` Checked half in 0.00s — 1 runtime check proven unnecessary, 1 remain ``` The prover never looks inside a called function, only at its contract. Say what the result is, and the index is proven: half.br ```brine fn half(n: int) -> int: requires n >= 0 ensures 0 <= result <= n ensures n == 0 or result < n return n / 2 fn main(): let xs = [1, 2, 3, 4] print(xs[half(len(xs))]) ``` ```text Checked half in 0.00s — 2 runtime checks proven unnecessary, 0 remain ``` Because each function is proven against its callees’ contracts and not their bodies, changing a function’s body can never break a proof in another function, as long as its contract still holds. An `ensures` that doesn’t hold on some path is [E0602](/reference/errors/#e0602). Often it has found a bug: dist.br ```brine fn distance(a: int, b: int) -> int: ensures result >= 0 if a > b: return a - b return a - b fn main(): print(distance(3, 5)) ``` ```text error[E0602]: cannot prove the postcondition of `distance` --> dist.br:5:5 | 5 | return a - b | ^^^^^^^^^^^^ this might return without `result >= 0` being true 2 | ensures result >= 0 | ----------- postcondition declared here | = note: known facts here: b >= a = note: to prove: a >= b = help: strengthen the function's `requires`, add a loop `invariant`, or check the claim with `assert` before returning = run `brine explain E0602` for more about this error error: could not compile due to 1 error ``` On the second path `b >= a`, so the function should return `b - a`. `ensures` works on functions that return nothing, too. With a `ref` parameter, it describes the variable after the call: fill.br ```brine fn append_zero(ref xs: [int]): ensures len(xs) >= 1 xs.push(0) fn main(): var xs: [int] = [] append_zero(ref xs) print(xs[0]) ``` Output ```text 0 ``` ## invariant [Section titled “invariant”](#invariant) A loop invariant is a claim that holds before every iteration. The prover checks it twice: once when the loop starts ([E0603](/reference/errors/#e0603) if it can’t), and once at the end of an iteration, assuming it held at the start ([E0604](/reference/errors/#e0604) if it can’t). You need one because the prover can’t run a loop. At the top of a loop it forgets every variable the loop assigns, and the invariant is all it keeps. This binary search has no invariant: search.br ```brine fn index_of(xs: [int], target: int) -> int: ensures -1 <= result < len(xs) var lo = 0 var hi = len(xs) while lo < hi: let mid = lo + (hi - lo) / 2 if xs[mid] == target: return mid if xs[mid] < target: lo = mid + 1 else: hi = mid return -1 fn main(): print(index_of([1, 3, 5, 7, 9], 7)) ``` ```text search.br:6:24: overflow check `(hi - lo)` search.br:7:12: index bounds check `xs[mid]` Checked search in 0.00s — 4 runtime checks proven unnecessary, 2 remain ``` Inside the loop, `lo` and `hi` could be anything, so `mid` could be out of bounds. (The `ensures` is still proven: `return mid` comes after the checked `xs[mid]`, so `mid` is known to be in range there.) Say what stays true: search.br ```brine fn index_of(xs: [int], target: int) -> int: ensures -1 <= result < len(xs) var lo = 0 var hi = len(xs) while lo < hi: invariant 0 <= lo <= hi <= len(xs) let mid = lo + (hi - lo) / 2 if xs[mid] == target: return mid if xs[mid] < target: lo = mid + 1 else: hi = mid return -1 fn main(): print(index_of([1, 3, 5, 7, 9], 7)) ``` ```text Checked search in 0.00s — 6 runtime checks proven unnecessary, 0 remain ``` After a `while` loop, the prover knows the invariant and that the condition is false. ### Invariants in for loops [Section titled “Invariants in for loops”](#invariants-in-for-loops) In `for i in a..b`, an invariant may mention the loop variable. It must hold with `i = a` when the loop starts, with `i + 1` at the end of each iteration, and after the loop it holds with `i = b`. That’s how a count is tied to the array length: count.br ```brine fn count_below(xs: [int], limit: int) -> int: ensures 0 <= result <= len(xs) var count = 0 for i in 0..len(xs): if xs[i] < limit: count += 1 return count fn main(): print(count_below([5, 1, 9, 2], 4)) ``` ```text error[E0602]: cannot prove the postcondition of `count_below` --> count.br:7:5 | 7 | return count | ^^^^^^^^^^^^ this might return without `0 <= result <= len(xs)` being true 2 | ensures 0 <= result <= len(xs) | ---------------------- postcondition declared here | = note: nothing is known here about the values involved = note: to prove: count >= 0 and len(xs) >= count = help: strengthen the function's `requires`, add a loop `invariant`, or check the claim with `assert` before returning = run `brine explain E0602` for more about this error error: could not compile due to 1 error ``` count.br ```brine fn count_below(xs: [int], limit: int) -> int: ensures 0 <= result <= len(xs) var count = 0 for i in 0..len(xs): invariant 0 <= count <= i if xs[i] < limit: count += 1 return count fn main(): print(count_below([5, 1, 9, 2], 4)) ``` Output ```text 2 ``` At the end of the loop `i = len(xs)`, so `count <= len(xs)`. `for x in xs` loops take invariants too, but have no index to mention; use `for i in 0..len(xs)` when the claim needs one. Stepped ranges (`range(a, b, step)`) and `par for` can’t have invariants ([E0507](/reference/errors/#e0507)); write a stepped loop as a `while` loop when it needs one. ## prove [Section titled “prove”](#prove) `prove e` asks the compiler to show `e` is true at that point. It generates no code. Use it to check your understanding of what the prover knows, or to document a fact for readers: steps.br ```brine fn main(): let xs = [3, 1, 4, 1, 5] let n = len(xs) prove n == 5 var i = 0 while i < n: invariant 0 <= i <= n i += 1 prove i == n ``` A claim it can’t show is [E0605](/reference/errors/#e0605). `k * k >= 0` is true for every integer, but it multiplies two variables, which the prover doesn’t handle: square.br ```brine fn main(): let k = len(args()) prove k * k >= 0 ``` ```text error[E0605]: cannot prove this claim --> square.br:3:11 | 3 | prove k * k >= 0 | ^^^^^^^^^^ the prover could not show this is always true | = note: nothing is known here about the values involved = note: to prove: len(args()) * len(args()) >= 0 = help: add facts it can use (`requires`, `invariant`, or an `assert` before this line), or use `assert` to check it at runtime instead = run `brine explain E0605` for more about this error error: could not compile due to 1 error ``` ## assert [Section titled “assert”](#assert) `assert e` checks `e` when the program runs, in every build, and the prover treats `e` as a fact after it. It’s how you hand the prover something it can’t work out, such as a fact about input. An optional message follows a comma: args.br ```brine fn main(): let a = args() assert len(a) >= 2, f"need 2 arguments, got {len(a)}" print(a[0], a[1]) ``` The assert makes both indexes safe, so their checks are removed: ```text Checked args in 0.00s — 2 runtime checks proven unnecessary, 0 remain ``` Run without arguments, it stops with exit code 101: Output ```text panic: assertion failed: len(a) >= 2 message: need 2 arguments, got 0 --> args.br:3:5 ``` An `assert` the prover can show is always true is removed from the program like any other proven check. One it can’t is never skipped: there is no build that leaves asserts out. Prefer the other contracts when they can do the job. A `requires` makes the caller prove the fact at compile time; an `assert` finds out at run time. Use `assert` for facts that depend on input, or that the prover can’t reason about (products, floats, array contents). ## What a specification may contain [Section titled “What a specification may contain”](#what-a-specification-may-contain) `requires`, `ensures`, `invariant` and `prove` are logical claims. They may use: * integer arithmetic, comparisons (chained too: `0 <= i < len(xs)`), and `and`, `or`, `not`; * `len`, `abs`, `min` and `max`; * variables, parameters, constants, struct fields and indexing (`xs[0] > 0`); * `result`, in `ensures`. Calls to your own functions aren’t allowed ([E0606](/reference/errors/#e0606)), even ones without side effects: spec.br ```brine fn in_bounds(i: int, n: int) -> bool: return 0 <= i and i < n fn get(xs: [int], i: int) -> int: requires in_bounds(i, len(xs)) return xs[i] fn main(): print(get([1, 2, 3], 1)) ``` ```text error[E0606]: this is not allowed in a specification --> spec.br:5:14 | 5 | requires in_bounds(i, len(xs)) | ^^^^^^^^^^^^^^^^^^^^^ not allowed here | = note: `requires`, `ensures`, `invariant` and `prove` may use arithmetic, comparisons, `and`/`or`/`not`, `len`, `abs`, `min`, `max`, fields, indexing, and `result` = run `brine explain E0606` for more about this error error: could not compile due to 1 error ``` Write the condition out: `requires 0 <= i < len(xs)`. Float comparisons are allowed in a specification but can’t be proven, not even with constants, because the prover reasons only about integers and booleans: root.br ```brine fn root(x: float) -> float: requires x >= 0.0 return sqrt(x) fn main(): print(root(2.0)) ``` ```text error[E0601]: cannot prove the precondition of `root` --> root.br:6:11 | 6 | print(root(2.0)) | ^^^^^^^^^ this call might violate `requires x >= 0.0` 2 | requires x >= 0.0 | -------- precondition declared here | = note: nothing is known here about the values involved = note: to prove: ? = help: add an `assert` before the call to check it at runtime, or a `requires` to this function so its callers must prove it = run `brine explain E0601` for more about this error error: could not compile due to 1 error ``` The `?` in `to prove` means the prover has no way to express the claim. Check float conditions with `assert` inside the function instead. ### Placement [Section titled “Placement”](#placement) `requires` and `ensures` must be the first lines of a function body, and `invariant` the first lines of a loop body. Anywhere else is [E0112](/reference/errors/#e0112): place.br ```brine fn f(x: int) -> int: let y = x + 1 requires x > 0 return y fn main(): print(f(1)) ``` ```text error[E0112]: `requires` is only allowed at the start of a function body --> place.br:3:5 | 3 | requires x > 0 | ^^^^^^^^^^^^^^ misplaced contract | = help: move it to the top of the body, before any other statement = run `brine explain E0112` for more about this error error: could not compile due to 1 error ``` `prove` and `assert` are ordinary statements and can go anywhere a statement can, including `test` blocks. ## Contracts and par [Section titled “Contracts and par”](#contracts-and-par) Contracts work the same inside `par` bodies. A function called from a `par for`, `par [...]` or a `par` reduction must have its `requires` proven at that call, and the loop variable’s range is a known fact there. Facts from before the `par` carry in. `assert` and `prove` can be used in a `par` body: dot.br ```brine fn dot(xs: [float], ys: [float]) -> float: requires len(xs) == len(ys) return par sum(xs[i] * ys[i] for i in 0..len(xs)) fn get(xs: [int], i: int) -> int: requires 0 <= i < len(xs) return xs[i] fn main(): print(dot([1.0, 2.0, 3.0], [4.0, 5.0, 6.0])) let xs = [10, 20, 30, 40] let shift = len(args()) var out = [0] * 4 par for i in 0..4: assert i + shift < len(xs) out[i] = get(xs, i + shift) print(out) ``` Output ```text 32.0 [10, 20, 30, 40] ``` The `requires` of `dot` makes `ys[i]` safe in every iteration, and the `assert` proves the `requires` of `get`. A failing `assert` in a `par` body stops the program like anywhere else. With one argument, `shift` is 1 and the last iteration fails: Output ```text 32.0 panic: assertion failed: i + shift < len(xs) --> dot.br:15:9 ``` A check that fails in a construct running on a GPU reruns it on the CPU, which panics with the same message (see [GPUs](/parallelism/gpu/)). `par for` can’t have an `invariant`: invariants describe the state between iterations, and parallel iterations have no order. parinv.br ```brine fn main(): var out = [0] * 8 par for i in 0..8: invariant 0 <= i <= 8 out[i] = i * i print(out) ``` ```text error[E0507]: `par for` loops can't have invariants --> parinv.br:4:19 | 4 | invariant 0 <= i <= 8 | ^^^^^^^^^^^ remove this = run `brine explain E0507` for more about this error error: could not compile due to 1 error ``` ## Rules [Section titled “Rules”](#rules) * `requires` and `ensures` go first in a function body, `invariant` first in a loop body ([E0112](/reference/errors/#e0112)). * `requires` is proven at every call ([E0601](/reference/errors/#e0601)), `ensures` at every `return` ([E0602](/reference/errors/#e0602)), an invariant on loop entry ([E0603](/reference/errors/#e0603)) and after each iteration ([E0604](/reference/errors/#e0604)), `prove` where it stands ([E0605](/reference/errors/#e0605)). * A caller knows a function’s `ensures`, never its body. A loop keeps only its invariant about the variables it assigns. * Specifications use integer arithmetic, comparisons, `and`/`or`/`not`, `len`, `abs`, `min`, `max`, fields, indexing, constants and `result`; no calls ([E0606](/reference/errors/#e0606)). Float claims can’t be proven. * `assert` is checked at run time in every build and is a fact afterwards. * An unproven contract is always an error. There’s no mode that turns it into a runtime check. See also: [proofs overview](/proofs/overview/), [fixing proof failures](/proofs/fixing-proof-failures/), [runtime checks](/proofs/runtime-checks/), [error codes](/reference/errors/). # Fixing proof failures > How to read the facts and goal in an E0601-E0606 error, and the usual fixes (a guard, a requires, an ensures, an assert, an invariant, or a code fix) with real before-and-after examples. A proof failure means the prover couldn’t show a claim from the facts it had at that point. The error lists those facts and the exact goal; the fix is to supply the missing fact where it’s true (a guard, a `requires`, an `ensures`, an `invariant`, or an `assert`), or to fix the bug the error found. Never weaken or delete the contract just to make the error go away. ## Reading a proof failure [Section titled “Reading a proof failure”](#reading-a-proof-failure) pick.br ```brine fn get(xs: [int], i: int) -> int: requires 0 <= i < len(xs) return xs[i] fn pick(xs: [int], k: int) -> int: if k < 5: return get(xs, k) return 0 fn main(): print(pick([10, 20, 30], 1)) ``` ```text error[E0601]: cannot prove the precondition of `get` --> pick.br:7:16 | 7 | return get(xs, k) | ^^^^^^^^^^ this call might violate `requires 0 <= i < len(xs)` 2 | requires 0 <= i < len(xs) | ---------------- precondition declared here | = note: known facts here: 0 >= k - 4 = note: to prove: k >= 0 and len(xs) > k = help: add an `assert` before the call to check it at runtime, or a `requires` to this function so its callers must prove it = run `brine explain E0601` for more about this error error: could not compile due to 1 error ``` Read it from the top: 1. **The code and the place.** `E0601` at `pick.br:7:16`: the call to `get`. 2. **The claim.** The `-` underline points at the contract that had to hold, here `requires 0 <= i < len(xs)` on line 2. 3. **`known facts here:`** the facts the prover had about the values in the goal. `0 >= k - 4` is `k < 5` from the `if`, rewritten (see below). 4. **`to prove:`** the goal, with the call’s arguments put in for the parameters: `i` became `k`. The difference between the facts and the goal is what’s missing. Here it’s `k >= 0` and `k < len(xs)`. 5. **`help:`** the general ways out. Facts and goals are printed in a normal form, with every term moved to one side and integer bounds tightened, so they may not look like your source: | Printed | Means | | ------------------------- | ----------------------------------------------------------------------------- | | `0 >= k - 4` | `k <= 4`, that is `k < 5` | | `len(xs) > k` | `k < len(xs)` | | `hi >= lo + 2` | `lo + 2 <= hi` | | `a, b` | two separate facts | | `len(args())`, `[...][0]` | a value without a name: the length of a call’s result, an element of an array | | `?` | a value the prover can’t express, such as a float comparison | Two other notes replace the list of facts: * `nothing is known here about the values involved`: the prover had no facts about these variables. Common causes: a parameter with no `requires`, a call result with no `ensures`, or a variable a loop changed. * `with the values used here, this is always false`: the claim can never hold at this point, for example `clamp(1, 10, 0)` with `requires lo <= hi`. This is a bug in the calling code. ## Choosing a fix [Section titled “Choosing a fix”](#choosing-a-fix) | Situation | Fix | | ------------------------------------------------------- | ------------------------ | | the claim is false on some path | fix the code | | the caller should never be in this state | `requires` on the caller | | the state is possible and has a sensible answer | an `if` guard | | the fact depends on input, or the prover can’t show it | `assert` | | a value comes from a call | `ensures` on the callee | | a value was changed by a loop | `invariant` on the loop | | the fact needs a product, a variable divisor or a float | `assert`, or restructure | All three fixes for `pick` above compile. Which is right depends on what `pick` should do when `k` is out of range. ### Guard the code [Section titled “Guard the code”](#guard-the-code) If an out-of-range `k` is a normal case, say what to do with it. Inside the `if`, the condition is a fact: pick.br ```brine fn get(xs: [int], i: int) -> int: requires 0 <= i < len(xs) return xs[i] fn pick(xs: [int], k: int) -> int: if 0 <= k < len(xs): return get(xs, k) return 0 fn main(): print(pick([10, 20, 30], 1)) ``` ### Pass it to the callers [Section titled “Pass it to the callers”](#pass-it-to-the-callers) If calling `pick` with a bad `k` is a mistake, make that the caller’s obligation. The fact is then known inside `pick`, and every caller must prove it (here `main` does, with constants): pick.br ```brine fn get(xs: [int], i: int) -> int: requires 0 <= i < len(xs) return xs[i] fn pick(xs: [int], k: int) -> int: requires 0 <= k < len(xs) return get(xs, k) fn main(): print(pick([10, 20, 30], 1)) ``` This is usually the best fix: the rule is checked at compile time, and it documents what `pick` needs. ### Check it at run time [Section titled “Check it at run time”](#check-it-at-run-time) If the value comes from outside and there’s no sensible fallback, `assert` it. The program stops with a clear message if it’s false, and the prover knows it’s true after the line: pick.br ```brine fn get(xs: [int], i: int) -> int: requires 0 <= i < len(xs) return xs[i] fn pick(xs: [int], k: int) -> int: assert 0 <= k < len(xs) return get(xs, k) fn main(): print(pick([10, 20, 30], 1)) ``` An `assert` is a runtime check you wrote on purpose, in one place, instead of one the compiler would otherwise need to insert. ## The goal is the missing requires [Section titled “The goal is the missing requires”](#the-goal-is-the-missing-requires) When `to prove:` names only parameters, the function needs a `requires`. This `clamp` has its precondition missing: clamp.br ```brine fn clamp(x: int, lo: int, hi: int) -> int: ensures lo <= result <= hi if x < lo: return lo if x > hi: return hi return x fn main(): print(clamp(5, 0, 10)) ``` ```text error[E0602]: cannot prove the postcondition of `clamp` --> clamp.br:4:9 | 4 | return lo | ^^^^^^^^^ this might return without `lo <= result <= hi` being true 2 | ensures lo <= result <= hi | ------------------ postcondition declared here | = note: known facts here: lo > x = note: to prove: hi >= lo = help: strengthen the function's `requires`, add a loop `invariant`, or check the claim with `assert` before returning = run `brine explain E0602` for more about this error error[E0602]: cannot prove the postcondition of `clamp` --> clamp.br:6:9 | 6 | return hi | ^^^^^^^^^ this might return without `lo <= result <= hi` being true 2 | ensures lo <= result <= hi | ------------------ postcondition declared here | = note: known facts here: x >= lo, x > hi = note: to prove: hi >= lo = help: strengthen the function's `requires`, add a loop `invariant`, or check the claim with `assert` before returning = run `brine explain E0602` for more about this error error: could not compile due to 2 errors ``` E0602 is reported once for each `return` it can’t prove. Both goals are `hi >= lo`, which no code in `clamp` can make true; add `requires lo <= hi`. When the facts contradict the goal instead (the facts say `b >= a` and the goal is `a >= b`), the code is wrong on that path. See the `distance` example in [contracts](/proofs/contracts/#ensures). ## A value from a call [Section titled “A value from a call”](#a-value-from-a-call) The prover doesn’t look inside called functions. A result is unknown except for the callee’s `ensures`. Here `xs[k]` keeps its bounds check, because nothing says what `wrap` returns: wrap.br ```brine fn wrap(i: int, n: int) -> int: requires n > 0 return i % n fn main(): let xs = [10, 20, 30] let k = wrap(7, len(xs)) print(xs[k]) ``` ```text wrap.br:8:11: index bounds check `xs[k]` Checked wrap in 0.00s — 1 runtime check proven unnecessary, 1 remain ``` Add the `ensures`. Now the prover has to show it inside `wrap`, and it can’t, because `%` by a variable is outside its arithmetic: wrap.br ```brine fn wrap(i: int, n: int) -> int: requires n > 0 ensures 0 <= result < n return i % n fn main(): let xs = [10, 20, 30] let k = wrap(7, len(xs)) print(xs[k]) ``` ```text error[E0602]: cannot prove the postcondition of `wrap` --> wrap.br:4:5 | 4 | return i % n | ^^^^^^^^^^^^ this might return without `0 <= result < n` being true 3 | ensures 0 <= result < n | --------------- postcondition declared here | = note: known facts here: n > 0, n != 0 = note: to prove: i % n >= 0 and n > i % n = help: strengthen the function's `requires`, add a loop `invariant`, or check the claim with `assert` before returning = run `brine explain E0602` for more about this error error: could not compile due to 1 error ``` The claim is true (Brine’s `%` takes the sign of the right side), so `assert` it. The `assert` runs once per call, and in exchange every caller knows the result is a valid index: wrap.br ```brine fn wrap(i: int, n: int) -> int: requires n > 0 ensures 0 <= result < n let r = i % n assert 0 <= r < n return r fn main(): let xs = [10, 20, 30] let k = wrap(7, len(xs)) print(xs[k]) ``` ```text Checked wrap in 0.00s — 2 runtime checks proven unnecessary, 0 remain ``` ## A value changed by a loop [Section titled “A value changed by a loop”](#a-value-changed-by-a-loop) At the top of a loop, the prover forgets everything about the variables the loop assigns, and it keeps only the loop’s invariants. After the loop, the same is true. `xs` starts with one element and the loop only adds more, but after the loop that’s forgotten: first.br ```brine fn first(xs: [int]) -> int: requires len(xs) > 0 return xs[0] fn main(): var xs = [0] for a in args(): xs.push(int(a)) print(first(xs)) ``` ```text error[E0601]: cannot prove the precondition of `first` --> first.br:9:11 | 9 | print(first(xs)) | ^^^^^^^^^ this call might violate `requires len(xs) > 0` 2 | requires len(xs) > 0 | ----------- precondition declared here | = note: nothing is known here about the values involved = note: to prove: len(xs) > 0 = help: add an `assert` before the call to check it at runtime, or a `requires` to this function so its callers must prove it = run `brine explain E0601` for more about this error error: could not compile due to 1 error ``` State what the loop keeps true: first.br ```brine fn first(xs: [int]) -> int: requires len(xs) > 0 return xs[0] fn main(): var xs = [0] for a in args(): invariant len(xs) >= 1 xs.push(int(a)) print(first(xs)) ``` The other option is to check after the loop, which suits values that really can be anything, like the arguments in the [overview example](/proofs/overview/#a-worked-example). ## E0603: invariant false on entry [Section titled “E0603: invariant false on entry”](#e0603-invariant-false-on-entry) An invariant must hold before the first iteration. For `for i in a..b`, it’s checked with `i = a`: count.br ```brine fn count_below(xs: [int], limit: int) -> int: ensures 0 <= result <= len(xs) var count = 0 for i in 0..len(xs): invariant 0 <= count < i if xs[i] < limit: count += 1 return count fn main(): print(count_below([5, 1, 9, 2], 4)) ``` ```text error[E0603]: loop invariant might not hold when the loop starts --> count.br:5:19 | 5 | invariant 0 <= count < i | ^^^^^^^^^^^^^^ might be false before the first iteration | = note: with the values used here, this is always false = help: establish it before the loop, e.g. by initializing variables so the claim holds = run `brine explain E0603` for more about this error error: could not compile due to 1 error ``` With `count = 0` and `i = 0`, `count < i` is false. Invariants are usually `<=` where the loop variable is involved: `0 <= count <= i`. ## E0604: invariant not preserved [Section titled “E0604: invariant not preserved”](#e0604-invariant-not-preserved) The prover assumes the invariant at the start of an iteration and must show it again at the end. This in-place reverse has an invariant that is false after the last swap, and also before the first one when the array is empty: reverse.br ```brine fn reverse(ref xs: [int]): var lo = 0 var hi = len(xs) - 1 while lo < hi: invariant 0 <= lo <= hi < len(xs) let t = xs[lo] xs[lo] = xs[hi] xs[hi] = t lo += 1 hi -= 1 fn main(): var xs = [1, 2, 3, 4, 5] reverse(ref xs) print(xs) ``` ```text error[E0603]: loop invariant might not hold when the loop starts --> reverse.br:5:19 | 5 | invariant 0 <= lo <= hi < len(xs) | ^^^^^^^^^^^^^^^^^^^^^^^ might be false before the first iteration | = note: nothing is known here about the values involved = note: to prove: len(xs) > 0 = help: establish it before the loop, e.g. by initializing variables so the claim holds = run `brine explain E0603` for more about this error error[E0604]: loop invariant might not be preserved by the loop body --> reverse.br:5:19 | 5 | invariant 0 <= lo <= hi < len(xs) | ^^^^^^^^^^^^^^^^^^^^^^^ might be false after an iteration 10 | hi -= 1 | ------- at the end of this iteration | = note: known facts here: lo >= 0 and hi >= lo and len(xs) > hi, hi > lo, lo >= 0 and len(xs) > lo, hi >= 0 and len(xs) > hi = note: to prove: lo >= -1 and hi >= lo + 2 and len(xs) >= hi = help: make sure every path through the loop body re-establishes the invariant = run `brine explain E0604` for more about this error error: could not compile due to 2 errors ``` In E0604, `lo` and `hi` in the facts and the goal are their values at the *start* of the iteration. The goal is the invariant with the end-of-iteration values put in: `lo + 1 <= hi - 1` became `hi >= lo + 2`. The facts only give `hi > lo`, so when `hi == lo + 1` the pointers cross and `lo <= hi` breaks. The `-` label shows the end of the iteration it checked. The invariant only needs what the body uses: `lo` is a valid index from below, `hi` from above. Dropping the middle comparison fixes both errors: reverse.br ```brine fn reverse(ref xs: [int]): var lo = 0 var hi = len(xs) - 1 while lo < hi: invariant 0 <= lo and hi < len(xs) let t = xs[lo] xs[lo] = xs[hi] xs[hi] = t lo += 1 hi -= 1 fn main(): var xs = [1, 2, 3, 4, 5] reverse(ref xs) print(xs) ``` ```text Checked reverse in 0.00s — 5 runtime checks proven unnecessary, 0 remain ``` The loop condition `lo < hi` supplies the rest. An invariant that’s too weak isn’t an error: with only `invariant 0 <= lo`, this compiles and the bounds checks on `xs[lo]` and `xs[hi]` stay. When E0604 is about a variable the invariant doesn’t bound, the usual fix is to add the bound (`0 <= i <= n`): the prover knows nothing else about variables the loop changes. ## E0605: a claim it can’t prove [Section titled “E0605: a claim it can’t prove”](#e0605-a-claim-it-cant-prove) `prove` has no runtime fallback. If the prover can’t show the claim, either give it the facts (as for the other errors) or use `assert` if a runtime check is acceptable. The most common cause is arithmetic outside what it handles: products of two variables, and `/` or `%` by a variable. The same limit shows up in other codes: area.br ```brine fn area(w: int, h: int) -> int: requires w >= 0 and h >= 0 ensures result >= 0 return w * h fn main(): print(area(3, 4)) ``` ```text error[E0602]: cannot prove the postcondition of `area` --> area.br:4:5 | 4 | return w * h | ^^^^^^^^^^^^ this might return without `result >= 0` being true 3 | ensures result >= 0 | ----------- postcondition declared here | = note: nothing is known here about the values involved = note: to prove: w * h >= 0 = help: strengthen the function's `requires`, add a loop `invariant`, or check the claim with `assert` before returning = run `brine explain E0602` for more about this error error: could not compile due to 1 error ``` area.br ```brine fn area(w: int, h: int) -> int: requires w >= 0 and h >= 0 ensures result >= 0 let a = w * h assert a >= 0 return a fn main(): print(area(3, 4)) ``` Products by a constant (`2 * i`, `3 * n + 1`) are fine. ## E0606: a call in a specification [Section titled “E0606: a call in a specification”](#e0606-a-call-in-a-specification) Specifications can’t call your functions. Write out what the function computes, using arithmetic, comparisons, `len`, `abs`, `min`, `max`, fields and indexing: ```brine fn get(xs: [int], i: int) -> int: requires in_bounds(i, len(xs)) # E0606 return xs[i] ``` ```brine fn get(xs: [int], i: int) -> int: requires 0 <= i < len(xs) return xs[i] fn main(): print(get([1, 2, 3], 1)) ``` If the condition can’t be written that way (it loops over an array, say), check it with `assert` in the function. ## Floats [Section titled “Floats”](#floats) The prover reasons only about integers and booleans. A float claim in a contract can never be proven (`to prove: ?`), so it’s always an error. Check float conditions with `assert`: root.br ```brine fn root(x: float) -> float: assert x >= 0.0, "root of a negative number" return sqrt(x) fn main(): print(root(2.0)) ``` Output ```text 1.4142135623730951 ``` ## Removing a runtime check [Section titled “Removing a runtime check”](#removing-a-runtime-check) A remaining runtime check is not an error, but you remove it the same way: find the missing fact and supply it. Start from `brine check --report`: pairs.br ```brine fn pair_sums(xs: [int]) -> [int]: var out: [int] = [] var i = 0 while i + 1 < len(xs): out.push(xs[i] + xs[i + 1]) i += 2 return out fn main(): print(pair_sums([1, 2, 3, 4, 5, 6])) ``` ```text pairs.br:4:11: overflow check `i + 1` pairs.br:5:18: index bounds check `xs[i]` pairs.br:5:18: overflow check `xs[i] + xs[i + 1]` Checked pairs in 0.00s — 2 runtime checks proven unnecessary, 3 remain ``` `xs[i + 1]` is already proven by the loop condition. `xs[i]` isn’t, because the loop changes `i` and nothing says it stays non-negative. Add that as an invariant: ```brine while i + 1 < len(xs): invariant i >= 0 ``` ```text pairs.br:4:11: overflow check `i + 1` pairs.br:6:18: overflow check `xs[i] + xs[i + 1]` Checked pairs in 0.00s — 3 runtime checks proven unnecessary, 2 remain ``` `i + 1` in the condition could overflow if `i` were the largest integer. The prover doesn’t bound `len(xs)`, so it can’t rule that out. Subtract instead, since `len(xs) - 1` can’t overflow: pairs.br ```brine fn pair_sums(xs: [int]) -> [int]: var out: [int] = [] var i = 0 while i < len(xs) - 1: invariant i >= 0 out.push(xs[i] + xs[i + 1]) i += 2 return out fn main(): print(pair_sums([1, 2, 3, 4, 5, 6])) ``` ```text pairs.br:6:18: overflow check `xs[i] + xs[i + 1]` Checked pairs in 0.00s — 4 runtime checks proven unnecessary, 1 remain ``` The last check adds two elements of the array. Their values are input, so the check is right to stay. Patterns that help the prover: * Loop over `for i in 0..len(xs)` when you index `xs`. The range gives `0 <= i < len(xs)` for free. (`for i, x in enumerate(xs)` doesn’t: `xs[i]` there keeps its check.) * Compare against `len(xs) - k` rather than `i + k < len(xs)`. * Write `lo + (hi - lo) / 2`, and give both pointers bounds in an invariant. * Use `s = s + x` instead of `s += x` when you want an overflow check proven or reported; compound assignments are always checked and never listed (see [runtime checks](/proofs/runtime-checks/#what-the-report-leaves-out)). * Assigning an element (`xs[j] = v`) makes the prover forget the array’s contents, though not its length. ## A checklist for agents [Section titled “A checklist for agents”](#a-checklist-for-agents) 1. Run `brine check` (or `brine check --json`) and take the first error. 2. Read `to prove:` against `known facts here:`. The missing piece is the difference. 3. If the note says `always false`, or the facts contradict the goal, the code is wrong. Fix the code, not the contract. 4. If the goal names only the current function’s parameters, add a `requires`, then fix the callers it breaks. 5. If the goal names a call’s result, add an `ensures` to that function. 6. If the goal names a variable a loop changes, add or strengthen an `invariant` with its bounds; check E0603 (entry) and E0604 (preserved) separately. 7. If the goal needs a product, a variable divisor, a float or array contents, use an `assert` where the fact is cheapest to check. 8. Never delete or weaken a contract to get past the error, and never replace a `requires` with an `assert` unless the value really comes from input. 9. Re-run `brine check --report` and confirm the checks you meant to remove are gone. See also: [contracts](/proofs/contracts/), [runtime checks](/proofs/runtime-checks/), [error codes](/reference/errors/#e0601), [JSON output](/agents/json-output/). # Proofs overview > What Brine's built-in prover does, the promise that rules can't be broken, how proofs remove runtime checks, and what the prover can and can't show. Brine’s compiler proves things about your program before it runs. Claims you write (`requires`, `ensures`, `invariant`, `prove`) must be proven, or the program doesn’t compile. The language’s own safety rules (array bounds, overflow, division by zero) are proven where possible and checked at run time everywhere else, and nothing turns those checks off. ## The promise [Section titled “The promise”](#the-promise) Every rule in Brine is enforced one of two ways: by a proof at compile time, or by a check at run time that stops the program with a clear message. There is no third way. There is no `unsafe`, no flag that skips checks, and no build profile without them. | Rule | Who writes it | When the prover can’t show it | | ------------------------------------------------------------------------------------------------------------- | ------------- | ----------------------------------------------------------------------------------- | | `requires`, `ensures`, `invariant`, `prove` | you | compile error ([E0601](/reference/errors/#e0601)-[E0605](/reference/errors/#e0605)) | | array and string bounds, slices, overflow, division by zero, narrowing conversions, `pop()` on an empty array | the language | the check stays and runs every time | | `assert` | you | checked at run time, and a known fact afterwards | A rule that could be switched off could be broken, and then nobody reading the code could trust it. So the compiler’s job is to *prove* checks away; you can’t *switch* them off. If you want a check gone, give the prover the fact it needs. The same reasoning is why an unproven contract is an error and never falls back to a runtime check: a `requires` that sometimes runs and sometimes doesn’t would be a rule that can be broken. When a runtime check is what you want, write `assert`. ## A worked example [Section titled “A worked example”](#a-worked-example) This program has two functions that fail on an empty array: stats.br ```brine fn average(xs: [int]) -> int: var total = 0 for x in xs: total += x return total / len(xs) fn middle(xs: [int]) -> int: return xs[len(xs) / 2] fn main(): let xs = [4, 8, 15, 16, 23, 42] print(average(xs), middle(xs)) ``` It compiles. `brine check` proves what it can and says how many checks are left: ```sh brine check stats.br --report ``` ```text stats.br:5:12: division check `total / len(xs)` stats.br:8:12: index bounds check `xs[len(xs) / 2]` Checked stats in 0.00s — 1 runtime check proven unnecessary, 2 remain ``` Nothing says `xs` isn’t empty, so the division and the index stay checked. The one it removed is `len(xs) / 2`, a division by a constant. If an empty array does reach `average` (here from a program called `empty.br`), the check stops the program: Output ```text panic: division by zero --> empty.br:5:12 ``` To remove both checks, state the rule the functions depend on: stats.br ```brine fn average(xs: [int]) -> int: requires len(xs) > 0 var total = 0 for x in xs: total += x return total / len(xs) fn middle(xs: [int]) -> int: requires len(xs) > 0 return xs[len(xs) / 2] fn main(): let xs = [4, 8, 15, 16, 23, 42] print(average(xs), middle(xs)) ``` ```text Checked stats in 0.00s — 3 runtime checks proven unnecessary, 0 remain ``` Inside the functions, `len(xs) > 0` is now a known fact, so the division can’t be by zero and `len(xs) / 2` is a valid index. (The `total += x` overflow check stays: it depends on the values in the array. `--report` doesn’t list checks in compound assignments; see [runtime checks](/proofs/runtime-checks/#what-the-report-leaves-out).) The obligation moved to the callers. `main` passes a six-element literal, so the call is proven. A caller that can’t show the array is non-empty doesn’t compile: avg.br ```brine fn average(xs: [int]) -> int: requires len(xs) > 0 var total = 0 for x in xs: total += x return total / len(xs) fn main(): var xs: [int] = [] for a in args(): xs.push(int(a)) print(average(xs)) ``` ```text error[E0601]: cannot prove the precondition of `average` --> avg.br:12:11 | 12 | print(average(xs)) | ^^^^^^^^^^^ this call might violate `requires len(xs) > 0` 2 | requires len(xs) > 0 | ----------- precondition declared here | = note: nothing is known here about the values involved = note: to prove: len(xs) > 0 = help: add an `assert` before the call to check it at runtime, or a `requires` to this function so its callers must prove it = run `brine explain E0601` for more about this error error: could not compile due to 1 error ``` The error is right: with no arguments, `xs` is empty. Handle that case, and the call is proven: avg.br ```brine fn average(xs: [int]) -> int: requires len(xs) > 0 var total = 0 for x in xs: total += x return total / len(xs) fn main(): var xs: [int] = [] for a in args(): xs.push(int(a)) if len(xs) == 0: eprint("usage: avg ") return print(average(xs)) ``` ```sh brine run avg.br -- 3 4 8 ``` Output ```text 5 ``` The bug that used to be a crash on empty input is now a compile error until the empty case is handled. ## How proofs remove checks [Section titled “How proofs remove checks”](#how-proofs-remove-checks) The prover goes through each function on its own, path by path, and keeps a list of facts that are true at each point. Facts come from: * `requires` clauses of the function (true on entry); * `if`, `elif`, `while` and `match` guard conditions (true inside the branch, false after it); * `for i in a..b` (inside the loop, `a <= i < b`); * `let` and assignments of integer expressions (`let mid = lo + (hi - lo) / 2`); * array and string lengths: literals (`[1, 2, 3]` has length 3), `[x] * n`, `push` and `pop`, slices; * the `ensures` of every function called; * loop `invariant`s, `assert`s and `prove`s; * every check that ran before (after `xs[i]`, `0 <= i < len(xs)` is known). At each check, the prover asks whether the facts imply that the check passes. If they do, the check is deleted from the generated code; if not, it stays. Either way, the condition is a fact afterwards, because the program can only continue if it held. The removal happens in every build profile (dev, `--release`, `--debug`) and on every backend, so a proof is also a speed-up: a loop like `for i in 0..len(xs): total = total + xs[i]` runs with no bounds check at all. ## What it can prove [Section titled “What it can prove”](#what-it-can-prove) The prover is a decision procedure for linear integer arithmetic with booleans. It handles: * `+` and `-`, multiplication by a constant, and `/` and `%` by a constant (`2 * (n / 2) <= n` and `0 <= n % 8 < 8` are proven for `n >= 0`); * comparisons, including chains (`0 <= i < len(xs)`), and `and`, `or`, `not`; * `len`, `abs`, `min` and `max`; * struct fields (`requires self.lo <= self.hi`); * elements of arrays whose contents it knows (a literal, before it’s changed). It reasons about integers as exact mathematical numbers. That’s sound because overflow is itself checked: a program that keeps running has exact values. ## What it can’t prove [Section titled “What it can’t prove”](#what-it-cant-prove) Some true claims are out of its reach. When the prover can’t show something it says so; it never guesses. It can’t prove: * **Products of variables.** `i * j`, `w * h >= 0`, `k * k >= 0`. * **Division or remainder by a variable.** `n / m <= n` and `n % m < m`. * **Anything about floats.** A contract that compares floats can’t be proven, even `requires x >= 0.0` called with `2.0`. Write those as `assert`. * **Array contents it hasn’t seen.** The elements of a parameter, or of any array after one of its elements is assigned, are unknown values. * **What a call returns,** beyond the callee’s `ensures`. Without an `ensures`, a function’s result is an unknown value. * **What a loop changes,** beyond its `invariant`. After a loop, every variable it assigns is unknown except for what the invariant says. It is *sound*: it never accepts a false claim. It is *incomplete*: when it can’t prove a true claim, you add the missing fact with a `requires`, an `ensures`, an `invariant` or an `assert`, or restructure the code so the fact is visible. [Fixing proof failures](/proofs/fixing-proof-failures/) shows how. ## Next [Section titled “Next”](#next) * [Contracts](/proofs/contracts/): `requires`, `ensures`, `invariant`, `prove` and `assert` in detail. * [Runtime checks](/proofs/runtime-checks/): every check, what it prints, and `brine check --report`. * [Fixing proof failures](/proofs/fixing-proof-failures/): reading E0601-E0606 and the usual fixes. * [Design](/reference/design/): why the rules are what they are. # Runtime checks > Every runtime check Brine inserts (bounds, slices, overflow, division, conversions, pop), what a failing check prints, which checks proofs remove, how to list the rest with brine check --report, and why release builds keep them. Every operation that could go wrong at run time (an index out of range, an integer overflow, a division by zero) is checked, and a failing check stops the program with a message and exit code 101. When the prover can show a check always passes, the check is removed. `brine check --report` lists the ones that remain. No flag or build profile removes a check the prover couldn’t. ## The checks [Section titled “The checks”](#the-checks) | Check | Example | Panic message | Removed when proven | In `--report` as | | ------------------------------- | ------------------------------------------ | ---------------------------------------------------------------- | ------------------- | -------------------- | | index bounds | `xs[i]`, `s[i]` | `index 6 is out of bounds for an array of length 3` | yes | `index bounds check` | | slice bounds | `xs[a:b]`, `s[a:b]` | `slice [1:6] is out of bounds for an array of length 3` | yes | `slice bounds check` | | division by zero | `a / b` | `division by zero` | yes | `division check` | | remainder by zero | `a % b` | `remainder by zero` | yes | `remainder check` | | division overflow | `a / b` with `a` the minimum and `b == -1` | `integer overflow in division` | yes | `division check` | | overflow | `a + b`, `a - b`, `a * b` | `integer overflow in addition` (`subtraction`, `multiplication`) | yes | `overflow check` | | overflow in compound assignment | `s += x`, `s -= x`, `s *= x` | `integer overflow in addition` (…) | no | not listed | | negation and `abs` | `-x`, `abs(x)` with `x` the minimum | `integer overflow in negation`, `integer overflow in abs` | no | not listed | | narrowing conversion | `i32(n)`, `u8(n)` | `integer conversion out of range for i32` | no | not listed | | `pop()` on an empty array | `xs.pop()` | `pop() from an empty array` | no | not listed | A few more operations can panic, but they depend on data the prover doesn’t track, so they’re always checked and never listed: | Operation | Panic message | | ------------------------------------ | ---------------------------- | | a missing map key, `m[k]` | `key "b" is not in the map` | | `unwrap()` on `None` or `Err` | `unwrap() called on None` | | `int(s)` on text that isn’t a number | `invalid integer: "abc"` | | `min` or `max` of an empty sequence | `max() of an empty sequence` | | `assert e` | `assertion failed: e` | [Error handling](/language/error-handling/) covers the alternatives that return an `Option` or `Result` instead (`m.get(k)`, `unwrap_or`, checking with `is_digit`). Floats have no checks: division by zero gives `inf` or `nan`, as IEEE says. ## What a failing check prints [Section titled “What a failing check prints”](#what-a-failing-check-prints) oob.br ```brine fn main(): let xs = [1, 2, 3] let i = len(args()) + 5 print(xs[i]) ``` ```sh brine build oob.br -o oob ./oob echo $? ``` Output ```text panic: index 5 is out of bounds for an array of length 3 --> oob.br:4:11 101 ``` A panic goes to stderr: a blank line, `panic:` with what went wrong, and the place in your source. The program stops at once and exits with code **101** (`brine run` returns the same code). For an `assert` with a message, the message is on its own line: Output ```text panic: assertion failed: len(a) >= 2 message: need 2 arguments, got 0 --> args.br:3:5 ``` The message and place are the same in every build profile, on every backend, and inside `par` bodies. A check that fails in a construct running on a GPU reruns the construct on the CPU, which then panics with the usual message. ## How proofs remove checks [Section titled “How proofs remove checks”](#how-proofs-remove-checks) For each check, the prover asks whether the facts it has at that point imply that the check passes. If they do, the check is removed from the generated code. Facts come from conditions, loop ranges, lengths, contracts and earlier checks; the [overview](/proofs/overview/#how-proofs-remove-checks) lists them. The usual way to remove a check is to state the fact it needs as a `requires`, so callers prove it where more is known. Here nothing is known about the parameters: pick.br ```brine fn pick(xs: [int], k: int) -> int: return xs[k] fn window(xs: [int], lo: int, n: int) -> [int]: return xs[lo:lo + n] fn ratio(a: int, b: int) -> int: return a / b fn main(): let xs = [1, 2, 3, 4] print(pick(xs, 2), window(xs, 1, 2), ratio(7, 2)) ``` ```sh brine check pick.br --report ``` ```text pick.br:2:12: index bounds check `xs[k]` pick.br:5:12: slice bounds check `xs[lo:lo + n]` pick.br:5:18: overflow check `lo + n` pick.br:8:12: division check `a / b` Checked pick in 0.00s — 0 runtime checks proven unnecessary, 4 remain ``` With the ranges stated, all four checks go, and `main` proves the contracts with its constants: pick.br ```brine fn pick(xs: [int], k: int) -> int: requires 0 <= k < len(xs) return xs[k] fn window(xs: [int], lo: int, n: int) -> [int]: requires 0 <= lo and 0 <= n and lo + n <= len(xs) return xs[lo:lo + n] fn ratio(a: int, b: int) -> int: requires b > 0 return a / b fn main(): let xs = [1, 2, 3, 4] print(pick(xs, 2), window(xs, 1, 2), ratio(7, 2)) ``` ```text Checked pick in 0.00s — 4 runtime checks proven unnecessary, 0 remain ``` `requires b > 0` also rules out the one overflowing division (the minimum divided by `-1`), so it removes the whole division check. Other ways to supply a fact: an `if` around the code, a loop `invariant`, an `ensures` on the function that computes the value, or an `assert` (which is a runtime check itself, but one you place, and it can stand in for several). [Fixing proof failures](/proofs/fixing-proof-failures/#removing-a-runtime-check) works through an example. You don’t have to remove every check. A check on data the program reads (an index from a file, a sum of input values) is often exactly right, and it costs a compare and a branch that is almost never taken. Remove checks in hot loops and where a contract also documents what the function expects. ## brine check –report [Section titled “brine check –report”](#brine-check-report) `brine check` proves without building. It prints how many checks were proven unnecessary and how many remain. For the first `pick.br` above, without the `requires`: ```sh brine check pick.br ``` ```text Checked pick in 0.00s — 0 runtime checks proven unnecessary, 4 remain (see --report) ``` `--report` lists each remaining check before the summary, sorted by position: `file:line:column: kind `source\`\`. The kinds are `index bounds check`, `slice bounds check`, `overflow check`, `division check` and `remainder check`. Remaining checks aren’t errors; `brine check` exits with 0. With `--json`, each check is one JSON object, followed by the summary: ```sh brine check pick.br --report --json ``` ```text {"runtime_check":"index bounds check","location":"pick.br:2:12","code":"xs[k]"} {"runtime_check":"slice bounds check","location":"pick.br:5:12","code":"xs[lo:lo + n]"} {"runtime_check":"overflow check","location":"pick.br:5:18","code":"lo + n"} {"runtime_check":"division check","location":"pick.br:8:12","code":"a / b"} {"ok":true,"checks_proven":0,"checks_remaining":4} ``` Without a file, `brine check` checks the current project. See [the brine command](/tooling/cli/) and [JSON output](/agents/json-output/). ## What the report leaves out [Section titled “What the report leaves out”](#what-the-report-leaves-out) `--report` lists the checks the prover tries to remove and couldn’t. Some checks are always kept, and the report doesn’t mention them: * **Overflow in compound assignments.** `s += x`, `s -= x` and `s *= x` are always checked at run time, even when the prover could show they can’t overflow, and they’re not counted. Write `s = s + x` to have the check proven or listed. * **Negation and `abs`** of an integer. * **Narrowing conversions** such as `i32(n)` and `u8(n)`. * **`pop()`** on an array. * **`assert`**, map lookups, `unwrap()`, `int(s)`, and `min`/`max` of an empty sequence. So “0 remain” means no bounds, slice, division or `+ - *` check remains; it doesn’t mean the program can’t panic. This loop reports nothing: total.br ```brine fn main(): let xs = [9223372036854775807, 1] var s = 0 for i in 0..len(xs): s += xs[i] print(s) ``` ```text Checked total in 0.00s — 1 runtime check proven unnecessary, 0 remain ``` but the `+=` is checked, and it overflows: Output ```text panic: integer overflow in addition --> total.br:5:9 ``` With `s = s + xs[i]` the same check is listed: ```text total.br:5:13: overflow check `s + xs[i]` Checked total in 0.00s — 1 runtime check proven unnecessary, 1 remain ``` ## Negative indexes [Section titled “Negative indexes”](#negative-indexes) Brine has no negative indexes. `xs[-1]` compiles, is listed as a remaining check, and panics when it runs: neg.br ```brine fn main(): let xs = [10, 20, 30] print(xs[-1]) ``` ```text neg.br:3:11: index bounds check `xs[-1]` Checked neg in 0.00s — 0 runtime checks proven unnecessary, 1 remain ``` Output ```text panic: index -1 is out of bounds for an array of length 3 --> neg.br:3:11 ``` Write `xs[len(xs) - 1]` for the last element. ## Release builds keep their checks [Section titled “Release builds keep their checks”](#release-builds-keep-their-checks) `--release` changes how the code is optimized, not what it checks. The checks that remain in a dev build remain in a release build, and fail with the same message at the same place. `empty.br` passes an empty array to the `average` function from the [overview](/proofs/overview/#a-worked-example): ```sh brine run empty.br --release ``` Output ```text panic: division by zero --> empty.br:5:12 ``` There is no option to remove them: ```sh brine build empty.br --unchecked ``` ```text error: unknown option `--unchecked` ``` The way to a faster program is to prove checks away, not to switch them off. See [builds and backends](/tooling/builds-and-backends/). ## Summary [Section titled “Summary”](#summary) * Bounds, slices, `+ - *` overflow, division and remainder checks are removed when proven; the rest run every time. * Compound-assignment overflow, negation, `abs`, conversions and `pop()` are always checked and not listed by `--report`. * A failed check prints `panic: ...` and the source location to stderr and exits with 101, in every build. * `brine check --report` lists what remains; `--json` gives one object per check. * To remove a check, give the prover the fact it needs: [contracts](/proofs/contracts/), [fixing proof failures](/proofs/fixing-proof-failures/). # Design and architecture > Why Brine is the way it is (its principles and core rules) and how the compiler, its backends and the runtime are built. Brine is built on one promise: every rule the language states is enforced, either by a proof at compile time or by a runtime check that can never be turned off. The rest follows from that and from five other goals: native speed, parallelism in the syntax, usability for people and coding agents alike, first-class support on every major OS, and a small language. This page explains those choices and how the compiler and runtime implement them. It is adapted from `docs/DESIGN.md` in the Brine repository, the source of truth for why Brine is the way it is. ## Philosophy [Section titled “Philosophy”](#philosophy) 1. **Rules cannot be broken.** Every rule is enforced by a static proof or by a runtime check that can never be turned off: not in release builds, not with a flag. There is no `unsafe`, no `-DNDEBUG`, no `--unchecked`. The compiler’s job is to *prove* checks away; the programmer can’t *switch* them off. 2. **Fast.** Compiled ahead of time to native code. No garbage collector, no VM, no interpreter. Value semantics with copy-on-write keep the language simple without giving up performance. 3. **Natively parallel.** Parallelism is syntax, not a library: `par for`, `par [..]`, `par sum(..)`, `par(a, b)`. Running as many processes on many machines (MPI-style) is the built-in `world` module. Data races and distributed deadlocks are compile errors. 4. **Usable by humans and agents.** Python-like syntax and one obvious way to write things. Every diagnostic has a stable code, a precise span, an explanation and, where possible, a machine-applicable fix. Every command has `--json` output. `brine explain ` and `brine guide` are built in. 5. **Cross-platform.** macOS, Linux and Windows are first-class. Code goes through brine’s own IR to native x86-64 and AArch64 backends (fast dev builds on Windows and Linux) or to portable C11 for whatever C compiler is present (optimized builds, and every other platform). 6. **Clean and easy to understand.** A small language surface. No hidden control flow, no implicit conversions, no aliasing surprises. ## The language at a glance [Section titled “The language at a glance”](#the-language-at-a-glance) glance.br ```brine struct Particle: pos: float vel: float fn step(ref self, dt: float): self.pos += self.vel * dt fn clamp(x: int, lo: int, hi: int) -> int: requires lo <= hi # callers must prove this ensures lo <= result <= hi # the compiler proves this if x < lo: return lo if x > hi: return hi return x fn main(): let n = 1_000_000 let xs = par [float(i) * 0.5 for i in 0..n] # parallel map let total = par sum(x * x for x in xs) # parallel reduction var out = [0.0] * n par for i in 0..n: # race-free parallel loop out[i] = xs[i] + 1.0 var p = Particle(pos=0.0, vel=2.0) p.step(0.5) print(f"total = {total:.3f}, clamp = {clamp(15, 0, 10)}, pos = {p.pos}") ``` Output ```text total = 83333208333375008.000, clamp = 10, pos = 1.0 ``` ## Core rules [Section titled “Core rules”](#core-rules) | Area | Rule | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Bindings | `let` is immutable, `var` is mutable. No shadowing inside a function. | | Types | Static, inferred for locals, explicit on function signatures. No implicit numeric conversion (literals adapt to context). | | Integers | `int` is 64-bit signed; `i8`..`i128`, `u8`..`u128`. Overflow **traps** (checked arithmetic). Narrowing conversions are checked. 128-bit types need a 64-bit target, and their overflow checks are never proved away (the prover’s own arithmetic is 128-bit). | | Floats | `float` is f64; `f32`. IEEE semantics. | | Values | Everything has **value semantics**: `let b = a` copies, and arguments are passed by value. Arrays and strings are copy-on-write and reference counted, so copies are O(1) until one is changed. | | References | Explicit at both ends: `fn fill(ref xs: [int])` is called as `fill(ref xs)`; methods that change their receiver take `ref self`. `let r = ref x` makes `r` a second name for `x`. References are *second-class*: they can’t be stored in structs or arrays or returned, so they never dangle, and the compiler always knows what they point to. A `ref` argument can’t alias another argument. `mut x: T` on a parameter is a local copy the function may change. | | Maps | `{K: V}` hash maps that remember insertion order. Values, like arrays (copy-on-write). Reading a missing key panics; `m.get(k)` returns `Option[V]`. Keys need a total `==` (no floats). `par` bodies may read maps but not change them. | | Strings | Immutable UTF-8. `len`, `s[i]` and slices count bytes (`s[i]` is a one-character `str`); `for c in s` walks characters. Case conversion and character classes follow Unicode (`is_digit` is `0`-`9`). | | Slices | `xs[a:b]` and `s[a:b]` copy; out-of-range bounds panic (or are proven in range). No negative indexes. | | Evaluation order | A statement that changes a variable (a mutating method, a `ref` argument) can’t read that variable anywhere else, so the order arguments are evaluated in can never change the result. | | I/O | `read_file`, `write_file`, `file_exists`, `getenv` and `run` return `Result`, `Option` or `bool`: failures are values, never crashes. Effects that depend on order (`write_file`, `run`) aren’t allowed in `par` bodies. | | Enums | Tagged unions with named fields per variant; values are immutable and may be recursive. `match` must be exhaustive (checked at compile time, with an example of a missed value). | | Memory | Deterministic: atomic reference counting, freed at scope exit. No GC pauses. | | Generics | `fn f[T](x: T)`, `struct Pair[A, B]`, `enum Option[T]`. Monomorphized: every use with new type arguments is a specialized copy, checked and proven like ordinary code. Type arguments are inferred from arguments and the expected type. | | Errors | `Result[T, E]` and `Option[T]` for expected failures; runtime traps with a source location (`panic`) for bugs. `x?` returns a `None` or `Err` to the caller (no implicit error conversions). `main` may return `Result[(), E]`. | Each rule exists to make one of the goals possible. Value semantics and second-class references mean values never alias, which is what lets the `par` rules be simple and lets the prover reason about a variable without tracking pointers. The evaluation-order rule removes the one place where C-like languages leave the result up to the compiler. And because overflow traps, the prover can treat integers as mathematical integers. ## Proofs [Section titled “Proofs”](#proofs) There are two kinds of obligations: * **Explicit** ones are written by the programmer and **must be proven at compile time**, or the program doesn’t compile: `requires` (proven at every call site), `ensures` (proven at every return; `result` names the return value), `invariant` (proven on loop entry and preserved by every iteration) and `prove e` (an inline claim). * **Implicit** ones come from the language’s safety rules: array bounds, division by zero, integer overflow, narrowing conversions, `pop()` on an empty array. They are **proven when possible and otherwise checked at runtime**, and the check can never be disabled. `brine check --report` lists every check that remains, so you can add the facts that remove them. `assert e` bridges the two: a runtime check (always on) that becomes a known fact for the prover afterwards. When a precondition can’t be proven, the compiler suggests an `assert` (check at runtime) or a `requires` (push the obligation up to the callers). The prover is a built-in decision procedure for linear integer arithmetic with booleans: symbolic execution plus Fourier–Motzkin elimination with integer tightening. It is **sound** (it never accepts a false claim) but **incomplete** (it may fail to prove a true claim; then you add an `invariant`, `requires` or `assert`). See [Contracts](/proofs/contracts/) and [Runtime checks](/proofs/runtime-checks/). ## Parallelism [Section titled “Parallelism”](#parallelism) | Construct | Meaning | | ---------------------------- | ----------------------------------------------------------------- | | `par for i in lo..hi:` | data-parallel loop over a thread pool | | `par [e for i in lo..hi]` | parallel map | | `par sum(e for i in lo..hi)` | parallel reduction (`sum`, `product`, `min`, `max`, `any`, `all`) | | `par(f(a), g(b))` | fork-join of independent expressions; returns a tuple | **Race freedom is a compile-time rule.** Inside a `par` body, variables from the enclosing scope are read-only, with one exception: an outer array may be written *only at the loop index* (`out[i] = ...`), and then not accessed at any other index in the body. Function calls can’t receive outer variables as `ref`. Since values have no observable aliasing, this is enough for data-race freedom. **Reductions are deterministic.** They combine in one fixed order that depends only on the number of elements, never on the number of threads: leaves of 16 consecutive elements, each folded left to right, then a balanced binary tree over the leaves. A float `par sum` gives the same bits on a laptop, a 128-core server and a GPU, and the tree loses less precision than a left-to-right sum. **GPUs** run eligible constructs with no annotation and no change in meaning: the kernels follow the CPU’s floating-point rules exactly, and a check that fails on the device reruns the construct on the CPU, which panics as usual. The compiler decides what *can* run on a GPU; the runtime decides, per launch, whether it *should*. **Many processes.** The `world` module gives every program MPI-style ranks and collectives. Every operation is collective (there is no unmatched `send` or `recv`), and the compiler checks that every rank makes the same calls in the same order by classifying each value as *uniform* (the same on every rank) or *varying*; a communication under a varying condition is [E0510](/reference/errors/#e0510). So a brine program can’t deadlock waiting for a message that never comes. See [Parallelism](/parallelism/overview/), [Race freedom](/parallelism/race-freedom/) and [GPUs](/parallelism/gpu/). ## Compiler pipeline [Section titled “Compiler pipeline”](#compiler-pipeline) ```text source ─► lexer (indentation-aware) ─► parser ─► AST ─► checker (names, types, mutability, race freedom) ─► uniform (deadlock freedom for `world`) ─► verifier (symbolic execution + solver; proves contracts, removes runtime checks) ─► lowering to brine IR (lower.br) ─► x86-64 machine code (x64.br, COFF/ELF objects) dev and debug builds on x86-64 Windows/Linux └► AArch64 machine code (a64.br, ELF objects) dev and debug builds on AArch64 Linux └► C11 (ir_c.br) ─► system C compiler --release, and every other platform ─► link with the brine runtime ─► native executable ``` | Stage | Source | Does | | ---------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------- | | lexer | `compiler/lexer.br` | tokens, with indentation turned into block structure | | parser | `compiler/parser.br` | the syntax tree; syntax errors (`E01xx`) | | checker | `compiler/check.br`, `types.br` | names, types, generics, mutability, references, `par` rules (`E02xx`-`E05xx`) | | uniform | `compiler/uniform.br` | deadlock freedom for `world` (E0510, E0511) | | verifier | `compiler/verify.br`, `solver.br` | proves contracts (`E06xx`) and removes the runtime checks it can prove unnecessary | | lowering | `compiler/lower.br` | the brine IR, with the remaining checks and reference counting made explicit | | backends | `x64.br`, `a64.br`, `ir_c.br`, `ptx.br`, `msl.br` | machine code, C, GPU kernels | | objects and debug info | `obj.br`, `dwarf.br` | COFF and ELF objects, DWARF | | driver | `driver.br`, `main.br` | the pipeline, caching, the C toolchain, the CLI | The compiler uses `par` itself: imported modules are parsed a wave of imports at a time, in parallel, and the prover takes every function on its own. Results are combined in a fixed order, so the output never depends on the thread count. ## Backends [Section titled “Backends”](#backends) The **brine IR** (`compiler/ir.br`) sits between the verifier and code generation: virtual registers with machine kinds (`i8`..`u128`, `f32`, `f64`, `ptr`, `bool`), explicit control flow, explicit checks (only those the prover couldn’t remove), explicit reference counting, stack slots, and data with relocations. Every backend reads it: * **x86-64** (`x64.br`, objects by `obj.br`) writes machine code and a COFF (Windows) or ELF (Linux) object directly, in well under a second for the whole compiler. It’s the default for dev and debug builds there; debug builds add DWARF and skip the optimizations. * **AArch64** (`a64.br`) is the same design for 64-bit ARM Linux, with `.eh_frame` unwind info and the same DWARF. * **C** (`ir_c.br`) writes one statement per IR instruction. gcc and clang at `-O2`/`-O3` make it as fast as hand-written C, so it serves `--release` and every platform without a native backend. It’s also the bootstrap seed. * **PTX** (`ptx.br`) and **Metal** (`msl.br`) turn each `par` construct that can run on a GPU into a kernel, next to the CPU code. `ptx.br` also decides which constructs are eligible, for both. Three rules keep the backends interchangeable: * Nothing above the IR may depend on a backend: no C-only semantics leak into the checker or the verifier. * The runtime ABI (value layouts, reference counting, panics, the `brine_x_*` entry points for helpers that are `static inline` in C) is fixed, so native code and the C runtime link together. * Every backend passes the same end-to-end test suite, and the compiler built by each one builds itself to the same object. A native backend’s main wins are compile speed, independence from a C toolchain, and what C can’t express well (guaranteed tail calls, exact overflow handling, SIMD, debug info). Running faster than C comes mostly from what brine knows that C doesn’t (no aliasing, proven-away checks, race-free `par`), and the IR is where those facts are kept for every backend. See [Builds, backends and performance](/tooling/builds-and-backends/). ## Runtime [Section titled “Runtime”](#runtime) The runtime (`runtime/brine_rt.h` and `runtime/brine_rt.c`) is C, embedded in the compiler binary, and compiled once per runtime version and cached. It has Win32 and POSIX code paths and provides: * reference-counted, copy-on-write strings, arrays and maps, with a small-block allocator; * checked arithmetic and the panic path (every panic goes through `brine_panic_at` with a source location); * the thread pool and the deterministic reductions; * GPU offload: loading the CUDA driver or the Metal framework at run time, timing each construct, and choosing CPU or GPU per launch; * the `world` transport: full-mesh TCP with a background receiver per peer, so sends never block; * files, processes and the `os` module. ## Memory model [Section titled “Memory model”](#memory-model) Every value is reference counted with atomic counts, and generated code releases every temporary and local at scope exit, so memory is freed at a predictable point and there are no collector pauses. A value with a single reference is freed without an atomic instruction. Values can’t form reference cycles (references are second-class, assignment copies, and enum values are immutable), so reference counting needs no cycle collector. `BRINE_LEAKCHECK=1` prints the number of live strings and arrays at exit; brine’s test suite requires 0. Copy-on-write makes value semantics cheap: `let b = a` shares the array until one of them is changed, and a change to an array with a single reference happens in place. ## Debugging and caching [Section titled “Debugging and caching”](#debugging-and-caching) * **Debugging:** native debug builds carry DWARF with lines, brine function names (`Point.norm`) and every local and parameter by its brine name and type. Debug code keeps each value in its stack slot, so `print` in the debugger is always right. `brine debug` loads `runtime/brine_gdb.py`, which prints values the way brine does. Elsewhere, and with `BRINE_BACKEND=c`, debug builds go through C with `#line` directives. See [Debugging](/tooling/debugging/). * **Caching:** objects are named by a hash of what they’re made from. C output is split into one translation unit per core (by a hash of each function’s name, each unit declaring only what it uses), compiled in parallel, so an edit recompiles only the units it touches. An unchanged program skips the link (the `.brine-hash` stamp). * **Timing:** `BRINE_TIMES=1` prints the time of each phase; `BRINE_TIMES=2` also names the functions the prover is slow on. ## Made for agents [Section titled “Made for agents”](#made-for-agents) * Stable error codes; every error has a primary span, a “why” and a “how to fix”. * Machine-applicable fixes in JSON: `fixes: [{line, column, end_line, end_column, replacement}]`. * Deterministic output: the same input gives the same diagnostics in the same order. * No configuration for anything common. `brine guide` and an `AGENTS.md` in every new project. * Syntax chosen to match what models already know (Python), minus the footguns. ## Self-hosting [Section titled “Self-hosting”](#self-hosting) The compiler was first written in Rust, then ported to brine module by module, with every stage cross-checked against the Rust compiler: identical diagnostics, prover reports and generated C on every file, plus mutation fuzzing. The brine compiler builds itself to a fixed point, and the Rust compiler has been retired. Its own generated C, `bootstrap/brinec.c`, is the seed that builds it from scratch with only a C compiler. Each release refreshes the seed, and CI builds brine from it on every platform, so the chain from a plain C compiler to the current brine is always tested. ## Decisions [Section titled “Decisions”](#decisions) Questions the design has settled, and why. Open questions are marked **\[DECISION]** in `docs/DESIGN.md` until they are answered. | Question | Answer | | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Should explicit claims the prover can’t decide fall back to runtime checks in a “development” mode? | **No.** That would be a rule that can be broken. Use `assert` explicitly instead. | | How are distributed deadlocks handled? | **Ruled out at compile time.** Every `world` operation is collective, and the uniformity check makes every rank reach the same calls in the same order. A rank that crashes makes the others stop with an error instead of hanging. | | Should `par` reductions match a sequential loop’s result? | **No: they match each other.** A fixed tree order gives the same bits for any thread count and on the GPU, and is usually more accurate. Programs that need a left-to-right order sum sequentially. | ## Related [Section titled “Related”](#related) * [Roadmap](/reference/roadmap/) * [Builds, backends and performance](/tooling/builds-and-backends/) * [Platforms](/reference/platforms/) * [Quick reference](/reference/quick-reference/) # Platforms > The operating systems, CPUs and GPUs Brine 0.4.0 supports, what each gets (prebuilt binaries, native dev builds, GPU offload), and the C compiler it needs. Brine runs on Linux, macOS and Windows, on x86-64 and 64-bit ARM. Every platform has a prebuilt compiler and needs a C compiler (gcc or clang; MinGW on Windows). Dev builds go straight to machine code on Linux and Windows; macOS builds go through C for now. GPU offload works with NVIDIA GPUs on Linux and Windows and with Apple GPUs on macOS, with nothing to install beyond the driver. Programs can also be compiled to [WebAssembly](#webassembly), for WASI runtimes and browsers. ## Supported platforms [Section titled “Supported platforms”](#supported-platforms) | Platform | Prebuilt | Release archive | Native dev builds | Debug builds | GPU | | ------------------- | -------- | ------------------- | ----------------- | -------------- | ----------------- | | Linux x86-64 | ✅ | `brine-linux-x64` | ✅ | native + DWARF | NVIDIA (CUDA) | | Linux ARM64 | ✅ | `brine-linux-arm64` | ✅ | native + DWARF | NVIDIA (CUDA) | | Windows x86-64 | ✅ | `brine-windows-x64` | ✅ | native + DWARF | NVIDIA (CUDA) | | macOS Apple silicon | ✅ | `brine-macos-arm64` | via C | C with `#line` | Apple GPU (Metal) | | macOS Intel | ✅ | `brine-macos-x64` | via C | C with `#line` | – | * **Prebuilt**: the [installers](/getting-started/installation/) and the [releases page](https://github.com/brinelang/brine/releases) have a binary, with `SHA256SUMS` and build provenance. CI builds and tests Brine on all five on every change, and the compiler builds itself to a fixed point on each. * **Native dev builds**: the compiler writes x86-64 or AArch64 machine code itself, so dev builds take a fraction of a second (the whole compiler builds in under one). “Via C” means dev builds go through the C compiler at `-O2` instead: same results, slower builds. `--release` always goes through C, on every platform. See [Builds, backends and performance](/tooling/builds-and-backends/). * **Debug builds**: on native platforms gdb and lldb see Brine names, lines and types; through C they see `#line` positions and C variable names. See [Debugging](/tooling/debugging/). * **GPU**: see [GPU support](#gpus). Windows on ARM and 32-bit systems have no prebuilt binary. Brine can be [built from source](#other-platforms) wherever there is a 64-bit C11 compiler. ## C compiler [Section titled “C compiler”](#c-compiler) Brine needs a C compiler on every platform, even where dev builds are native: it compiles the runtime (once, then cached), links every program, and compiles every `--release` build. | Platform | Supported | Install | | -------- | ----------------------- | -------------------------------------------------------------- | | Linux | gcc or clang | your package manager (`apt install gcc`, `dnf install gcc`, …) | | macOS | Apple clang | `xcode-select --install` | | Windows | MinGW-w64 gcc, or clang | `winget install LLVM.LLVM` or `scoop install mingw-winlibs` | Brine uses [`BRINE_CC`](/tooling/environment-variables/#brine_cc-and-cc), then `CC`, then the first of `clang`, `gcc` and `cc` on your `PATH`. The C it generates is C11 and needs GCC/Clang extensions (128-bit integers, atomics builtins). MSVC (`cl.exe`) isn’t supported yet. Programs link against `ws2_32` and `shell32` on Windows, and `libm`, `libpthread` and `libdl` elsewhere. Without a C compiler, building fails with [E0702](/reference/errors/#e0702); `brine check` still works. `brine doctor` shows which compiler Brine found and builds a test program with it. ## 128-bit integers [Section titled “128-bit integers”](#128-bit-integers) `i128` and `u128` use the C compiler’s `__int128`, which gcc and clang provide on 64-bit targets, as does wasi-sdk’s clang for WebAssembly. All supported platforms are 64-bit, so they work everywhere Brine ships, and in WebAssembly builds too. Their overflow checks always run: the prover itself computes with 128-bit integers, so it never proves them away. ## GPUs [Section titled “GPUs”](#gpus) `par for`, `par [...]` and `par` reductions can run on a GPU when the runtime expects that to be faster. Nothing is needed at build time: the program carries the GPU code and loads the driver when it first needs it. | GPU | Platforms | How | Notes | | -------------------------------- | ----------------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------- | | NVIDIA | Linux x86-64, Linux ARM64, Windows x86-64 | CUDA driver (`libcuda.so.1`, `nvcuda.dll`), PTX kernels | f64 runs, but GeForce cards run it at a fraction of their f32 speed | | Apple (M1 and later) | macOS Apple silicon | Metal, loaded through the Objective-C runtime | no f64 and no recursion on the device: those constructs stay on the CPU | | AMD, Intel | any | – | the CPU runs everything; Vulkan support is planned | | AMD or Intel GPU in an Intel Mac | macOS Intel | – | Metal is used only on Apple GPUs for now | Without a driver or a device, programs run on the CPU and print the same results. See [GPUs](/parallelism/gpu/) for which constructs qualify and how the runtime decides. ## Multiple processes [Section titled “Multiple processes”](#multiple-processes) `brine run --ranks N` and the [`world` module](/parallelism/world/) work on every platform: ranks talk over TCP, through Winsock on Windows and BSD sockets elsewhere. `brine doctor` checks that a local port can be reserved. ## WebAssembly [Section titled “WebAssembly”](#webassembly) Every platform can also build WebAssembly. This is new on Brine’s main branch and not in a release yet. `brine build --target wasm` makes a module for WASI preview 1 (`wasm32-wasip1`), which `brine run` and `brine test` run with Node.js; `--target web` makes a folder a browser opens. | | WebAssembly | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Build needs | [wasi-sdk](https://github.com/WebAssembly/wasi-sdk/releases), found through [`WASI_SDK_PATH`](/tooling/environment-variables/#wasi_sdk_path) or `/opt/wasi-sdk` | | Run needs | Node.js (Brine’s CI uses Node 24), another WASI runtime in [`BRINE_WASM_RUNNER`](/tooling/environment-variables/#brine_wasm_runner), or a browser | | Code generator | C, in every profile | | Threads | one: `par` runs in order, with the same results | | `world` | one rank | | GPU, processes, sockets | none | | Files | the current folder under `brine run`; none in the browser | `os.name()` returns `"wasi"` and `os.arch()` returns `"wasm32"`. Brine’s CI installs wasi-sdk 34 and Node 24 and runs the WebAssembly tests on Linux, macOS and Windows. See [WebAssembly](/tooling/webassembly/). ## Other platforms [Section titled “Other platforms”](#other-platforms) The compiler is written in Brine, and its own generated C (`bootstrap/brinec.c`) builds it from scratch with only a C compiler: ```sh git clone https://github.com/brinelang/brine && cd brine cc -std=c11 -O2 -fno-strict-aliasing -w -Iruntime bootstrap/brinec.c runtime/brine_rt.c -o brine -lm -lpthread -ldl ./brine build compiler/main.br -o brine2 # brine rebuilding itself ``` On Windows, use `brine.exe` and `-lws2_32 -lshell32` instead of `-lm -lpthread -ldl`. On a 64-bit platform without a native backend, every build then goes through C. The runtime has Win32 and POSIX code paths; other POSIX systems may work but aren’t tested. ## Coming next [Section titled “Coming next”](#coming-next) From the [roadmap](/reference/roadmap/): | Planned | What it brings | | -------------------------------------------------- | ---------------------------------------------------- | | Mach-O objects | native dev and debug builds on macOS (Apple silicon) | | MSVC support in the C backend | building with Visual Studio’s compiler on Windows | | CodeView debug info | debugging in Visual Studio and WinDbg | | A built-in linker and a prebuilt runtime | builds that need no C toolchain at all | | Vulkan compute (SPIR-V kernels) | AMD and Intel GPUs, including those in Intel Macs | | Threads on WebAssembly (wasi-threads, Web Workers) | `par` running in parallel in WebAssembly builds | ## Related [Section titled “Related”](#related) * [Installation](/getting-started/installation/) * [Builds, backends and performance](/tooling/builds-and-backends/) * [WebAssembly](/tooling/webassembly/) * [Environment variables](/tooling/environment-variables/) # Quick reference > The whole Brine language on one page, the same text `brine guide` prints. Brine is a compiled, verified, natively parallel language with Python-like syntax. Blocks use `:` plus 4-space indentation. Comments start with `#`. This page is the whole language on one page. The same text is built into the compiler, so it works offline and always matches the version you have installed: ```sh brine guide ``` This page is the guide of Brine 0.4.0. For more detail on each part, see [values and types](/language/values-and-types/), [parallelism](/parallelism/overview/), [proofs](/proofs/contracts/), [the brine command](/tooling/cli/) and [error codes](/reference/errors/). ## Programs and commands [Section titled “Programs and commands”](#programs-and-commands) ```sh brine new hello && cd hello # create a project (brine.toml, src/main.br, AGENTS.md) brine run # build + run brine run file.br -- args brine check # type-check + prove brine check --report brine check --typed # also require a written type on every variable brine test # run test blocks brine test "name filter" brine fmt # format brine fmt --check brine run --ranks 4 # 4 cooperating processes (world module) brine debug file.br # debug build in gdb/lldb: break file.br:12, step, print x brine explain E0601 # explain any error code brine run --target wasm # WebAssembly, run with Node.js (needs wasi-sdk, $WASI_SDK_PATH) brine build --target web -o d # d/: index.html + brine.js + program.wasm, for a browser ``` Add `--json` to any command for one JSON object per line. WebAssembly runs on one thread (`par` runs in order, same results) and as one `world` rank; in a browser there are no files or processes. ## Values and types [Section titled “Values and types”](#values-and-types) | Type | Examples | | ------------------------------------------------------ | --------------------------------------------------------------- | | `int` (= `i64`), `i8 i16 i32 i128 u8 u16 u32 u64 u128` | `42`, `1_000_000`, `0xff`, `0b1010` | | `float` (= `f64`), `f32` | `3.14`, `1e-9` | | `bool` | `true`, `false` | | `str` | `"hi\n"`, `f"x = {x}, pi = {pi:.3f}"` | | arrays `[T]` | `[1, 2, 3]`, `[0.0] * n`, `[i * i for i in 0..n if i % 2 == 0]` | | maps `{K: V}` | `{"ann": 31, "bob": 27}`, `var m: {str: int} = {}` | | tuples `(A, B)` | `(1, "one")`, `pair.0` | | structs | `Point(x=1, y=2)` or `Point(1, 2)` | * No implicit conversions. Convert explicitly: `float(i)`, `int(x)` (truncates), `i32(n)` (checked), `str(v)`, `int("42")` (parses), `float("2.5")`. * Literals adapt to context: `let b: u8 = 200`, `let f: f32 = 1`, `let big: i128 = 10_000_000_000_000_000_000_000`. Integer literals go up to `u128`’s maximum (2^128 - 1); above `i128`’s maximum only a `u128` holds them. * `i128` and `u128` need a 64-bit target (they’re a GCC/Clang feature). The prover can’t remove their overflow checks; those always run. * Integer overflow, out-of-range conversions, division by zero and out-of-bounds indexing stop the program with a clear message (a *panic*), unless the compiler proved they can’t happen, in which case the check is removed. * Integer `/` rounds down like Python’s `//` (`-7 / 2 == -4`), and `%` takes the sign of the right side (`-7 % 2 == 1`). Use `float(a) / float(b)` for a fractional result. * Values are **values**: `let b = a` copies (cheaply, copy-on-write). Changing `b` never changes `a`. To share a variable on purpose, use `ref` (see Functions). ## Bindings [Section titled “Bindings”](#bindings) ```brine let x = 10 # immutable var total = 0 # mutable total += x let (q, r) = (17 / 5, 17 % 5) # tuple unpacking let (a, (b, c)) = (1, (2, 3)) # nests var names: [str] = [] # annotate when the type can't be inferred let n: i32 = 7 # ...or whenever you like for i: int in 0..3: # loop variables too: `for k: str, v: int in m.items():` total += i const LIMIT = 100 # compile-time constant (top level) ``` Types are optional, Python style. To require them, put `typed = true` under `[check]` in brine.toml (or pass `--typed`): then every `let`, `var` and `for` variable needs a written type (error E0337 tells you which one to add). No shadowing: a name can be declared only once per function. `_` ignores a value (`let (q, _) = ...`, `for _ in 0..n:`); it may repeat and can’t be read. ## Control flow [Section titled “Control flow”](#control-flow) ```brine if x < 0: print("negative") elif x == 0: print("zero") else: print("positive") let sign = "neg" if x < 0 else "non-neg" # conditional expression for i in 0..n: # 0, 1, ..., n-1 (also: 0..=n, range(a, b, step)) pass for x in xs: # iterates over a snapshot of xs pass for i, x in enumerate(xs): # unpack tuples: (0, xs[0]), (1, xs[1]), ... pass for i, (k, v) in enumerate(pairs): # nested tuples too (also in comprehensions) pass for c in "héllo": # each UTF-8 character, as a one-character str pass while k < n: k += 1 # break / continue work as usual ``` Comparisons chain: `0 <= i < n`. Logic is `and`, `or`, `not`. ## Functions [Section titled “Functions”](#functions) ```brine fn area(w: float, h: float) -> float: return w * h fn fill(ref xs: [int], v: int): # `ref` param: changes the caller's variable for i in 0..len(xs): xs[i] = v fn countdown(mut n: int): # `mut` param: a local copy the function may change while n > 0: n -= 1 fn main(): var data = [0] * 10 fill(ref data, 7) # the caller says `ref` too let d = ref data # `d` is another name for `data` d[0] = 1 # changes `data` ``` Arguments are passed by value (cheaply: arrays and strings are copy-on-write). References can’t be stored in structs or arrays or returned, so they never dangle. `main` may return `int` (the exit code). Parameter types are required; local types are inferred. ## Structs and methods [Section titled “Structs and methods”](#structs-and-methods) ```brine struct Particle: pos: float vel: float fn step(ref self, dt: float): # method that modifies its receiver self.pos += self.vel * dt fn energy(self) -> float: return 0.5 * self.vel * self.vel fn at_rest(pos: float) -> Particle: # no `self`: call as Particle.at_rest(1.0) return Particle(pos=pos, vel=0.0) var p = Particle(pos=0.0, vel=2.0) p.step(0.1) print(p, p.energy()) # Particle(pos=0.2, vel=2.0) 2.0 ``` ## Enums and `match` [Section titled “Enums and match”](#enums-and-match) ```brine enum Color: # no data: a plain tag Red Green enum Shape: Circle(radius: float) Rect(w: float, h: float) Empty fn area(self) -> float: # enums can have methods match self: case Shape.Circle(r): return 3.14 * r * r case Shape.Rect(w, h): return w * h case Shape.Empty: return 0.0 enum Expr: # recursive enums are fine Num(value: int) Add(left: Expr, right: Expr) let s = Shape.Rect(w=2.0, h=3.0) # positional or keyword fields print(s, s.area(), s == Shape.Empty) # Shape.Rect(w=2.0, h=3.0) 6.0 false ``` Patterns: `_`, a name (captures), int/str/bool literals, tuples `(a, 0)`, alternatives `1 | 2 | 3`, and variants `Shape.Rect(w, _)` / `Expr.Add(left=l)` (nested to any depth). A guard adds a condition: `case x if x < 0:`. Every `match` must handle every value (error E0320 shows one it misses); end with `case _:` when listing values isn’t possible. Variant data is read with `match`, never with `.field`. ## Generics, `Option` and `Result` [Section titled “Generics, Option and Result”](#generics-option-and-result) ```brine struct Pair[A, B]: first: A second: B fn first[T](xs: [T]) -> Option[T]: # type parameters in brackets if len(xs) == 0: return None return Some(xs[0]) fn parse_bit(s: str) -> Result[int, str]: if s == "0": return Ok(0) if s == "1": return Ok(1) return Err(f"not a bit: {s}") let p = Pair(first=1, second="one") # type arguments are inferred... let q = Pair[int, str](first=2, second="two") # ...or written out let none: Option[int] = None # `None` alone needs a known type match first([3, 4]): case Some(x): print(x) case None: print("empty") print(parse_bit("1").unwrap(), parse_bit("x").unwrap_or(-1)) # 1 -1 ``` `x?` unwraps an `Option` or `Result`: it gives the value inside `Some`/`Ok`, or returns the `None`/`Err` from the current function at once (the function must return `Option[...]`, or `Result[..., E]` with the same `E`): ```brine fn load(path: str) -> Result[int, str]: let text = read_file(path)? # Err(...) is returned to the caller return parse_bit(text.strip()) fn main() -> Result[(), str]: # an Err prints "error: ..." and exits with 1 print(load("bit.txt")?) return Ok(()) ``` `Option[T]` (`Some(value)`, `None`) and `Result[T, E]` (`Ok(value)`, `Err(error)`) are always available. Methods: `is_some()`, `is_none()`, `is_ok()`, `is_err()`, `unwrap()` (panics on `None`/`Err`), `unwrap_or(default)`, `ok()` (Result to Option). Each use of a generic function or type with new type arguments makes a specialized copy, so generic code is as fast as hand-written code. A generic function’s body is checked for each set of type arguments it’s used with: `largest[T]` that compares with `>` works for `int` and `str`, and is an error for a struct. Methods can have type parameters of their own, next to their type’s: ```brine struct Box[T]: v: T fn pair[U](self, u: U) -> (T, U): return (self.v, u) let b = Box(v=1) print(b.pair("x"), b.pair[float](2.5)) # (1, "x") (1, 2.5) ``` ## Arrays [Section titled “Arrays”](#arrays) ```brine var xs = [3, 1, 2] xs.push(5) # append let last = xs.pop() # remove and return the last element (xs.pop(i): at i) xs.insert(0, 9) # put 9 at index 0, shifting the rest right xs.extend([7, 8]) # append every element xs.sort() # numbers and strings; stable xs.reverse() xs.clear() print(len(xs), xs == [3, 1, 2], 3 in xs, xs.index(3)) # index: Option[int] print(xs.contains(3)) # the same as `3 in xs` print(xs[1:3], xs[:2], xs[2:]) # slices are copies; out of range panics ``` There are no negative indexes: write `xs[len(xs) - 1]`. A statement can’t both change a variable and read it elsewhere (`print(xs.pop(), xs)` is an error), so evaluation order never matters. ## Strings [Section titled “Strings”](#strings) Strings are immutable UTF-8. `len(s)`, `s[i]` and slices count **bytes**; `s[i]` is a one-character string (ASCII text is the common case, and `for c in s` / `s.chars()` walk real characters). ```brine let s = "hello, brine" print(s[0], s[7:], s + "!", "ri" in s, s.contains("ri"), f"{s} has {len(s)} bytes") print(s.find("brine")) # Some(7); None if absent print(s.split(", "), "a b c".split()) # ["hello", "brine"], ["a", "b", "c"] print("-".join(["a", "b"]), "ab".repeat(3), s.replace("l", "L")) print(s.starts_with("he"), s.ends_with("!"), s.count("l")) print(" x ".strip(), " x".lstrip(), "x ".rstrip(), s.upper(), s.lower()) print("a\nb".lines(), s.chars(), s.bytes()) # [str], [str], [int] print("42".is_digit(), "ab".is_alpha(), " ".is_space(), "a1".is_alnum()) print(ord("a"), chr(97)) # 97 "a" ``` Case conversion and `is_alpha`/`is_space` follow Unicode, as in Python (`"straße".upper()` is `"STRASSE"`); `is_digit` is `0`-`9`, the digits `int()` reads. `str(x)` converts any printable value; `int("42")` and `float("2.5")` parse (and panic on bad input, so check with `is_digit` first when the text comes from outside). ## Maps [Section titled “Maps”](#maps) ```brine var ages = {"ann": 31, "bob": 27} # {str: int}; remembers insertion order ages["cy"] = 40 # insert or replace ages["ann"] += 1 # missing keys panic: check with `in` first if "bob" in ages: print(ages["bob"]) print(ages.get("zed")) # Option[int]: None print(ages.get("zed", 0)) # 0 (default) ages.remove("bob") # true if it was there for name in ages: # keys, in insertion order (a snapshot: print(name, ages[name]) # the loop body may change the map) print(ages.keys(), ages.values(), ages.items(), len(ages)) ``` Keys can be integers, `bool`, `str`, enums, or arrays/tuples/structs of those (not floats). Maps are values like everything else: `let b = a` copies (lazily). ## Built-ins [Section titled “Built-ins”](#built-ins) `print(a, b, ...)` (`sep=" "` goes between the values and `end="\n"` after them: `print(x, end="")`), `len`, `abs`, `min(a, b)`, `max(a, b)`, `sqrt`, `sin`, `cos`, `tan`, `exp`, `log`, `pow(x, y)`, `floor`, `ceil`, `round`, `clock()` (seconds), `args()` (command-line arguments), `exit(code)`, `panic(msg)`, `eprint(...)` (print to stderr), `ord(c)`, `chr(n)`, `enumerate(xs)`. Files and processes (all failures are values, never crashes): ```brine match read_file("in.txt"): # Result[str, str]; text must be UTF-8 case Ok(text): print(len(text.lines())) case Err(e): eprint(e) # "cannot read in.txt: No such file or directory" write_file("out.txt", "hi\n") # Result[(), str] file_exists("out.txt") # bool getenv("HOME") # Option[str] run(["cc", "-O2", "-o", "app", "app.c"]) # Result[int, str]: Ok(exit code), Err if it can't start ``` `write_file` and `run` can’t be used inside `par` (their effects would happen in an unpredictable order). The `os` module (built in, no import; errors are `Err` values with the OS’s message, e.g. `"The system cannot find the file specified. (os error 2)"`): ```brine os.list_dir(path) # Result[[str], str]: entry names (not paths), sorted, no "." / ".." os.is_dir(path), os.is_file(path) # bool os.make_dir(path) # Result[(), str]: creates missing parents; Ok if it exists os.remove_file(path), os.rename(src, dst) # Result[(), str]; rename replaces dst os.cwd(), os.temp_dir() # str (temp_dir has no trailing separator) os.set_cwd(path) # Result[(), str]: change the working directory os.write_bytes(path, b) # Result[(), str]: write a [u8] as a binary file os.exe_path() # str: the absolute path of the running program os.abs_path(path) # Result[str, str]: canonical; on Windows `\\?\C:\...` os.name(), os.arch() # "windows"/"macos"/"linux"..., "x86_64"/"aarch64"... os.pid(), os.cpu_count() # int (cpu_count >= 1) os.sleep(0.5) # seconds os.is_terminal(2) # is fd 1 (stdout) / 2 (stderr) / 0 a terminal? os.enable_ansi() # Windows: let the console show color codes os.free_ports(n) # Result[[int], str]: n distinct free localhost TCP ports let p = os.spawn(["cc", "x.c"], {"LANG": "C"}, true, false)? # Result[int, str]: a handle # env: added to this process's environment; capture: collect stdout/stderr; # stdin: false gives the child an empty stdin. cmd[0] is searched on PATH. os.wait(p) # int: exit code (101 if killed by a signal) os.poll(p) # Option[int]: exit code if it has exited os.kill(p) # stop it let (out, err) = os.output(p) # captured text once it has exited ("" if not captured) ``` `os.spawn`, `wait`, `poll`, `kill`, `make_dir`, `remove_file`, `rename`, `sleep` and `enable_ansi` can’t be used inside `par`. Results of `os` calls (except `os.name()`/`os.arch()`) count as rank-dependent for `world` deadlock checks. `embed("help.txt")` is a `str` with the file’s contents, read when the program is compiled (path: a string literal, relative to the file containing the call; `\r\n` becomes `\n`). It works in `const`s too: `const HELP = embed("help.txt")`. Reductions over generators: `sum(x * x for x in xs)`, `product`, `min`, `max`, `any(x < 0 for x in xs)`, `all(...)`. `min`/`max` of an empty sequence panic. ## Parallelism (data-race free by construction) [Section titled “Parallelism (data-race free by construction)”](#parallelism-data-race-free-by-construction) ```brine let ys = par [f(x) for x in xs] # parallel map let total = par sum(x * x for x in xs) # parallel reduction var out = [0.0] * n par for i in 0..n: # parallel loop out[i] = xs[i] * 2.0 # only out[i] of outer arrays may be written let (a, b) = par(work(1), work(2)) # fork-join ``` Rules: inside a `par` body, outer variables are read-only, except that `par for i` may write `arr[i]` (exactly the loop variable) of an outer array. Reductions are deterministic: same result bits on any machine and thread count. They fold leaves of 16 consecutive elements left to right, then combine the leaves in a balanced pairwise tree (so `par sum` of floats can differ in the last bits from a sequential `sum`, usually by being more accurate). Integer overflow of any partial result in that order traps. `BRINE_THREADS=n` or `brine run --threads n` sets the thread count. **GPUs.** `par for`, `par [...]` and `par` reductions also run on a GPU (NVIDIA through CUDA, Apple silicon through Metal) when the runtime expects that to be faster (big constructs with much work per byte copied); the results are the same bits, and a failing check panics as on the CPU. On Apple GPUs, f64 code stays on the CPU (they have no f64). Nothing to write or install beyond the driver: the compiler makes a kernel of every body that only uses int/float arithmetic, comparisons, branches, loops, structs, tuples, reads of outer arrays and scalars, `out[i]` writes, `sqrt`, `abs`, `floor`, `ceil`, `round`, `min`, `max`, and calls to functions that do the same. Strings, maps, new arrays, printing, `sin`/`exp`/`log`/`pow` and the like keep a body on the CPU. `BRINE_GPU=off|on|auto` (default `auto`; `on` uses the GPU whenever it can), `BRINE_GPU_LOG=1` prints where each construct ran and why. ## Multiple processes and machines: `world` [Section titled “Multiple processes and machines: world”](#multiple-processes-and-machines-world) ```brine fn main(): let rank = world.rank() # 0 .. world.size()-1 let local = sum(i for i in 0..1000 if i % world.size() == rank) let total = world.sum(local) # every rank gets the total if rank == 0: print(total) let left = world.shift(rank, 1) # send to rank + 1, receive from rank - 1 let inbox = world.exchange([rank] * world.size()) # xs[j] goes to rank j ``` API: `rank()`, `size()`, `barrier()`, `broadcast(root, v)`, `sum(v)`, `min(v)`, `max(v)`, `gather_all(v) -> [T]`, `shift(v, by)`, `exchange(xs) -> [T]`. Message types: `int`, `float`, `bool`, `str`, and arrays of those (`[[float]]`, …). Run with `brine run --ranks N`. Without it, `world.size() == 1`. **No deadlocks, by construction.** Every rank takes part in every `world` call, and the compiler checks that all ranks make the same calls in the same order: a call under a condition that depends on `world.rank()` (or received values, `clock()`, …) is an error (E0510). Results of `sum`/`min`/`max`/`broadcast`/ `gather_all` are the same on every rank, so loops like `while world.max(err) > tol:` are fine. ## Proofs: rules that can’t be broken [Section titled “Proofs: rules that can’t be broken”](#proofs-rules-that-cant-be-broken) ```brine fn clamp(x: int, lo: int, hi: int) -> int: requires lo <= hi # every caller must prove this ensures lo <= result <= hi # proven for every return if x < lo: return lo if x > hi: return hi return x fn index_of(xs: [int], target: int) -> int: ensures -1 <= result < len(xs) var lo = 0 var hi = len(xs) while lo < hi: invariant 0 <= lo <= hi <= len(xs) # holds before every iteration let mid = lo + (hi - lo) / 2 if xs[mid] == target: # bounds check proven: removed return mid if xs[mid] < target: lo = mid + 1 else: hi = mid return -1 ``` * `requires`, `ensures`, `invariant` and `prove e` **must** be proven at compile time; if the prover can’t, it’s an error listing the facts it knew. * `assert e` checks at runtime (always, in every build) and is a known fact afterwards. Use it to hand the prover a fact it can’t derive itself. * Specs may use arithmetic, comparisons, `and`/`or`/`not`, `len`, `abs`, `min`, `max`, fields, indexing, constants and `result`. * In `for i in a..b` loops, invariants may mention `i`: they must hold at the start of each iteration (and with `i = b` after the loop). * The prover handles linear integer arithmetic (`+ - `and multiplication or division by constants). It never accepts a false claim. * `brine check --report` lists the runtime checks that remain. ## Modules [Section titled “Modules”](#modules) ```brine import geometry # loads geometry.br next to the main file import util.strings as s # loads util/strings.br let a = geometry.area(2.0, 3.0) let p = geometry.Point(x=1, y=2) from geometry import Point, area # use these names directly let q = Point(x=3, y=4) ``` `from m import x` only imports items defined in `m` itself. A name can’t be both imported and defined in the same module. ## Tests [Section titled “Tests”](#tests) ```brine test "clamp keeps values in range": assert clamp(15, 0, 10) == 10 assert clamp(-5, 0, 10) == 0 ``` `brine test` runs each test in its own process, in parallel. `$BRINE` is set to the `brine` running the tests, so a test can run the compiler. ## C interop [Section titled “C interop”](#c-interop) ```brine extern fn rand() -> i32 # declare a C standard library function (ints, floats, bools only) ``` # Roadmap > What Brine has done so far, what is planned for backends, GPUs, proofs and tools, and how versions work before and after 1.0. Brine is young: version 0.4.0, with a self-hosted compiler that builds itself on every CI run on Linux, macOS and Windows. Until 1.0, any minor release (0.x) may change the language; 1.0 will mean a stable language and ABI, and there is a lot to do before then. The order below is a plan, not a promise. ## Next [Section titled “Next”](#next) The next steps, from the Brine README: * **Mach-O output**, so macOS on Apple silicon gets native dev builds. * **A built-in linker**, so builds don’t need a C toolchain. * **Arrays kept on the GPU** between constructs. * **AMD and Intel GPUs** through Vulkan. * **Quantifiers in contracts**: `forall i in 0..n: a[i] >= 0`. * **An LSP server** and **a package manager**. ## Done [Section titled “Done”](#done) | Version | What it brought | | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 0.1 | The core language: structs and methods, enums and `match`, generics, `Option`/`Result` and `?`, tuples, arrays, maps, strings with methods and slices, f-strings, `for a, b in`, `enumerate`, file and process I/O, modules. Contracts and the prover. The `par` constructs and `world`. Tests, the CLI, `fmt`, `explain`, `guide`. The self-hosted compiler. | | 0.2 | The Brine IR. The native x86-64 backend (COFF and ELF). C generated from the IR, in parallel translation units cached one by one. | | 0.3 | Unicode-aware string methods. Nested tuple unpacking. `print` with `sep=` and `end=`. The compiler parses and proves in parallel. Unwind tables in native code. | | 0.4 | GPU offload (CUDA and Metal) with results identical to the CPU’s. Deterministic reduction order. The native AArch64 backend for Linux. Native debug builds with DWARF and gdb pretty-printers. Prebuilt releases and one-line installers. Faster native code, strings and allocation. | The [changelog](/reference/changelog/) has every change. ## Planned [Section titled “Planned”](#planned) ### Backends [Section titled “Backends”](#backends) * **Mach-O objects**, so macOS on Apple silicon gets native dev and debug builds instead of going through C. * **CodeView debug info**, for Visual Studio and WinDbg. * **Register allocation and optimization passes on the IR**, so native builds get closer to optimized C. * **A built-in linker and a prebuilt runtime**, so builds don’t need a C toolchain at all. * **SIMD.** * **MSVC support** in the C backend. ### GPUs [Section titled “GPUs”](#gpus) * **Arrays kept on the device** between constructs. Today every launch copies the arrays a body reads to the GPU and the ones it writes back, so constructs that do little work per byte (a plain sum of an array) stay on the CPU. * **`par(...)` forks and nested constructs** on the GPU. * **AMD and Intel GPUs**, once there is hardware to test on: Vulkan compute with SPIR-V kernels (the driver loaded at run time, as CUDA is), exactly rounded `/` and `sqrt` in the kernels, a capability check for denormals and f64, and a self-test kernel at startup that turns the GPU off if its results differ from the CPU’s. NVIDIA GPUs and lavapipe (Mesa’s CPU Vulkan) can test most of it, but not each vendor’s own shader compiler, so it waits for real devices. This includes the AMD and Intel GPUs in Intel Macs: Metal is used only on Apple GPUs for now. ### WebAssembly [Section titled “WebAssembly”](#webassembly) [WebAssembly builds](/tooling/webassembly/) (`--target wasm` and `web`) are on Brine’s main branch, not yet in a release. Next: * **Threads** (wasi-threads, Web Workers), so `par` runs in parallel there too. Today it runs in order on one thread. * **Files in the browser.** * **A playground page** that runs the compiler itself in the browser. The compiler already builds for WebAssembly: its generated C for itself is byte-identical to the native compiler’s. ### Proofs [Section titled “Proofs”](#proofs) * **Generic bodies checked once** against constraints, rather than once per instance. * **Quantifiers in specs**: `forall i in 0..n: a[i] >= 0`. * **Reasoning about array contents.** * **Nonlinear facts** such as `i * j`. * **Counterexamples** when a proof fails. ### Tools [Section titled “Tools”](#tools) * **An LSP server**, for editors. * **`brine fix`**, which applies the machine-applicable fixes diagnostics already carry in `--json` output. * **A package manager** (`brine add`). * **`brine launch --hosts`**, to start a multi-machine `world` run (today you set `BRINE_RANK`, `BRINE_WORLD_SIZE` and `BRINE_PEERS` yourself; see [Environment variables](/tooling/environment-variables/#brine_rank-brine_world_size-brine_peers)). ## Versions [Section titled “Versions”](#versions) Brine follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The version is set in one place in the compiler, and every release is a tagged commit with notes in the [changelog](/reference/changelog/). | Release | Means | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 0.x.0 (minor) | new features or **breaking changes**: the language, the CLI or the results of a program may change. Breaking changes are marked in the changelog with how to migrate. | | 0.x.y (patch) | fixes only | | 1.0.0 | a stable language and ABI: from then on, breaking changes only in a new major version | Versions move one step at a time, and only the maintainer decides when 1.0 happens. Pre-releases are tagged with a `-` (like `v0.5.0-rc.1`) and marked as such on GitHub. Until 1.0, pin the version you build with (`BRINE_VERSION=0.4.0` for the [installers](/tooling/environment-variables/#installer-variables)) and read the changelog before upgrading. ## Related [Section titled “Related”](#related) * [Changelog](/reference/changelog/) * [Design and architecture](/reference/design/) * [Platforms](/reference/platforms/#coming-next) # Builds, backends and performance > The dev, release and debug profiles, the native x86-64 and AArch64 backends and the C backend, build caching, compile speed, and how Brine's run time compares with C. Brine has three build profiles: **dev** (the default, built for a fast edit-run loop), **release** (`--release`, optimized through a C compiler) and **debug** (`--debug`, for a debugger). On x86-64 Windows and Linux and on AArch64 Linux, dev and debug builds go straight to machine code, so the whole 34,000-line compiler builds itself in under a second; release builds go through gcc or clang and run at about the speed of hand-written C. Every profile keeps every runtime check the prover couldn’t remove: there is no unchecked mode. ## Profiles [Section titled “Profiles”](#profiles) | Profile | Command | Code generator on x86-64 Windows/Linux, AArch64 Linux | Elsewhere | Output | | ------- | -------------------------- | ----------------------------------------------------- | ------------------------------------------------- | ----------------- | | dev | `brine build`, `brine run` | native backend | C at `-O2`, split into units compiled in parallel | `target/dev/` | | release | `--release` | C at `-O3`, one unit | C at `-O3`, one unit | `target/release/` | | debug | `--debug`, `brine debug` | native backend with DWARF, no optimizations | C at `-O0 -g` with `#line` | `target/debug/` | What stays the same in every profile: * **The checks.** Overflow, bounds, division by zero, narrowing conversions and contracts behave identically. A program that panics in a release build panics in a dev build at the same place with the same message. See [Runtime checks](/proofs/runtime-checks/). * **The results.** Integer and float semantics are Brine’s, not the C compiler’s: no fused multiply-adds, the same rounding, the same [reduction order](/parallelism/deterministic-reductions/). A release build prints the same output as a dev build. * **The runtime.** The runtime (`brine_rt.c`) is always C, compiled once with the profile’s flags and cached. C builds use `-std=c11 -fno-strict-aliasing -w` plus the profile’s flags. `brine test --release` runs the tests with the release profile. ## Backends [Section titled “Backends”](#backends) All backends read the same **Brine IR**, which sits after the type checker and the prover. The IR keeps only the checks the prover couldn’t remove, and records what Brine knows that C doesn’t (no aliasing between values, race-free `par`). | Backend | Source | Writes | Used for | | ------- | --------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | x86-64 | `compiler/x64.br`, `obj.br` | COFF objects (Windows), ELF objects (Linux), with unwind tables and DWARF | dev and debug builds on x86-64 Windows and Linux | | AArch64 | `compiler/a64.br`, `obj.br` | ELF objects with `.eh_frame` and DWARF | dev and debug builds on AArch64 Linux | | C | `compiler/ir_c.br` | C11, one statement per IR instruction | every `--release` build; every build on macOS and other platforms; every [WebAssembly](/tooling/webassembly/) build; the bootstrap seed | | PTX | `compiler/ptx.br` | NVIDIA GPU kernels | `par` constructs that can run on a GPU, on Windows and Linux | | Metal | `compiler/msl.br` | Metal Shading Language | the same, on Apple GPUs (macOS) | The native backends write the object file themselves; the system C compiler is still used to link it with the runtime. The GPU backends run next to whichever CPU backend is used: the program carries both versions of each eligible construct, and the runtime picks one per launch. See [GPUs](/parallelism/gpu/). | Platform | dev | release | debug | | ------------------- | ------------- | ------- | -------------- | | Windows x86-64 | native (COFF) | C | native + DWARF | | Linux x86-64 | native (ELF) | C | native + DWARF | | Linux ARM64 | native (ELF) | C | native + DWARF | | macOS Apple silicon | C | C | C with `#line` | | macOS Intel | C | C | C with `#line` | macOS builds go through C until Brine writes Mach-O objects (on the [roadmap](/reference/roadmap/)). [`BRINE_BACKEND`](/tooling/environment-variables/#brine_backend)`=c` or `=native` overrides the choice; such builds go to their own folders (`target/dev-c/`, …). ### WebAssembly [Section titled “WebAssembly”](#webassembly) `--target wasm` and `--target web` compile the generated C with [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) instead of the system C compiler, for every profile: dev at `-O2` in parallel units, release at `-O3` in one unit, debug at `-O0 -g`. They go to their own folders (`target/dev-wasm/`, `target/release-web/`, …). The checks and the results are the same as natively; `par` constructs run on one thread. See [WebAssembly](/tooling/webassembly/). ### Why release goes through C [Section titled “Why release goes through C”](#why-release-goes-through-c) gcc and clang at `-O2`/`-O3` already produce code close to the best hand-written C gets, with decades of optimization passes behind them. The native backends are built for compile speed: they turn the IR into machine code in one quick pass, with locals and floats kept in registers but without the loop optimizations, inlining across functions and vectorization a C compiler does. So Brine uses its own backends where you build often (dev, debug) and the C compiler where you want the fastest program (release). Optimization passes on the IR are on the [roadmap](/reference/roadmap/). A native backend’s advantages are elsewhere: builds take a fraction of a second, they will eventually need no C toolchain, and they can do what C can’t express well (exact overflow handling, guaranteed tail calls, debug info for Brine names and types). ### The generated C [Section titled “The generated C”](#the-generated-c) `brine build --emit-c` writes the program as a single C file next to the executable (`target/dev/hello.c`), as the C backend generates it, even when the executable was built by a native backend: ```sh brine build --emit-c ``` ```text Wrote target/dev/hello.c Fresh hello v0.1.0 [dev] in 0.02s Output target/dev/hello.exe ``` It includes `brine_rt.h`, so compiling it by hand needs the runtime from the Brine source tree (`runtime/brine_rt.h`, `runtime/brine_rt.c`). It’s for reading and for curiosity: don’t edit it and build from it, since the next build regenerates it. ## Caching [Section titled “Caching”](#caching) Builds do as little work as they can: * **The runtime** is compiled once per runtime version, C compiler and flags, into `rt-/` in the cache. The first build of a new project takes about two seconds for this; later builds skip it. * **Objects** are named by a hash of what they’re made from. A C dev build splits the program into one translation unit per core (up to 16), assigning each function to a unit by a hash of its name, with each unit declaring only what it uses. The units compile in parallel, and an edit recompiles only the units it touches. Release builds stay a single unit, so the C compiler sees the whole program. * **The link** is skipped when nothing changed: the `.brine-hash` file next to the executable records what it was linked from, and a matching build prints `Fresh`. The cache is `target/cache/` in a project and `/brine-cache/` for single files. See [Projects and brine.toml](/tooling/projects/#the-cache). The compiler uses `par` itself: imported modules are parsed a wave of imports at a time, in parallel, and the prover checks every function on its own thread. Results are combined in a fixed order, so diagnostics are the same whatever the thread count. ## Compile speed [Section titled “Compile speed”](#compile-speed) The compiler is about 34,000 lines of Brine. Building it with itself on a Ryzen 9 9950X3D (Windows 11, gcc 16.2), with the runtime already cached: | Build | Time | | ------------------------------------------------------- | ------ | | dev, native x86-64 | 0.92 s | | dev, nothing changed (`Fresh`) | 0.66 s | | dev through C (`BRINE_BACKEND=c`, 16 units in parallel) | 2.65 s | | release through C (`-O3`, one unit) | 27.4 s | `BRINE_TIMES=1` shows where the time goes in the native build: ```sh BRINE_TIMES=1 brine build compiler/main.br -o brine2 ``` ```text parse 0.263s check 0.122s uniform 0.013s verify 0.157s lower 0.071s x86-64 0.162s link 0.089s Built main [dev] in 0.92s ``` `brine check` stops after `verify`, which is why it is the fastest way to find errors. A small program checks in a few milliseconds and builds in about 0.1 s, almost all of it linking. ## Performance [Section titled “Performance”](#performance) The `bench/` folder in the Brine repository has eight single-threaded programs, each written in Brine and in C with the same algorithm. Times are relative to the C version compiled with `gcc -O2`; lower is better, below 1.00x is faster than C. | Program | What it exercises | Brine release | Brine dev (native) | | ----------- | ----------------------------------------------------- | ------------- | ------------------ | | nbody | float math on an array of structs (C: 0.717 s) | 1.01x | 3.42x | | spectral | float math, small function calls, array reads | 1.02x | 2.03x | | fannkuch | small int arrays, swaps and index arithmetic | 0.91x | 1.22x | | mandelbrot | tight float loops over a 4000×4000 grid | 1.00x | 1.30x | | sieve | a 100M-element bool array, strided writes | 1.15x | 1.44x | | binarytrees | allocating and freeing many small recursive values | 0.55x | 0.89x | | wordfreq | building strings, counting them in a `{str: int}` map | 3.91x | 5.06x | | matmul | 800×800 float matrix multiply in flat arrays | 1.16x | 6.28x | Measured on a Ryzen 9 9950X3D, Windows 11, gcc 16.2 (MinGW), Brine 0.4.0, best of three runs. Expect a few percent of noise between runs, more for the shortest programs. What the numbers say: * **Release builds run at C’s speed** on numeric code (nbody, spectral, mandelbrot), within a few percent, with every check that remains still running. * **Brine wins where its memory model helps.** binarytrees runs in about half of C’s time: small blocks come from Brine’s own allocator, and a value with only one reference is freed without an atomic instruction, while the C version calls `malloc` and `free` for every node. * **Checks that remain cost something.** In sieve, the prover can’t show that `is_prime[j]` is in bounds in the inner loop, so the bounds check runs on every write of the inner loop (`brine check --report` lists it). matmul and nbody have no checks left at all. * **Strings and maps are the slow spot.** wordfreq takes almost 4x C’s time. The C version writes each word into a reused `char[16]` buffer and looks it up in a bare open-addressing table; the brine version builds a new reference-counted string for every word (`w += letters[...]` makes a one-character string per step), then looks the word up twice (`w in counts`, then `counts[w] += 1`) in a map that also keeps insertion order. The C run is also only 23 ms, so fixed startup costs show more. Faster strings and maps are ongoing work; 0.4.0 already made `s += t` grow strings in place. * **Dev builds trade run time for build time.** The native backend doesn’t vectorize or optimize loops, which matters most for matmul (6.28x) and nbody (3.42x). For anything you time, use `--release`. These programs are single-threaded on purpose, to compare code generation. With `par`, Brine programs also use every core and, when it’s faster, the GPU: see [GPUs](/parallelism/gpu/) for those numbers. ### Reproducing the numbers [Section titled “Reproducing the numbers”](#reproducing-the-numbers) From a clone of the Brine repository, with Brine and a C compiler on your `PATH`: ```sh git clone https://github.com/brinelang/brine && cd brine brine run bench/compare.br ``` `bench/compare.br` builds each program three ways (C with `$CC -O2`, falling back to the first of `cc`, `gcc` and `clang`; Brine `--release`; Brine dev), checks that all three print the same output, and prints the best of three runs: ```text program C -O2 release dev (native) nbody 0.708s 0.710s 1.00x 2.441s 3.45x spectral 0.271s 0.274s 1.01x 0.548s 2.02x fannkuch 1.686s 1.537s 0.91x 2.038s 1.21x mandelbrot 0.567s 0.597s 1.05x 0.821s 1.45x sieve 0.253s 0.351s 1.39x 0.508s 2.01x binarytrees 1.337s 0.747s 0.56x 1.183s 0.88x wordfreq 0.023s 0.090s 3.90x 0.117s 5.04x matmul 0.062s 0.068s 1.10x 0.405s 6.53x ``` (That is a second run on the same machine: the short programs, sieve and matmul especially, vary the most.) It tests the `brine` on your `PATH`; to test another build, pass its path first (it must contain a `/` or end in `.exe`): `brine run bench/compare.br -- ./brine2`. Program names after that run only those programs: `brine run bench/compare.br -- nbody sieve`. The executables go to `target/bench/`. ## Related [Section titled “Related”](#related) * [The brine command](/tooling/cli/#brine-build) * [Debugging](/tooling/debugging/) * [WebAssembly](/tooling/webassembly/) * [Environment variables](/tooling/environment-variables/) * [Design and architecture](/reference/design/) * [Platforms](/reference/platforms/) # The brine command > Every brine subcommand and flag, what each one prints, its JSON output, and the exit codes it returns. Everything in Brine goes through one command, `brine`: creating projects, checking, building, running, testing, formatting and debugging. Every command that reports diagnostics takes `--json`, and exit codes are stable: `0` success, `1` your code has a problem, `2` the command line or setup has a problem, and `101` a program panicked. ```sh brine --help ``` ```text brine — a verified, natively parallel systems language Usage: brine [options] Project: new Create a new project in ./ init Create a project in the current directory Build and run: run [file] [-- args] Build and run (default: the current project) --release optimize more --threads worker threads for `par` (default: all cores) --ranks run cooperating processes (the `world` module) --target wasm build WebAssembly and run it with Node.js build [file] Build a native executable --release | --debug optimization profile (default: dev) -o output path --emit-c also write the generated C next to the output --target host (default), wasm (a .wasm module) or web (a page) check [file] Type-check and verify without building (fastest) --report list every runtime check the prover could not remove test [file] [filter] Run `test "...":` blocks in parallel (also --target wasm) debug [file] [-- args] Debug build, then launch gdb or lldb Tools: fmt [files] [--check] Format source files (default: the whole project) explain Explain an error code, e.g. `brine explain E0601` guide Print the language quick reference doctor Check that your toolchain works version Print the version Global options: --json Machine-readable output (one JSON object per line) --color auto | always | never -q, --quiet Only print errors ``` ## Commands at a glance [Section titled “Commands at a glance”](#commands-at-a-glance) | Command | What it does | Flags | | ---------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------ | | [`brine new `](#brine-new) | create a project in `./` | | | [`brine init`](#brine-init) | create a project in the current directory | | | [`brine run [file] [-- args]`](#brine-run) | build and run | `--release`, `--debug`, `--threads N`, `--ranks N`, `--target wasm`, `--typed` | | [`brine build [file]`](#brine-build) | build an executable, a `.wasm` module or a web folder | `--release`, `--debug`, `-o PATH`, `--emit-c`, `--target T`, `--typed` | | [`brine check [file]`](#brine-check) | type-check and prove, no code generation | `--report`, `--typed` | | [`brine test [file] [filter]`](#brine-test) | run `test "...":` blocks in parallel | `--release`, `--target wasm`, `--typed` | | [`brine debug [file] [-- args]`](#brine-debug) | debug build, then gdb or lldb | | | [`brine fmt [files]`](#brine-fmt) | format source files | `--check` | | [`brine explain [code]`](#brine-explain) | explain an error code, or list them all | | | [`brine guide`](#brine-guide) | print the language quick reference | | | [`brine doctor`](#brine-doctor) | check the toolchain | | | [`brine version`](#brine-version) | print the version | | Every command also accepts the [global options](#global-options). A flag a command doesn’t know is an error, with a suggestion: ```text error: unknown option `--reprot` (did you mean `--report`?) ``` ## Which file a command works on [Section titled “Which file a command works on”](#which-file-a-command-works-on) `run`, `build`, `check`, `test` and `debug` take an optional file: | You pass | Brine uses | | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | nothing | the project around you: the nearest `brine.toml` in this directory or a parent, and its main file (`src/main.br` unless [`[build] main`](/tooling/projects/#build) says otherwise) | | `file.br` | that file, as a single-file program (imports load next to it) | | a directory with `brine.toml` | that project | | a directory with `main.br` and no `brine.toml` | that folder’s `main.br` | Outside a project and without a file, Brine says what to do: ```text error: no brine.toml found in this directory or any parent; pass a file (`brine run hello.br`) or create a project with `brine new ` ``` See [Projects and brine.toml](/tooling/projects/) for where each kind of build puts its output. ## brine new [Section titled “brine new”](#brine-new) ```sh brine new hello ``` ```text Created project `hello` cd hello brine run ``` Creates `hello/` with `brine.toml`, `src/main.br`, `AGENTS.md`, `README.md` and `.gitignore`, and runs `git init` unless the folder is already inside a Git repository. The name must start with an ASCII letter or `_` and contain only letters, digits, `_` and `-`. The folder must not exist yet. With `--json` it prints `{"created":"hello"}`. [Projects and brine.toml](/tooling/projects/#brine-new-and-brine-init) shows the files. ## brine init [Section titled “brine init”](#brine-init) ```sh mkdir myapp && cd myapp brine init ``` ```text Created project `myapp` (brine.toml, src/main.br, .gitignore, AGENTS.md, README.md) ``` Like `brine new`, in the current directory. The project is named after the folder (characters that aren’t allowed become `_`). Files that already exist are kept, and the message lists only the ones it wrote. It fails if the directory already has a `brine.toml`. ## brine run [Section titled “brine run”](#brine-run) Builds the program, then runs it. Status lines go to stderr; the program’s own output is untouched. ```sh brine run ``` ```text Built hello v0.1.0 [dev] in 2.05s Running target/dev/hello.exe Hello, brine! [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] ``` A second run with nothing changed skips the compile and the link: ```text Fresh hello v0.1.0 [dev] in 0.02s Running target/dev/hello.exe Hello, brine! [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] ``` (The first build of a project takes about two seconds because it compiles the runtime once; after that, builds take a fraction of a second.) | Flag | Effect | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--release` | build with the [release profile](/tooling/builds-and-backends/#profiles) (optimized C) | | `--debug` | build with the debug profile (no optimizations, debug info) | | `--threads ` | worker threads for `par` (sets [`BRINE_THREADS`](/tooling/environment-variables/) for the program); a positive number | | `--ranks ` | start `n` cooperating processes for the [`world` module](/parallelism/world/), 1 to 1024 | | `--target wasm` | build a WebAssembly module and run it with Node.js, or with [`BRINE_WASM_RUNNER`](/tooling/environment-variables/#brine_wasm_runner); needs [wasi-sdk](/tooling/webassembly/#setup). Can’t be combined with `--ranks`. `--target web` is refused: open a web build in a browser. See [WebAssembly](/tooling/webassembly/) | | `--typed` | require a written type on every variable, as [`[check] typed`](/tooling/projects/#check) does | ### Passing arguments [Section titled “Passing arguments”](#passing-arguments) Everything after `--` goes to the program: argv.br ```brine fn main(): print(args()) ``` ```sh brine run argv.br -- one --two 3 ``` ```text Built argv [dev] in 0.10s Running C:/Users/you/AppData/Local/Temp/brine-cache/465ef71a47ea2199/dev/argv.exe ["one", "--two", "3"] ``` With a file, plain words after it are passed too (`brine run argv.br a b`), but anything that starts with `-` would be read as a Brine flag, so use `--`. Inside a project, always use `--`: `brine run foo` means “run the file `foo`”. ### Several processes [Section titled “Several processes”](#several-processes) ```sh brine run world.br --ranks 4 ``` ```text Built world [dev] in 0.10s Running C:/Users/you/AppData/Local/Temp/brine-cache/88f5d4ab53aec8ad/dev/world.exe (4 ranks) 4 ranks, total = 499999500000, squares = [0, 1, 4, 9] after one lap: token->0->1->2->3 rank 3 received [[0, 3], [1, 3], [2, 3], [3, 3]] ``` Each rank is a process on this machine, connected over loopback TCP. Only rank 0’s output is shown. If one rank fails, Brine stops the others half a second later and returns the first non-zero exit code. See [Multiple processes](/parallelism/world/). ### Exit code [Section titled “Exit code”](#exit-code) `brine run` returns the program’s exit code: `0` when `main` returns normally, the value `main` returns if it returns an `int` (clamped to 0-255), `1` when `main` returns `Err`, and `101` after a [panic](/proofs/runtime-checks/): panic.br ```brine fn main(): let xs = [1, 2, 3] let i = len(args()) print(xs[i + 5]) ``` ```text Built panic [dev] in 0.09s Running C:/Users/you/AppData/Local/Temp/brine-cache/fce079b2338829bc/dev/panic.exe panic: index 5 is out of bounds for an array of length 3 --> panic.br:4:11 ``` That run exits with `101`. If the program doesn’t compile, nothing runs and the exit code is `1`. ## brine build [Section titled “brine build”](#brine-build) Builds an executable without running it. ```sh brine build --release ``` ```text Built hello v0.1.0 [release] in 2.74s Output target/release/hello.exe ``` | Flag | Effect | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--release` | optimized build through C at `-O3`, one translation unit | | `--debug` | debug build: native code with DWARF on x86-64 Windows/Linux and AArch64 Linux, C with `-O0 -g` elsewhere ([Debugging](/tooling/debugging/)) | | `-o ` | where to write the executable; on Windows `.exe` is added when the name has no extension (native builds only) | | `--emit-c` | also write the program as one C file next to the executable (`hello.exe` → `hello.c`) | | `--target ` | `host` (the default, also `native`), `wasm` (a WASI module, also `wasm32` or `wasi`) or `web` (a folder with `index.html`, `brine.js` and `program.wasm`). See [WebAssembly](/tooling/webassembly/) | | `--typed` | require a written type on every variable | Where the executable goes: | Build | Output | | -------------------- | ---------------------------------------------------------------------------------- | | project, no `-o` | `target//` (`target/dev/hello.exe`) | | single file, no `-o` | the current directory, named after the file (`brine build tool.br` → `./tool.exe`) | | `-o path` | `path` | `--target wasm` writes `.wasm` instead (`target/dev-wasm/hello.wasm` in a project), and `--target web` a folder named `-web` (`target/dev-web/hello-web/`). `--emit-c` writes the whole program as the C backend would generate it, even when the executable itself was built by the [native backend](/tooling/builds-and-backends/#backends): ```text Wrote target/dev/hello.c Fresh hello v0.1.0 [dev] in 0.02s Output target/dev/hello.exe ``` With `--json`, a successful build prints `{"built":"target/dev/hello.exe"}`. ## brine check [Section titled “brine check”](#brine-check) Parses, type-checks and runs the prover, without generating code. It is the fastest feedback loop: run it after every edit. sum.br ```brine fn total(xs: [int]) -> int: var s = 0 for i in 0..len(xs): s += xs[i] return s fn pick(xs: [int], k: int) -> int: return xs[k] fn main(): let xs = [1, 2, 3, 4] print(total(xs), pick(xs, 2)) ``` ```sh brine check sum.br ``` ```text Checked sum in 0.00s — 1 runtime check proven unnecessary, 1 remain (see --report) ``` | Flag | Effect | | ---------- | ---------------------------------------------------------------------------- | | `--report` | list every runtime check the prover couldn’t remove, one per line | | `--typed` | require a written type on every variable ([E0337](/reference/errors/#e0337)) | ```sh brine check sum.br --report ``` ```text sum.br:8:12: index bounds check `xs[k]` Checked sum in 0.00s — 1 runtime check proven unnecessary, 1 remain ``` `xs[i]` in `total` was proven in bounds; `xs[k]` in `pick` wasn’t, because nothing says `k` is in range. A `requires 0 <= k < len(xs)` would remove it. See [Runtime checks](/proofs/runtime-checks/). `brine check` doesn’t need a `main` function, so it also works on library modules. It exits with `0` when there are no errors (warnings don’t count) and `1` when there are. With `--json`: ```text {"runtime_check":"index bounds check","location":"sum.br:8:12","code":"xs[k]"} {"ok":true,"checks_proven":1,"checks_remaining":1} ``` ## brine test [Section titled “brine test”](#brine-test) Builds the `test "...":` blocks of a file or project and runs each one in its own process, in parallel. t.br ```brine fn double(x: int) -> int: return x * 2 test "double doubles": assert double(4) == 8 test "double of zero": assert double(0) == 1 fn main(): print(double(21)) ``` ```sh brine test t.br ``` ```text Built tests [dev] in 0.10s test double doubles ... ok test double of zero ... FAILED ---- double of zero ---- panic: assertion failed: double(0) == 1 --> t.br:8:5 test result: FAILED. 1 passed; 1 failed; finished in 0.03s ``` | Argument or flag | Effect | | ---------------- | ------------------------------------------------------------------------------------------------------------------ | | `file` | a `.br` file or a directory; without one, the current project | | `filter` | run only the tests whose name contains this text (`brine test t.br doubles`, or `brine test doubles` in a project) | | `--release` | build the tests with the release profile | | `--target wasm` | build the tests as a WebAssembly module and run each one with Node.js ([WebAssembly](/tooling/webassembly/#tests)) | | `--typed` | require a written type on every variable | Details: * A test passes when it finishes without a panic. The exit code is `0` when every test passed and `1` when any failed or the tests didn’t compile. * Each test process gets `BRINE_THREADS=2`, so many tests can run at once without each starting a thread per core, and `BRINE` set to the path of the `brine` running the tests, so a test can run the compiler. * Results are printed in source order, whatever order the tests finish in. `--quiet` prints only the failures and the summary. * A filter that matches nothing is not an error: it prints `(no tests match the filter; add one with `test “name”:`)` and exits with `0`. With `--json`, one object per test: ```text {"test":"double doubles","ok":true,"seconds":0.021,"output":""} {"test":"double of zero","ok":false,"seconds":0.026,"output":"\npanic: assertion failed: double(0) == 1\n --> t.br:8:5\n"} ``` ## brine debug [Section titled “brine debug”](#brine-debug) Makes a [debug build](/tooling/debugging/), then starts gdb (or lldb, if there is no gdb) on it. Arguments after `--` go to the program. `brine debug` takes no other flags. ```sh brine debug shapes.br ``` gdb gets Brine’s pretty-printers, so values print the way Brine prints them, and stepping skips the C runtime. Without gdb or lldb: ```text error: no debugger found (install gdb or lldb); the debug build is at ``` [Debugging](/tooling/debugging/) has a full session. ## brine fmt [Section titled “brine fmt”](#brine-fmt) Rewrites source files in the canonical format: 4-space indentation, one space around operators, at most one blank line, and lines up to 100 characters. ```sh brine fmt # every .br file in the project brine fmt src/main.br # just these files (or directories) brine fmt --check # change nothing; exit 1 if a file would change ``` ```text would reformat messy.br 1 file checked, 1 need formatting ``` Without arguments it formats every `.br` file under the project root (or the current directory outside a project), skipping `target/` and folders whose names start with `.`. A file with a syntax error is reported and left alone. See [Formatting](/tooling/formatting/) for what it changes. | Flag | Effect | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `--check` | report files that aren’t formatted (`would reformat `, or `{"unformatted":""}` with `--json`) and exit with `1`; write nothing | ## brine explain [Section titled “brine explain”](#brine-explain) ```sh brine explain E0401 ``` ```text E0401: assigning to an immutable binding Variables declared with `let` (and loop variables) can't change. Declare the variable with `var` if it needs to change: var total = 0 total += 1 ``` Without a code, it lists every code with its title. With `--json`, it prints `{"code":...,"title":...,"explanation":...}` (or one `{"code":...,"title":...}` line per code for the list). An unknown code exits with `2`. The same text is on the [error codes](/reference/errors/) page. ## brine guide [Section titled “brine guide”](#brine-guide) Prints the language quick reference: the whole language on one page, built into the compiler, so it works offline and always matches the installed version. It’s the same text as the [quick reference](/reference/quick-reference/) page. ```sh brine guide | less ``` ## brine doctor [Section titled “brine doctor”](#brine-doctor) Checks that Brine can build and run programs on this machine. ```sh brine doctor ``` ```text ✓ brine 0.4.0 (windows x86_64) ✓ C compiler gcc — gcc (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders, r1) 16.2.0 ✓ parallelism 32 hardware threads ✓ local sockets needed for `brine run --ranks` ✓ build + run compiled and ran a parallel program ✓ debugger gdb ✓ WebAssembly C:/Users/you/tools/wasi-sdk-34.0-x86_64-windows\bin\clang.exe ``` | Check | What it tests | | ------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `brine` | the version and the platform | | C compiler | `BRINE_CC`, `CC`, or the first of `clang`, `gcc`, `cc` that runs | | parallelism | how many hardware threads `par` will use by default | | local sockets | that a local TCP port can be reserved (for `--ranks`) | | build + run | builds and runs a small `par sum` program in a temp folder and checks its output | | debugger | gdb or lldb, for `brine debug` (optional: a missing debugger doesn’t fail) | | WebAssembly | wasi-sdk’s clang, through `WASI_SDK_PATH` or `/opt/wasi-sdk`, for [`--target wasm` and `web`](/tooling/webassembly/) (optional) | It exits with `1` if a required check fails. With `--json` it prints one `{"check":...,"ok":...,"detail":...}` line per required check. ## brine version [Section titled “brine version”](#brine-version) ```sh brine version ``` ```text brine 0.4.0 ``` `brine --version` and `brine -V` print the same; `brine version --json` prints `{"version":"0.4.0"}`. `brine help`, `brine --help` and `brine -h` print the help, as does `brine` alone. ## Global options [Section titled “Global options”](#global-options) These work with every command, before or after it (`brine --json check` and `brine check --json` are the same). Flags that take a value also accept `=`: `--color=never`. | Option | Effect | | ---------------- | --------------------------------------------------------------------------------------------------- | | `--json` | machine-readable output on stdout, one JSON object per line; status lines are not printed | | `--color ` | `auto` (the default: color when stderr is a terminal and `NO_COLOR` isn’t set), `always` or `never` | | `-q`, `--quiet` | print only errors: no `Built`/`Running` lines, no warnings | ### JSON output [Section titled “JSON output”](#json-output) In text mode, diagnostics and status lines go to stderr. With `--json`, each diagnostic is one line on stdout: ```text {"severity":"error","code":"E0401","message":"cannot assign to `x` because it was declared with `let`","file":"bad.br","line":3,"column":5,"end_line":3,"end_column":6,"label":"immutable","related":[{"file":"bad.br","line":2,"column":9,"end_line":2,"end_column":10,"label":"declared here"}],"notes":[],"help":["declare it with `var x = ...` to make it mutable"],"fixes":[]} ``` | Field | Meaning | | -------------------------------------------------- | ----------------------------------------------------------------------------- | | `severity` | `"error"` or `"warning"` | | `code` | the stable code (`"E0401"`), or `null` for command-line errors | | `message` | the one-line message | | `file`, `line`, `column`, `end_line`, `end_column` | the primary span (1-based; the end is exclusive) | | `label` | the text under the primary span | | `related` | other spans, each with its own location and `label` | | `notes`, `help` | the `= note:` and `= help:` lines | | `fixes` | machine-applicable edits: `{line, column, end_line, end_column, replacement}` | Errors in the command line itself also come out as JSON when `--json` is given: ```text {"severity":"error","code":null,"message":"unknown command `chek`; did you mean `brine check`? (see `brine help`)"} ``` Other JSON records each command prints: | Command | Records | | ------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `check` | `{"ok":true,"checks_proven":N,"checks_remaining":N}`; with `--report`, a `{"runtime_check","location","code"}` line per check | | `build` | `{"built":""}` | | `test` | `{"test","ok","seconds","output"}` per test | | `fmt --check` | `{"unformatted":""}` per file | | `new` | `{"created":""}` | | `explain` | `{"code","title","explanation"}` | | `doctor` | `{"check","ok","detail"}` per check | | `version` | `{"version":"0.4.0"}` | For `run`, the program’s own output is not wrapped: it passes through as is. ## Exit codes [Section titled “Exit codes”](#exit-codes) | Code | When | | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `0` | success | | `1` | the program has errors (`check`, `build`, `run`, `test`, `debug`); a test failed; `fmt --check` found a file to reformat or `fmt` hit a syntax error; `doctor` found a problem; a program’s `main` returned `Err` | | `2` | a problem with the command itself: unknown command or option, a missing or bad value, a file that doesn’t exist, no project found, an invalid `brine.toml`, an unknown error code for `explain` | | `101` | a program panicked (overflow, an out-of-bounds index, a failed `assert`, …) | | other | `brine run` returns the program’s own exit code (`fn main() -> int`) | Warnings never change the exit code. ## Internal commands [Section titled “Internal commands”](#internal-commands) Three more commands exist for working on the compiler itself. They aren’t in `--help` and may change in any release: | Command | Prints | | --------------------------------------------------------------- | -------------------------------------------------------- | | `brine lex [--comments] ` | the tokens | | `brine parse ` | the syntax tree | | `brine emit-c [--tests] [--release\|--debug] [-o out.c]` | the generated C (this is how the bootstrap seed is made) | ## Related [Section titled “Related”](#related) * [Projects and brine.toml](/tooling/projects/) * [Builds, backends and performance](/tooling/builds-and-backends/) * [WebAssembly](/tooling/webassembly/) * [Environment variables](/tooling/environment-variables/) * [Error codes](/reference/errors/) # Debugging > Debug builds, brine debug, breaking at file.br:line and at Brine functions in gdb and lldb, the gdb pretty-printers, the C fallback, and BRINE_LEAKCHECK. `brine debug` makes a debug build and starts gdb (or lldb) on it. On x86-64 Windows and Linux and on AArch64 Linux, debug builds are native code with DWARF debug info: you break at `file.br:line` or at a Brine function such as `Point.norm`, step through Brine lines, and see every local by its Brine name and type. With gdb, values print the way Brine prints them (`"origin"`, `[1, 2, 3]`, `Shape.Rect(w=2, h=3)`). ## A session [Section titled “A session”](#a-session) shapes.br ```brine struct Point: x: float y: float fn norm(self) -> float: return sqrt(self.x * self.x + self.y * self.y) fn main(): let name = "origin" let xs = [1, 2, 3] let p = Point(x=3.0, y=4.0) let n = p.norm() print(name, xs, n) ``` ```sh brine debug shapes.br ``` A session on Windows with gdb 17.2, with the commands typed after each `(gdb)` prompt (gdb’s `[New Thread ...]` lines are left out): ```text Built shapes [debug] in 0.10s Reading symbols from C:\Users\you\AppData\Local\Temp\brine-cache\d708f6e0b6b1cd6e\debug\shapes.exe... (gdb) break shapes.br:13 Breakpoint 1 at 0x1400019e4: file shapes.br, line 13. (gdb) run Starting program: C:\Users\you\AppData\Local\Temp\brine-cache\d708f6e0b6b1cd6e\debug\shapes.exe Thread 1 hit Breakpoint 1, main () at shapes.br:13 13 print(name, xs, n) (gdb) info locals n = 5 p = {x = 3, y = 4} xs = [1, 2, 3] name = "origin" ``` Methods are named `Type.method`, so you can break on them directly: ```text (gdb) break Point.norm Breakpoint 2 at 0x14000176f: file shapes.br, line 6. (gdb) run Thread 1 hit Breakpoint 2, Point.norm (self=...) at shapes.br:6 6 return sqrt(self.x * self.x + self.y * self.y) (gdb) bt #0 Point.norm (self=...) at shapes.br:6 #1 0x00007ff70c6419d4 in main () at shapes.br:12 #2 0x00007ff70c6418f5 in main () #3 0x00007ff70c6413ba in __tmainCRTStartup () #4 0x00007ff70c641080 in mainCRTStartup () (gdb) info args self = {x = 3, y = 4} (gdb) finish 0x00007ff70c6419d4 in main () at shapes.br:12 12 let n = p.norm() Value returned is $1 = 5 ``` Frame `#1 main` is your `fn main`; the second `main` below it is the runtime’s C entry point, which starts the thread pool and calls yours. ## brine debug [Section titled “brine debug”](#brine-debug) ```sh brine debug # the current project brine debug shapes.br # a single file brine debug shapes.br -- a b c # pass arguments to the program ``` `brine debug` builds with the debug profile (the same build as `brine build --debug`), then starts: | Debugger found | Command Brine runs | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | gdb with Python | `gdb -q -x /brine-gdb.py --args [args]`: loads the [pretty-printers](#the-gdb-pretty-printers), which also skip the C runtime when stepping | | gdb without Python | `gdb -q -ex "skip -gfi */brine_rt.c" -ex "skip -gfi */brine_rt.h" --args [args]`: steps skip the runtime, values print as their C layout | | lldb (no gdb) | `lldb -O "settings set target.process.thread.step-avoid-regexp ^(brine_\|br_)" -- [args]` | It looks for `gdb` first, then `lldb`, on your `PATH`. Without either it stops after the build and says where the executable is. `brine doctor` shows which debugger it found. ## Debug builds [Section titled “Debug builds”](#debug-builds) ```sh brine build --debug ``` ```text Built hello v0.1.0 [debug] in 0.97s Output target/debug/hello.exe ``` | Platform | Debug build | | ----------------------------------------- | --------------------------------------------------------------------------- | | x86-64 Windows (COFF), x86-64 Linux (ELF) | native code from the x86-64 backend, with DWARF | | AArch64 Linux (ELF) | native code from the AArch64 backend, with DWARF | | macOS, other platforms | C at `-O0 -g` with `#line` directives ([below](#through-c-line-directives)) | A native debug build carries: * **Lines**: every Brine line has an address, so `break file.br:line`, `step`, `next` and `finish` work in Brine terms. * **Functions** under their Brine names: `main`, `area`, `Point.norm`, `Shape.area`. Functions from imported modules keep their plain names (`area` from `geometry.br` is `area`, at `geometry.br:2`). Each instance of a generic function has the generic’s name (`largest`). * **Variables**: every local and parameter by its Brine name and type, in the scope where it’s declared. * **Unwind info**, so `bt` and profilers walk through Brine frames. Debug code skips the optimizations and keeps each value in its stack slot (no register allocation, no cached values), so what `print` shows is always the variable’s current value. Checks still run: a debug build panics exactly where a dev or release build would. ## Breakpoints [Section titled “Breakpoints”](#breakpoints) | To stop | gdb | lldb | | ------------------------------------ | -------------------------- | ------------------------------ | | at a line | `break shapes.br:13` | `b shapes.br:13` | | in a function | `break main`, `break area` | `b main` | | in a method | `break Point.norm` | `b Point.norm` | | in every function matching a pattern | `rbreak ^Point\.` | `breakpoint set -r '^Point\.'` | | on any panic | `break brine_panic_at` | `b brine_panic_at` | Every panic (overflow, a bad index, a failed `assert`, `panic(...)`, `unwrap()` on `None`) goes through the runtime function `brine_panic_at`, which receives the Brine location and the message: ```text (gdb) break brine_panic_at (gdb) run Thread 1 hit Breakpoint 1, brine_panic_at (loc=0x7ff6a6ee306c "panic.br:4:11", msg=0x5ff920 "index 5 is out of bounds for an array of length 3") at C:\Users\you\AppData\Local\Temp\brine-cache\rt-2fdf516b1c19bd8c\brine_rt.c:142 142 br_lock(&br_io_mu); (gdb) frame 3 #3 0x00007ff6a6ec1902 in main () at panic.br:4 4 print(xs[i + 5]) ``` ## The gdb pretty-printers [Section titled “The gdb pretty-printers”](#the-gdb-pretty-printers) `runtime/brine_gdb.py` teaches gdb to print Brine values the way `print` does. `brine debug` writes it next to the debug build as `brine-gdb.py` and loads it when gdb has Python support. | Type | Without the printers | With them | | ------------- | --------------------------------------- | ------------------------ | | `str` | `0x7ff70c661010 ` | `"origin"` | | `[int]` | `0x80000040` | `[1, 2, 3]` | | `{str: int}` | a pointer | `{"ann": 31, "bob": 27}` | | tuple | a C struct | `(1, "two")` | | enum | a C struct | `Shape.Rect(w=2, h=3)` | | `Option[int]` | a C struct | `Some(1)` | ```text (gdb) info locals pair = (1, "two") ages = {"ann": 31, "bob": 27} found = Some(1) s = Shape.Rect(w=2, h=3) ``` Structs keep gdb’s own layout (`{x = 3, y = 4}`). At most 200 elements or bytes of a value are shown. To use the printers with gdb started by hand (or from an IDE), load the file from the Brine source tree, or the copy `brine debug` wrote: ```sh gdb -x path/to/brine_gdb.py --args target/debug/hello ``` or, inside gdb, `source path/to/brine_gdb.py`. They also make `step` skip the runtime’s C functions. ## lldb [Section titled “lldb”](#lldb) lldb reads the same DWARF, so lines, functions and variables work as in gdb: ```sh lldb target/debug/hello ``` ```text (lldb) b shapes.br:13 (lldb) run (lldb) frame variable (lldb) b Point.norm (lldb) thread step-over ``` There are no lldb formatters yet: strings, arrays, maps and enums show as their C layout (pointers and structs). `brine debug` sets `step-avoid-regexp` so stepping doesn’t enter runtime functions (`brine_*`, `br_*`). ## Through C: #line directives [Section titled “Through C: #line directives”](#through-c-line-directives) Where there is no native backend (macOS, and any other platform that builds through C), and whenever `BRINE_BACKEND=c` is set, a debug build is C compiled at `-O0 -g`, with `#line` directives that point back at the Brine source. Breakpoints by file and line, stepping and backtraces still work, but: * variables show under their C names: the Brine name and a number, like `name_0`, `xs_1`, `p_3`, `n_4`, next to the compiler’s temporaries (`v0`, `v1`, …); * values don’t always match the Brine variable at a given line, because the C code keeps them in temporaries; * the pretty-printers don’t apply to those C variables. ```sh BRINE_BACKEND=c brine build --debug shapes.br -o shapes_c ``` Prefer a native debug build wherever there is one. ## Leak checking [Section titled “Leak checking”](#leak-checking) `BRINE_LEAKCHECK=1` makes a program print how many strings and arrays are still allocated when it exits. Memory is reference counted and freed at scope exit, so the answer should always be 0: ```sh BRINE_LEAKCHECK=1 target/dev/hello ``` ```text Hello, brine! [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] brine: 0 live objects at exit ``` Brine’s own test suite requires 0. A non-zero count is a bug in Brine, not in your program; please report it with the program. ## Related [Section titled “Related”](#related) * [The brine command](/tooling/cli/#brine-debug) * [Builds, backends and performance](/tooling/builds-and-backends/) * [Environment variables](/tooling/environment-variables/) * [Runtime checks](/proofs/runtime-checks/) # Environment variables > Every environment variable Brine, Brine programs and the Brine installers read, with its values and effect. Brine needs no environment variables: everything has a default. The ones below change how programs run (threads, the GPU, multi-process runs), how the compiler builds (backend, C compiler, WebAssembly, timing), and how the installers install. This is the complete list for Brine 0.4.0, plus the two [WebAssembly](#webassembly) variables, which come with WebAssembly support after 0.4.0. ## Summary [Section titled “Summary”](#summary) | Variable | Read by | Values | Effect | | -------------------------------------------------------------- | ------------------ | ------------------- | ---------------------------------------------------------------------------------------- | | [`BRINE_THREADS`](#brine_threads) | programs | a number | worker threads for `par` (default: all hardware threads) | | [`BRINE_GPU`](#brine_gpu) | programs | `auto`, `on`, `off` | when `par` constructs may run on a GPU | | [`BRINE_GPU_LOG`](#brine_gpu_log) | programs | `1` | print where each `par` construct ran, and why | | [`BRINE_LEAKCHECK`](#brine_leakcheck) | programs | any | print the number of live strings and arrays at exit | | [`BRINE_RANK`](#brine_rank-brine_world_size-brine_peers) | programs | `0` to size − 1 | this process’s rank in a `world` run | | [`BRINE_WORLD_SIZE`](#brine_rank-brine_world_size-brine_peers) | programs | a number | how many ranks there are | | [`BRINE_PEERS`](#brine_rank-brine_world_size-brine_peers) | programs | `host:port,...` | where every rank listens | | [`BRINE`](#brine) | tests | a path | set by `brine test`: the `brine` running the tests | | [`BRINE_BACKEND`](#brine_backend) | compiler | `c`, `native` | force the C or the native backend | | [`BRINE_CC`](#brine_cc-and-cc) | compiler | a command | the C compiler (checked first) | | [`CC`](#brine_cc-and-cc) | compiler | a command | the C compiler, if `BRINE_CC` isn’t set | | [`BRINE_TIMES`](#brine_times) | compiler | `1`, `2` | print how long each compiler phase takes | | [`BRINE_DUMP_PTX`](#brine_dump_ptx) | compiler | a file path | write the program’s GPU module to this file | | [`WASI_SDK_PATH`](#wasi_sdk_path) | compiler | a path | the wasi-sdk folder, for WebAssembly builds | | [`BRINE_WASM_RUNNER`](#brine_wasm_runner) | compiler | a command | the WASI runtime `brine run` and `brine test` use for `--target wasm` (default: Node.js) | | [`NO_COLOR`](#no_color) | compiler | any | no colors in `--color auto` mode | | [`TMPDIR`](#tmpdir) | compiler, programs | a path | the temp directory on Linux and macOS | | [`BRINE_VERSION`](#installer-variables) | installers | `0.4.0` | the version to install | | [`BRINE_INSTALL`](#installer-variables) | installers | a path | where to install | | [`BRINE_NO_MODIFY_PATH`](#installer-variables) | installers | `1` | don’t add Brine to `PATH` | | [`BRINE_DOWNLOAD_URL`](#installer-variables) | installers | a URL | where to download releases from | “Programs” means every executable Brine builds: the variables are read by the runtime when the program runs, not when it’s compiled. The `brine` compiler is itself a Brine program, so the program variables also apply to it (see the [note below](#programs-and-the-compiler)). ## Running programs [Section titled “Running programs”](#running-programs) ### BRINE_THREADS [Section titled “BRINE_THREADS”](#brine_threads) The number of worker threads for `par` constructs. Unset, `0`, a negative number or something that isn’t a number means one thread per hardware thread; the most is 1024. ```sh BRINE_THREADS=4 target/release/sim brine run --release --threads 4 # the same: brine sets BRINE_THREADS for the program ``` Results don’t depend on it: [reductions](/parallelism/deterministic-reductions/) combine in an order fixed by the number of elements, so `BRINE_THREADS=1` and `BRINE_THREADS=32` print the same bits. `brine test` runs each test with `BRINE_THREADS=2`. [WebAssembly](/tooling/webassembly/#one-thread) builds always use one thread and ignore it. ### BRINE_GPU [Section titled “BRINE_GPU”](#brine_gpu) Whether `par for`, `par [...]` and `par` reductions may run on a GPU. See [GPUs](/parallelism/gpu/). | Value | Effect | | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | | `auto` (default; also any value not below) | the runtime measures each construct and uses the GPU when it expects that to be at least 25% faster, copies included | | `on`, `1` | use the GPU for every construct that can run there | | `off`, `0` | never use a GPU | The results are the same bits whichever side runs a construct. ### BRINE_GPU_LOG [Section titled “BRINE_GPU_LOG”](#brine_gpu_log) Any value other than empty or `0` prints a line to stderr each time a `par for`, `par [...]` or `par` reduction runs: its location, kind, size, where it ran, how long it took, and why. ```sh brine build --release gpu.br BRINE_GPU_LOG=1 ./gpu ``` ```text brine gpu: gpu.br:3:14 par map n=10000000 -> cpu 5.035 ms (faster on the CPU) brine gpu: gpu.br:4:17 par reduction n=10000000 -> cpu 1.218 ms (faster on the CPU) 8.333332083333376e+19 ``` With `BRINE_GPU=off` the reason reads `(BRINE_GPU=off)`; a body that can’t run on a GPU says why (`not GPU-eligible: ...`). ### BRINE_LEAKCHECK [Section titled “BRINE_LEAKCHECK”](#brine_leakcheck) When set (to any value, even `0`), the program prints the number of strings and arrays still allocated when it exits. It should always be 0. See [Debugging](/tooling/debugging/#leak-checking). ```text brine: 0 live objects at exit ``` ### BRINE_RANK, BRINE_WORLD_SIZE, BRINE_PEERS [Section titled “BRINE_RANK, BRINE_WORLD_SIZE, BRINE_PEERS”](#brine_rank-brine_world_size-brine_peers) These make a program one rank of a multi-process [`world`](/parallelism/world/) run. `brine run --ranks N` sets all three for each process it starts, with every rank on `127.0.0.1`. To run across machines, start the same executable on each machine and set them yourself: | Variable | Value | | ------------------ | -------------------------------------------------------------------------------------------------- | | `BRINE_RANK` | this process’s rank, from `0` to `BRINE_WORLD_SIZE - 1` | | `BRINE_WORLD_SIZE` | the number of ranks | | `BRINE_PEERS` | `host:port` for every rank, in rank order, separated by commas; each rank listens on its own entry | ```sh # on host a BRINE_RANK=0 BRINE_WORLD_SIZE=2 BRINE_PEERS=10.0.0.1:7000,10.0.0.2:7000 ./sim # on host b BRINE_RANK=1 BRINE_WORLD_SIZE=2 BRINE_PEERS=10.0.0.1:7000,10.0.0.2:7000 ./sim ``` All three must be set; otherwise, or when `BRINE_WORLD_SIZE` is 1 or less, the program runs as a single rank (`world.size() == 1`). An entry without a `:` stops the program with `brine: invalid BRINE_PEERS entry '...'`. A [WebAssembly](/tooling/webassembly/#one-world-rank) build runs as one rank: with `BRINE_WORLD_SIZE` above 1 it stops with ``brine: `world` programs can't run with more than one rank on WebAssembly``. When every peer is `127.x.x.x` or `localhost`, ranks listen on loopback only; otherwise they listen on every interface. A `brine launch --hosts` command that does this for you is on the [roadmap](/reference/roadmap/). ### BRINE [Section titled “BRINE”](#brine) `brine test` sets `BRINE` to the path of the `brine` executable running the tests, so a test can run the compiler (for example with `run([getenv("BRINE").unwrap(), "check", "x.br"])`). ## Building [Section titled “Building”](#building) ### BRINE_BACKEND [Section titled “BRINE_BACKEND”](#brine_backend) Picks the code generator, overriding the [default for the profile and platform](/tooling/builds-and-backends/#backends). | Value | Effect | | -------- | --------------------------------------------------------------------------------------------------------------------- | | `c` | generate C and compile it with the system C compiler, for every profile | | `native` | use the native backend (x86-64 or AArch64) for every profile, `--release` included, where there is one; elsewhere C | | unset | native for dev and debug builds on x86-64 Windows/Linux and AArch64 Linux; C for `--release` and every other platform | A build with `BRINE_BACKEND` set goes to its own folder (`target/dev-c/`, `target/release-native/`, …), so it never replaces a default build’s executable: ```sh BRINE_BACKEND=c brine build ``` ```text Built hello v0.1.0 [dev] in 0.27s Output target/dev-c/hello.exe ``` It’s meant for comparing backends and for debugging the compiler. `BRINE_BACKEND=native` with `--release` gives unoptimized native code, not a faster build. ### BRINE_CC and CC [Section titled “BRINE_CC and CC”](#brine_cc-and-cc) The C compiler Brine uses to compile the runtime, to link, and for every C build. Brine checks `BRINE_CC`, then `CC`, then tries `clang`, `gcc` and `cc` on your `PATH`, in that order, and takes the first that runs. Empty values are skipped. ```sh BRINE_CC=clang brine build --release ``` The value is one program (a name on `PATH` or a full path), not a command line: extra flags in it aren’t split off. `brine doctor` shows which compiler was found. Without one, builds fail with [E0702](/reference/errors/#e0702) (`brine check` doesn’t need one). ### BRINE_TIMES [Section titled “BRINE_TIMES”](#brine_times) Prints how long each phase of the compiler takes, to stderr. ```sh BRINE_TIMES=1 brine build sum.br ``` ```text parse 0.000s check 0.000s uniform 0.000s verify 0.001s lower 0.000s x86-64 0.000s link 0.084s Built sum [dev] in 0.10s Output sum.exe ``` C builds show `emit C` and `cc (compiled/total units)` instead of `x86-64`. With `BRINE_TIMES=2`, Brine also names every function the prover spends more than 20 ms on, which tells you where a `requires` or `invariant` would speed up checking. The end of that output when checking the compiler’s own source: ```text verify alloc_regs: 0.050s verify forward: 0.027s verify write_elf: 0.027s verify 0.158s Checked main in 0.38s — 1003 runtime checks proven unnecessary, 1633 remain (see --report) ``` ### BRINE_DUMP_PTX [Section titled “BRINE_DUMP_PTX”](#brine_dump_ptx) `BRINE_DUMP_PTX=file` writes the program’s GPU module to `file` while building: PTX for NVIDIA GPUs, or Metal Shading Language source on macOS. ```sh BRINE_DUMP_PTX=kernels.ptx brine build --release ``` kernels.ptx ```text // generated by brine .version 6.0 .target sm_50 .address_size 64 ``` ### WebAssembly [Section titled “WebAssembly”](#webassembly) These two are read when you build or run with [`--target wasm` or `--target web`](/tooling/webassembly/). #### WASI_SDK_PATH [Section titled “WASI_SDK_PATH”](#wasi_sdk_path) The folder of [wasi-sdk](https://github.com/WebAssembly/wasi-sdk/releases), whose clang and C library Brine uses for WebAssembly builds. Brine looks for `$WASI_SDK_PATH/bin/clang`, then `/opt/wasi-sdk/bin/clang`. An empty value is skipped. ```sh WASI_SDK_PATH=~/tools/wasi-sdk-34.0-x86_64-linux brine build --target web ``` Without it, WebAssembly builds fail with [E0702](/reference/errors/#e0702) (`no WebAssembly C toolchain found`). Native builds don’t use it. `brine doctor` shows the clang it found. #### BRINE_WASM_RUNNER [Section titled “BRINE_WASM_RUNNER”](#brine_wasm_runner) The command `brine run --target wasm` and `brine test --target wasm` run a module with. Brine splits the value at spaces, then adds the module’s path and the program’s arguments. Unset or empty, Brine uses Node.js with a small `node:wasi` script. ```sh BRINE_WASM_RUNNER="wasmtime run --dir=." brine run --target wasm ``` Give the runtime access to the current folder (`--dir=.` for wasmtime) if the program uses files. A command that can’t start fails with `error: could not start : program not found`. ### NO_COLOR [Section titled “NO_COLOR”](#no_color) When `NO_COLOR` is set, `--color auto` (the default) prints no colors. `--color always` still does. ### TMPDIR [Section titled “TMPDIR”](#tmpdir) On Linux and macOS, the temp directory for single-file builds (`$TMPDIR/brine-cache`, else `/tmp/brine-cache`) and for `os.temp_dir()` in programs. On Windows, Brine uses the system temp folder (`GetTempPath2W`, usually `%TEMP%`). ## Installer variables [Section titled “Installer variables”](#installer-variables) The one-line installers (`install.sh` and `install.ps1`) read: | Variable | Default | Effect | | ---------------------- | ------------------ | ---------------------------------------------------------------------------------------------- | | `BRINE_VERSION` | the latest release | the version to install, like `0.4.0` | | `BRINE_INSTALL` | `~/.brine` | where to install; the binary goes in `$BRINE_INSTALL/bin` | | `BRINE_NO_MODIFY_PATH` | unset | when set, don’t add Brine to `PATH` (in your shell’s startup file, or the Windows user `PATH`) | | `BRINE_DOWNLOAD_URL` | GitHub releases | where the release archives and `SHA256SUMS` are, for mirrors | ```sh curl -fsSL https://raw.githubusercontent.com/brinelang/brine/main/install.sh | BRINE_VERSION=0.4.0 sh ``` ```powershell $env:BRINE_VERSION = '0.4.0'; irm https://raw.githubusercontent.com/brinelang/brine/main/install.ps1 | iex ``` ## Programs and the compiler [Section titled “Programs and the compiler”](#programs-and-the-compiler) The `brine` compiler is written in Brine, so it’s a Brine program like any other, and the program variables apply to it too. `BRINE_THREADS` limits the threads the compiler uses for parsing and proving. `BRINE_LEAKCHECK` and `BRINE_GPU_LOG` make `brine run` print the compiler’s own lines as well as your program’s: ```sh BRINE_LEAKCHECK=1 brine run en.br ``` ```text Built en [dev] in 0.10s Running C:/Users/you/AppData/Local/Temp/brine-cache/7477652331a79471/dev/en.exe Shape.Rect(w=2, h=3) Some(1) {"ann": 31, "bob": 27} (1, "two") brine: 0 live objects at exit brine: 0 live objects at exit ``` The first count is your program’s, the second the compiler’s. To see only your program’s, build it, then run the executable with the variable set. ## Related [Section titled “Related”](#related) * [The brine command](/tooling/cli/) * [Builds, backends and performance](/tooling/builds-and-backends/) * [WebAssembly](/tooling/webassembly/) * [GPUs](/parallelism/gpu/) * [Installation](/getting-started/installation/) # Formatting > What brine fmt changes and what it leaves alone, brine fmt --check, and checking formatting in CI. `brine fmt` rewrites source files in the one canonical Brine layout: 4-space indentation, one space around binary operators and after commas, at most one blank line in a row, and lines of at most 100 characters. It only moves whitespace: it never adds, removes or reorders a token, so a formatted program means exactly what it meant before. Run it before every commit; `brine fmt --check` fails a CI job when a file isn’t formatted. ## Usage [Section titled “Usage”](#usage) ```sh brine fmt # every .br file in the project brine fmt src/main.br lib/ # these files, and every .br file in these folders brine fmt --check # change nothing; list unformatted files, exit 1 if any ``` ```text Formatted messy.br 1 file checked, 1 reformatted ``` Without arguments, `brine fmt` formats every `.br` file under the project root (the folder with `brine.toml`), or under the current directory outside a project. It skips `target/` and every folder whose name starts with `.`. There are no options to configure. Every Brine file in every project looks the same, which is one less thing for people and agents to decide. ## Before and after [Section titled “Before and after”](#before-and-after) messy.br (before) ```brine fn area(w:float,h :float)->float : return w*h # width times height struct Point : x:int y:int fn main(): let xs=[1,2,3,] var total=0 for i in 0..len(xs): total+=xs[i] if total>5 and not(total==7): print( "big",total ) let p=Point(x=1,y=2) print(area(2.0,3.0),p) ``` ```sh brine fmt messy.br ``` messy.br (after) ```brine fn area(w: float, h: float) -> float: return w * h # width times height struct Point: x: int y: int fn main(): let xs = [1, 2, 3,] var total = 0 for i in 0..len(xs): total += xs[i] if total > 5 and not (total == 7): print("big", total) let p = Point(x=1, y=2) print(area(2.0, 3.0), p) ``` Both versions compile and print the same thing. Note the trailing comma in `[1, 2, 3,]`: it’s a token, so `brine fmt` keeps it. ## What it changes [Section titled “What it changes”](#what-it-changes) | Rule | Before | After | | -------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ----------------------------- | | Indentation is 4 spaces per block, whatever the input used | `return w*h` | `return w * h` | | Tabs in indentation become 4 spaces | a tab | 4 spaces | | One space around binary and assignment operators | `total+=xs[i]` | `total += xs[i]` | | One space after `,` and after `:` in annotations; none before | `fn area(w:float,h :float)` | `fn area(w: float, h: float)` | | `->` has a space on each side; no space before a block’s `:` | `->float :` | `-> float:` | | No space inside brackets, or between a name and its call or index | `print( "big",total )` | `print("big", total)` | | No spaces around `=` in keyword arguments | `Point(x = 1)` | `Point(x=1)` | | No spaces around `:` and `..` in slices and ranges | `xs[0 : 2]`, `0 .. n` | `xs[0:2]`, `0..n` | | Keywords are separated from what follows | `not(total==7)` | `not (total == 7)` | | At most one blank line in a row | three blank lines | one | | No blank line right after a line ending in `:` | | | | One blank line before each top-level `fn`, `struct`, `enum` and `test`, and after a top-level block ends | `struct Point:` right before `fn main():` | a blank line between them | | Two spaces before a trailing comment | `w*h # width` | `w * h # width` | | No trailing whitespace; the file ends with exactly one newline; `\r\n` becomes `\n` | | | | An expression spread over several lines inside brackets is joined into one line | `sum(` / `x * x` / `for x in xs` / `)` | `sum(x * x for x in xs)` | | A line longer than 100 characters is split at its first bracket, one element per line | see below | | A long call: long.br (after) ```brine fn describe(name: str, age: int, city: str, country: str, email: str, phone: str) -> str: return f"{name} {age} {city} {country} {email} {phone}" fn main(): print( describe("Ada Lovelace", 36, "London", "United Kingdom", "ada@example.com", "+44 20 7946 0000") ) ``` The line was split at the `(` of `print`; each element of that bracket goes on its own line, indented one level, and the closing bracket returns to the original indentation. Only the first bracket is split, so a very long single element (here the `describe(...)` call) can still be over 100 characters. ## What it keeps [Section titled “What it keeps”](#what-it-keeps) * **Comments**, exactly as written (only their indentation and the spacing before a trailing comment change). A comment on its own line takes the indentation of the code below it, unless it is indented deeper (then it stays with the end of the block above). * **Literal spellings**: `0xFF`, `1_000_000`, `1e-9`, string escapes, f-string contents and format specs. * **Every token**: trailing commas, redundant parentheses and the order of everything stay. * **Lines that have a comment in the middle**: a bracketed list spread over lines with a comment after an element keeps its layout; only its indentation changes. This is the way to keep a hand-made layout: ```brine let ys = [ 1, # one 2, ] ``` This file came out of `brine fmt` with its comment, hex literal, underscores and f-string spec untouched: Kept as written ```brine # Tools for points. const LIMIT = 0xFF fn main(): let big = 1_000_000 # keep the underscores let xs = [1, 2, 3] let ys = [x * 2 for x in xs if x > 1] print(xs[0:2], -big, ys, LIMIT) print(f"{big:>12}") ``` ## Safety [Section titled “Safety”](#safety) `brine fmt` works on tokens, not on the syntax tree, so comments and spellings survive. After formatting, it lexes the result again and compares the tokens with the input’s. If they differ in any way (which would be a bug in the formatter), it leaves the file as it was, apart from replacing tabs in indentation. A file with a syntax error isn’t changed. The error is reported and the command exits with `1`: ```text error[E0110]: expected `)`, found end of line --> broken.br:3:1 | 3 | | ^ expected `)` = run `brine explain E0110` for more about this error error: could not compile due to 1 error 1 file checked, 0 reformatted ``` Only syntax is checked: `brine fmt` doesn’t resolve imports or check types, so it formats files that `brine check` would still reject. ## brine fmt –check [Section titled “brine fmt –check”](#brine-fmt-check) `--check` writes nothing. It prints each file that would change and exits with `1` if there is one: ```sh brine fmt --check ``` ```text would reformat messy.br 1 file checked, 1 need formatting ``` With `--json`, each file is a line of its own, and the summary isn’t printed: ```text {"unformatted":"messy.br"} ``` | Result | Exit code | | ------------------------------- | --------- | | everything formatted | `0` | | a file would change (`--check`) | `1` | | a file has a syntax error | `1` | | a file can’t be read or written | `2` | ## In CI [Section titled “In CI”](#in-ci) Add a step that runs `brine fmt --check` after installing Brine. It needs no C compiler, so it can run before anything is built: .github/workflows/ci.yml ```yaml jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install brine run: | curl -fsSL https://raw.githubusercontent.com/brinelang/brine/main/install.sh | sh echo "$HOME/.brine/bin" >> "$GITHUB_PATH" - run: brine fmt --check - run: brine check - run: brine test ``` As a Git pre-commit hook: .git/hooks/pre-commit ```sh #!/bin/sh exec brine fmt --check ``` ## Related [Section titled “Related”](#related) * [The brine command](/tooling/cli/#brine-fmt) * [Projects and brine.toml](/tooling/projects/) # Projects and brine.toml > What brine new and brine init create, every brine.toml key, the target/ folder layout, the build cache, and running single files instead of projects. A Brine project is a folder with a `brine.toml` at its root and its main file at `src/main.br`. Commands run inside the folder (or any folder below it) work on the project without arguments. A single `.br` file works too: every command takes a file instead, and its build goes to a cache folder in your temp directory. ## brine new and brine init [Section titled “brine new and brine init”](#brine-new-and-brine-init) ```sh brine new hello ``` ```text Created project `hello` cd hello brine run ``` ```text hello/ ├── .git/ ├── .gitignore ├── AGENTS.md ├── README.md ├── brine.toml └── src/ └── main.br ``` | File | Contents | | ------------- | ------------------------------------------------------------------------------------------------ | | `brine.toml` | the project’s name and version | | `src/main.br` | a starter program with a `par` comprehension and a test | | `AGENTS.md` | instructions for coding agents: the commands to run and the rules that most often trip people up | | `README.md` | a title and the two commands to build and test | | `.gitignore` | `/target` | `brine new ` makes the folder; `brine init` uses the current one, names the project after it, and keeps any of these files that already exist. Both run `git init` unless the folder is already inside a Git repository (and skip it quietly if `git` isn’t installed). The starter program: src/main.br ```brine # Welcome to brine! Run this program with `brine run`. fn greet(name: str) -> str: return f"Hello, {name}!" fn main(): print(greet("brine")) # parallel by default: this runs across all your cores let squares = par [i * i for i in 0..10] print(squares) test "greeting": assert greet("you") == "Hello, you!" ``` ```sh cd hello brine run ``` ```text Built hello v0.1.0 [dev] in 2.05s Running target/dev/hello.exe Hello, brine! [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] ``` ## brine.toml [Section titled “brine.toml”](#brinetoml) The file `brine new` writes: brine.toml ```toml [package] name = "hello" version = "0.1.0" ``` These are all the keys Brine reads: | Section | Key | Default | Meaning | | ----------- | --------- | --------------- | ----------------------------------------------------------------------------------------------- | | `[package]` | `name` | (required) | the project’s name: the executable is `target//`, and it appears in status lines | | `[package]` | `version` | `"0.1.0"` | shown in status lines (`Built hello v0.1.0`) | | `[build]` | `main` | `"src/main.br"` | the main file, relative to `brine.toml` | | `[check]` | `typed` | `false` | `true` requires a written type on every `let`, `var` and `for` variable | A project that uses all of them: brine.toml ```toml [package] name = "tool" version = "1.2.0" [build] main = "app/cli.br" [check] typed = true ``` ### package [Section titled “package”](#package) `name` is required: ```text error: brine.toml: missing `name` in [package] ``` `version` is free text. Brine doesn’t compare versions yet (there is no package manager; `brine add` is on the [roadmap](/reference/roadmap/)). ### build [Section titled “build”](#build) `main` names the file that holds `fn main`. Modules load relative to the main file’s folder: with `main = "app/cli.br"`, `import util` loads `app/util.br`. ### check [Section titled “check”](#check) `typed = true` makes every variable need a written type, for teams and agents that want types spelled out. It applies to `check`, `build`, `run` and `test`; the `--typed` flag does the same for one command, or for a single file. A missing type is [E0337](/reference/errors/#e0337): ```text error[E0337]: `s` has no written type --> sum.br:2:9 | 2 | var s = 0 | ^ add `: int` | = note: this project asks for a type on every variable (`typed = true` in brine.toml, or `--typed`) = run `brine explain E0337` for more about this error ``` The value must be `true` or `false`: ```text error: brine.toml: `typed` in [check] must be true or false, not `yes` ``` ### Syntax [Section titled “Syntax”](#syntax) Brine reads a small subset of TOML: `[section]` headers and `key = "string"` or `key = value` lines, with `#` comments. Keys it doesn’t know are ignored, so a newer `brine.toml` still works with an older Brine. ## Finding the project [Section titled “Finding the project”](#finding-the-project) Without a file argument, Brine looks for `brine.toml` in the current directory, then in each parent. So `brine run` works from `src/` or any subfolder. A file or directory argument overrides this: | Command | Builds | | ------------------------ | -------------------------------------------------------------------- | | `brine run` | the project around the current directory | | `brine run tools/gen.br` | that one file, as its own program | | `brine run ../other` | the project in `../other` (it has a `brine.toml`) | | `brine run scripts` | `scripts/main.br`, if `scripts/` has a `main.br` and no `brine.toml` | ## The target folder [Section titled “The target folder”](#the-target-folder) Project builds go to `target/`, one folder per profile: ```text target/ ├── cache/ objects and the compiled runtime │ ├── obj-0548db582edbf74a/ │ ├── rt-2fdf516b1c19bd8c/ │ └── ... ├── debug/ │ ├── hello.brine-hash │ └── hello.exe ├── dev/ │ ├── hello.brine-hash │ └── hello.exe └── release/ ├── hello.brine-hash └── hello.exe ``` | Folder | Made by | | ---------------------------------------------------------------------- | ------------------------------------------------ | | `target/dev/` | `brine run`, `brine build` (the default profile) | | `target/release/` | `--release` | | `target/debug/` | `--debug`, `brine debug` | | `target/dev-c/`, `target/release-c/`, `target/debug-c/` | a build with `BRINE_BACKEND=c` | | `target/dev-native/`, `target/release-native/`, `target/debug-native/` | a build with `BRINE_BACKEND=native` | | `target/cache/` | intermediate files for every profile | Test builds sit next to the program as `-tests` (`target/dev/hello-tests.exe`). On Linux and macOS executables have no `.exe`. A build with [`BRINE_BACKEND`](/tooling/environment-variables/) set always gets its own folder, even when it picks the backend the profile would use anyway, so it can never replace an executable a default build is running. ### The cache [Section titled “The cache”](#the-cache) `target/cache/` holds: * `rt-/`: the runtime (`brine_rt.c`, `brine_rt.h`) and its object file, compiled once per runtime version, C compiler and flags. This is why the first build in a new project takes about two seconds and later ones a fraction of one. * `obj-/`: the object files of one output, each named by a hash of what it was made from. An edit recompiles only the objects whose input changed. Old objects are pruned once there are many more than the build needs. Next to each executable, `.brine-hash` records a hash of the objects and runtime it was linked from. When a build produces the same hash and the executable exists, Brine skips the link and prints `Fresh` instead of `Built`. Deleting `target/` is always safe: the next build makes it again. ## Single files [Section titled “Single files”](#single-files) Every command takes a `.br` file instead of a project. Imports load next to that file. ```sh brine run panic.br brine check sum.br brine test t.br brine build tool.br # writes ./tool.exe (./tool on Linux and macOS) ``` `brine build` writes a single file’s executable in the current directory, the way a C compiler does. `brine run`, `brine test` and `brine debug` put theirs in a cache folder under your temp directory: ```text /brine-cache/ ├── 465ef71a47ea2199/ one folder per source file (a hash of its absolute path) │ ├── dev/argv.exe │ └── debug/argv.exe ├── obj-.../ object files, shared by all single-file builds └── rt-.../ the compiled runtime ``` `` is `%TEMP%` on Windows (`GetTempPath2W`) and `$TMPDIR` (else `/tmp`) on Linux and macOS. Nothing removes old folders from `brine-cache`; delete it whenever you like. | | Project | Single file | | ----------------------------- | ------------------------------- | -------------------------------------------- | | main file | `src/main.br` or `[build] main` | the file you pass | | executable name | `[package] name` | the file name without `.br` | | `brine build` output | `target//` | `./` | | `brine run` output | `target//` | `/brine-cache///` | | cache | `target/cache/` | `/brine-cache/` | | `[check] typed` | from `brine.toml` | only with `--typed` | | `brine fmt` without arguments | every `.br` file in the project | every `.br` file under the current directory | ## Related [Section titled “Related”](#related) * [The brine command](/tooling/cli/) * [Builds, backends and performance](/tooling/builds-and-backends/) * [Modules](/language/modules/) # WebAssembly > Compile Brine programs to WebAssembly with --target wasm (a WASI module, run with Node.js or any WASI runtime) and --target web (a folder a browser opens), set up wasi-sdk, use the brine.js loader, and know the limits. Brine programs compile to WebAssembly as well as to native executables, with the same checks and the same results. `--target wasm` makes a `.wasm` module for WASI runtimes, and `brine run` and `brine test` run it with Node.js. `--target web` makes a folder a browser opens: a page, a small loader (`brine.js`) and the module. Both need [wasi-sdk](#setup). WebAssembly runs on one thread, so `par` constructs run in order (with the same result bits), and there are no processes, sockets or GPU. ```sh brine run --target wasm collatz.br # build a .wasm module and run it with Node.js brine build --target wasm collatz.br # collatz.wasm, for any WASI runtime brine build --target web collatz.br -o site # site/: index.html, brine.js, program.wasm ``` ## Setup [Section titled “Setup”](#setup) WebAssembly builds compile the generated C with the clang and C library of [wasi-sdk](https://github.com/WebAssembly/wasi-sdk/releases), not with your system C compiler. Download the archive for your platform, unpack it, and set `WASI_SDK_PATH` to its folder: ```sh export WASI_SDK_PATH=~/tools/wasi-sdk-34.0-x86_64-linux ``` Brine looks for `$WASI_SDK_PATH/bin/clang`, then for `/opt/wasi-sdk/bin/clang`, so unpacking it to `/opt/wasi-sdk` also works. Without either, a WebAssembly build stops with [E0702](/reference/errors/#e0702): ```text error[E0702]: no WebAssembly C toolchain found = note: brine builds WebAssembly with the clang and C library of wasi-sdk = help: download wasi-sdk from https://github.com/WebAssembly/wasi-sdk/releases and set WASI_SDK_PATH to its folder = run `brine explain E0702` for more about this error error: could not compile due to 1 error ``` To run modules, `brine run --target wasm` and `brine test --target wasm` also need [Node.js](https://nodejs.org) on your `PATH` (Brine’s CI tests with Node 24), or another WASI runtime in [`BRINE_WASM_RUNNER`](#other-wasi-runtimes). `brine doctor` shows what it found on its last line: ```text ✓ WebAssembly C:/Users/you/tools/wasi-sdk-34.0-x86_64-windows\bin\clang.exe ``` Without wasi-sdk it reads: ```text - WebAssembly no wasi-sdk (optional, for `--target wasm`; set WASI_SDK_PATH) ``` WebAssembly is optional, so a missing wasi-sdk doesn’t make `brine doctor` fail. ## Run a program with –target wasm [Section titled “Run a program with –target wasm”](#run-a-program-with-target-wasm) This program uses `par` for a map and two reductions: collatz.br ```brine fn collatz_len(n: int) -> int: var x = n var steps = 0 while x != 1: x = x / 2 if x % 2 == 0 else 3 * x + 1 steps += 1 return steps fn main(): let a = args() let n = int(a[0]) if len(a) > 0 else 1000 let lens = par [collatz_len(i) for i in 1..=n] let total = par sum(float(l) for l in lens) let longest = par max(l for l in lens) print(f"{os.name()} {os.arch()}, n = {n}") print(f"average steps: {total / float(n):.2f}, longest: {longest}") ``` ```sh brine run --target wasm collatz.br -- 1000000 ``` ```text Built collatz [dev, wasm] in 0.15s Running C:/Users/you/AppData/Local/Temp/brine-cache/07c4ee958925d33a/dev-wasm/collatz.wasm wasi wasm32, n = 1000000 average steps: 131.43, longest: 524 ``` The native build prints the same numbers (`windows x86_64, n = 1000000` on the first line). `os.name()` is `"wasi"` and `os.arch()` is `"wasm32"` in a WebAssembly build. `brine run` runs the module with a small script around Node’s `node:wasi` module. The program gets its arguments and the environment, and the current folder is opened for it, so relative paths (`read_file("data.txt")`, `write_file("out.txt", ...)`) work as they do natively. Nothing outside the current folder is reachable: `read_file("/etc/hosts")` returns `Err("cannot read /etc/hosts: No such file or directory")`. The exit code is the program’s, `101` after a panic, as for a [native run](/tooling/cli/#exit-code): ```text Built panic [dev, wasm] in 0.11s Running C:/Users/you/AppData/Local/Temp/brine-cache/b2e1b9ea98c70c0b/dev-wasm/panic.wasm panic: index 5 is out of bounds for an array of length 3 --> panic.br:4:11 ``` A crash of the module itself (a WebAssembly trap) prints `WebAssembly trap: ...` and exits with `134`. See [Deep recursion](#deep-recursion) for the common case. ### Tests [Section titled “Tests”](#tests) `brine test --target wasm` builds the tests as one module and runs each test in its own Node.js process, as a native `brine test` does with executables. The output is the same: ```text Built tests [dev, wasm] in 0.15s test double doubles ... ok test double of zero ... FAILED ---- double of zero ---- panic: assertion failed: double(0) == 1 --> t.br:8:5 test result: FAILED. 1 passed; 1 failed; finished in 0.04s ``` ### Other WASI runtimes [Section titled “Other WASI runtimes”](#other-wasi-runtimes) `brine build --target wasm` writes a module for WASI preview 1 (`wasm32-wasip1`), so any WASI runtime can run it. To have `brine run` and `brine test` use one instead of Node.js, set `BRINE_WASM_RUNNER` to its command; Brine adds the module’s path and the program’s arguments after it: ```sh BRINE_WASM_RUNNER="wasmtime run --dir=." brine run --target wasm collatz.br -- 1000000 ``` The value is split at spaces (no quoting). Give the runtime access to the current folder (`--dir=.` for wasmtime) if the program reads or writes files. Without Node.js and without `BRINE_WASM_RUNNER`, `brine run --target wasm` builds the module and then stops: ```text error: running WebAssembly needs Node.js (https://nodejs.org) or a WASI runtime in BRINE_WASM_RUNNER (say `wasmtime run --dir=.`) ``` ## Build for the browser with –target web [Section titled “Build for the browser with –target web”](#build-for-the-browser-with-target-web) ```sh brine build --target web collatz.br -o site ``` ```text Built collatz [dev, web] in 0.15s Output site Open its index.html over HTTP, e.g. `python3 -m http.server -d site` and http://localhost:8000 ``` The output is a folder you can put on any static host: | File | What it is | | -------------- | --------------------------------------------------------------------------------------------------------- | | `program.wasm` | the program | | `brine.js` | the loader: the WASI calls a Brine program makes, for the browser (about 6 KB) | | `index.html` | a starter page that runs the program and shows what it prints, stderr in red, with a **Run again** button | Serve the folder over HTTP: browsers don’t load WebAssembly from `file://` pages. ```sh python3 -m http.server -d site # then open http://localhost:8000 ``` Where the folder goes: | Build | Output | | -------------------- | --------------------------------------------------------------- | | project, no `-o` | `target/-web/-web/` (`target/dev-web/demo-web/`) | | single file, no `-o` | `-web/` in the current directory (`collatz-web/`) | | `-o path` | `path/` | ### Rebuilding keeps your page [Section titled “Rebuilding keeps your page”](#rebuilding-keeps-your-page) `index.html` is a starting point. Replace it with your own page: a rebuild writes `program.wasm` and `brine.js` again but leaves an existing `index.html` alone. Other files you add to the folder (scripts, styles, images) are left alone too. The build’s change stamp lives in the build cache, not in the folder, so the folder holds only what you deploy. ### The run function [Section titled “The run function”](#the-run-function) `brine.js` exports one function: ```js import { run } from "./brine.js"; const code = await run("program.wasm", { args: ["1000000"], env: { MODE: "fast" }, stdout: (text) => console.log(text), stderr: (text) => console.error(text), }); ``` | Argument | Meaning | | ---------------- | ------------------------------------------------------------------------------------------------ | | `wasm` | the module: a URL (string or `URL`), a `Response`, the bytes, or a compiled `WebAssembly.Module` | | `options.args` | the program’s arguments, what `args()` returns (default: none) | | `options.env` | an object of environment variables, what `getenv` sees (default: none) | | `options.stdout` | called with text the program prints (default: `console.log`) | | `options.stderr` | called with text the program writes to stderr, panics included (default: `console.error`) | `run` resolves to the exit code: `0` when `main` returns normally, the code the program exits with (`exit(code)`, or the value an `int` `main` returns), `1` when `main` returns `Err`, and `101` after a panic. The panic message arrives through `stderr`. If the module traps (for example, it [runs out of stack](#deep-recursion)), the promise rejects with the engine’s error instead. Output arrives as text whenever the program flushes it, so one call can hold several lines, or part of one. Append it to what you have; don’t treat each call as a line. To run a program more than once, compile it once and pass the `WebAssembly.Module`. Each `run` starts a fresh instance: ```js const module = await WebAssembly.compileStreaming(fetch("program.wasm")); await run(module, { args: ["10"] }); await run(module, { args: ["20"] }); ``` What the loader provides, and what it doesn’t: * **No files.** No folder is opened for the program, so opening any file fails: `read_file("data.txt")` returns `Err("cannot read data.txt: No such file or directory")`, `write_file` fails the same way, and `file_exists` is `false`. Pass input through `args` or `env` instead. * **Empty standard input.** Reading stdin gives end-of-file at once. * **`os.sleep` spins.** The loader has no way to pause a thread, so a sleep busy-waits until its time is up. It keeps the CPU busy and, on the main thread, freezes the page. * **Clocks** come from the browser’s `performance.now()` and `Date.now()`. ### Run it in a Web Worker [Section titled “Run it in a Web Worker”](#run-it-in-a-web-worker) `run` calls the module’s `_start` and returns only when the program has finished. Until then, the thread it runs on does nothing else. On a page’s main thread, that means no clicks, no scrolling and no repaints: in a test, a 10 ms timer didn’t fire once during a 0.5 s run. Anything that takes more than a moment, or sleeps, belongs in a Web Worker. A worker that runs the program once per message and sends its output back: site/worker.js ```js import { run } from "./brine.js"; // Each message runs the program once: postMessage({ args: [...] }) self.onmessage = async (e) => { try { const code = await run("program.wasm", { args: e.data.args, stdout: (text) => self.postMessage({ stdout: text }), stderr: (text) => self.postMessage({ stderr: text }), }); self.postMessage({ exit: code }); } catch (err) { // a trap, such as running out of stack self.postMessage({ error: String(err) }); } }; ``` A page that starts it. Save it as the folder’s `index.html`; rebuilds keep it: site/index.html ```html


```
 With `collatz.br` built into `site/` and the folder served over HTTP, the page shows: Output (Chrome) 
```text
wasi wasm32, n = 1000000
average steps: 131.43, longest: 524
[exit code 0]
```
 The worker is a module worker (`type: "module"`), because `brine.js` is an ES module. `"program.wasm"` resolves against the worker script’s URL, so keep `worker.js` in the same folder. ## Limits [Section titled “Limits”](#limits) The same source compiles for WebAssembly with no changes. Some things the platform doesn’t have, and each fails with a message that says so. ### One thread [Section titled “One thread”](#one-thread) WebAssembly builds run on one thread. `par for`, `par [...]`, `par` reductions and `par(...)` forks run in order on that thread. Because [reductions combine in a fixed order](/parallelism/deterministic-reductions/) that doesn’t depend on the thread count, the results are the same bits as a native run on any number of cores. Only the speed differs. `os.cpu_count()` returns `1`. `--threads` and [`BRINE_THREADS`](/tooling/environment-variables/#brine_threads) have no effect. ### No GPU [Section titled “No GPU”](#no-gpu) WebAssembly builds contain no GPU code, so every `par` construct runs on the CPU. With [`BRINE_GPU_LOG=1`](/tooling/environment-variables/#brine_gpu_log) the reason is printed: 
```text
brine gpu: gpu.br:3:14 par map n=1000000 -> cpu 0.734 ms (not GPU-eligible: WebAssembly has no GPU)
brine gpu: gpu.br:4:17 par reduction n=1000000 -> cpu 1.282 ms (not GPU-eligible: WebAssembly has no GPU)
```
 ### One world rank [Section titled “One world rank”](#one-world-rank) A [`world`](/parallelism/world/) program runs as a single rank: `world.size()` is `1`, and collectives such as `world.sum` return the rank’s own value. `--ranks` is refused: 
```sh
brine run --target wasm --ranks 2 world.br
```
 
```text
error: `--ranks` needs native processes; WebAssembly programs run as one rank
```
 A module started with `BRINE_WORLD_SIZE` above 1 stops with exit code `2`: 
```text
brine: `world` programs can't run with more than one rank on WebAssembly
```
 ### No processes or sockets [Section titled “No processes or sockets”](#no-processes-or-sockets) `run`, `os.spawn` and `os.free_ports` return `Err` values, which the program can handle like any other failure: 
```text
Err("cannot run git: WebAssembly programs can't start processes")
Err("WebAssembly programs have no network ports")
```
 `os.pid()` returns `1`. ### Files [Section titled “Files”](#files) Under `brine run --target wasm` (Node.js), the program can read and write files in the current folder and below, and nothing else. In the browser, [there are no files](#the-run-function). ### Deep recursion [Section titled “Deep recursion”](#deep-recursion) The engine that runs WebAssembly (V8, in Node.js and Chrome) limits the call stack, and that limit is reached long before a native program’s. This function, called with 15,000 levels of recursion, traps under Node.js 24 where a native build doesn’t: deep.br 
```brine
fn depth(n: int) -> int:
    if n == 0:
        return 0
    return 1 + depth(n - 1)


fn main():
    print(depth(int(args()[0])))
```
 
```text
     Built deep [dev, wasm] in 0.11s
   Running C:/Users/you/AppData/Local/Temp/brine-cache/b3a8a1c856a7f000/dev-wasm/deep.wasm


WebAssembly trap: Maximum call stack size exceeded
```
 The exit code is `134`. In the browser, `run` rejects with `RangeError: Maximum call stack size exceeded`. Rewrite deep recursion as a loop if it must run on WebAssembly. ### Commands [Section titled “Commands”](#commands) | Command | Error | | -------------------------- | --------------------------------------------------------------------------------------------------- | | `brine run --target web` | ``a web build runs in a browser: build it with `brine build --target web` and open its index.html`` | | `brine test --target web` | ``tests run with `--target wasm` (Node.js), not in a browser`` | | `brine build --target arm` | ``unknown target `arm` (use host, wasm or web)`` | `brine debug` has no `--target`: debugging is native only. Each of these exits with `2`. ## Profiles and size [Section titled “Profiles and size”](#profiles-and-size) WebAssembly builds always go through the [C backend](/tooling/builds-and-backends/#backends): the native x86-64 and AArch64 backends write machine code, not WebAssembly. The profiles keep their C flags: | Profile | Command | C compile | Output in a project | | ------- | ------------------------- | -------------------------------------------- | --------------------------------- | | dev | `--target wasm` | `-O2`, split into units compiled in parallel | `target/dev-wasm/.wasm` | | release | `--target wasm --release` | `-O3`, one unit | `target/release-wasm/.wasm` | | debug | `--target wasm --debug` | `-O0 -g` | `target/debug-wasm/.wasm` | `--target web` uses the same flags, with `-web` folders (`target/release-web/`). A single file without `-o` builds `.wasm` in the current directory. With `--emit-c`, the generated C is written next to the output, as for a native build. Because every profile goes through C, a dev build for WebAssembly is slower to build than a native dev build on x86-64 or AArch64, but still takes a fraction of a second for a small program once the runtime is cached. A module carries the Brine runtime, so even a program that only prints `hi` is about 400 KB (about 130 KB with gzip, which static hosts usually apply). `collatz.br` is 428 KB in a dev build and 429 KB in a release build: release makes the code faster, not smaller. A debug build is about 640 KB. ## How it works [Section titled “How it works”](#how-it-works) * **Through C and wasi-sdk.** Brine generates the same C it generates for a native C build and compiles it, and the runtime (`brine_rt.c`), with wasi-sdk’s clang for `wasm32-wasip1`. The module gets an 8 MB stack for Brine code, like a native main thread. Everything before code generation is unchanged: the type checker and the prover see the same program, so a program that checks natively checks for WebAssembly. * **The 64-bit memory layout on wasm32.** wasm32 has 4-byte pointers, but Brine’s generated code and runtime lay memory out as on a 64-bit machine. Each pointer, in a struct field or an array element, takes an 8-byte slot, with the pointer in its low half. So the IR, the prover and the native backends don’t change, and on 64-bit platforms the code compiles exactly as before. `int` is 64 bits everywhere, and `i128`/`u128` work as they do natively. * **One thread in the runtime.** Locks do nothing, worker threads are never started, and the thread pool has one thread, so every `par` construct runs on the calling thread. * **Two loaders.** `brine run` uses a short Node.js script around `node:wasi`, written to the build cache. `--target web` uses `brine.js`, which implements the WASI calls a Brine program makes for the browser. Calls it doesn’t implement (files, sockets) return an error code, which the program sees as an `Err`. Threads on WebAssembly (wasi-threads and Web Workers, so `par` runs in parallel there too), files in the browser, and a playground page that runs the compiler itself in the browser are [planned](/reference/roadmap/#webassembly). ## Summary [Section titled “Summary”](#summary) * `--target wasm` builds a WASI module; `brine run` and `brine test` run it with Node.js, or with `BRINE_WASM_RUNNER`. * `--target web` builds a folder with `index.html`, `brine.js` and `program.wasm`. Rebuilds keep your `index.html`. * Both need wasi-sdk: set `WASI_SDK_PATH`. * `run(wasm, { args, env, stdout, stderr })` resolves to the exit code (`101` after a panic). Run long programs in a Web Worker. * One thread (`par` runs in order, same bits), one `world` rank, no processes, sockets or GPU, and no files in the browser. ## Related [Section titled “Related”](#related) * [The brine command](/tooling/cli/) * [Builds, backends and performance](/tooling/builds-and-backends/) * [Environment variables](/tooling/environment-variables/#webassembly) * [Platforms](/reference/platforms/#webassembly) * [Deterministic reductions](/parallelism/deterministic-reductions/)

# Changelog

> Every notable change in each Brine release, from 0.1.0 to 0.4.0.

Every notable change to Brine, newest first. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and Brine uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html): until 1.0, a minor release may change the language (see [Versions](/reference/roadmap/#versions)). Downloads, checksums and the same notes are on the [GitHub releases page](https://github.com/brinelang/brine/releases). ## 0.4.0 [Section titled “0.4.0”](#040) Released 2026-10-27. [Release](https://github.com/brinelang/brine/releases/tag/v0.4.0) · [Changes since 0.3.0](https://github.com/brinelang/brine/compare/v0.3.0...v0.4.0) ### Added [Section titled “Added”](#added) * `par for`, `par [...]` maps and `par` reductions run on the GPU when that’s faster: NVIDIA GPUs through CUDA (Windows and Linux) and Apple GPUs (M1 and later) through Metal. Bodies that only compute qualify (arithmetic, comparisons, branches, loops, reads of plain data and arrays, calls to such functions), and nothing changes in the source. Results are bit-identical to the CPU’s; a check that fails on the device reruns the construct on the CPU, which panics as usual. The GPU driver is loaded when the program runs, so there’s nothing to install or link. `BRINE_GPU=off|on|auto` picks the policy and `BRINE_GPU_LOG=1` prints where each construct ran and why. See [GPUs](/parallelism/gpu/). * A native AArch64 backend for Linux: dev and debug builds on 64-bit ARM Linux go straight to machine code, as on x86-64. * Native debug builds: `--debug` on Windows and Linux carries DWARF line and variable info, so gdb and lldb break at `file.br:line` or a Brine function, step through Brine lines and show locals by their Brine names and types. See [Debugging](/tooling/debugging/). * `brine debug` shows values the way Brine prints them (`"hello"`, `[1, 2, 3]`, `Some(5)`) when gdb has Python, and steps over the runtime. * Release builds on GitHub for Linux (x64, arm64), macOS (arm64, x64) and Windows (x64), with checksums and build provenance, and one-line installers: `curl -fsSL https://raw.githubusercontent.com/brinelang/brine/main/install.sh | sh` and `irm https://raw.githubusercontent.com/brinelang/brine/main/install.ps1 | iex`. * `examples/find_signal.br`: a GPS signal search whose indexing is proved safe and whose search runs on the GPU. ### Changed [Section titled “Changed”](#changed) * **Breaking:** `par` reductions combine their elements in a fixed order: leaves of 16 consecutive elements, each folded left to right, then a balanced tree. The result is the same for any thread count and on the GPU. `par sum` and `par product` of floats can differ from 0.3.0 in the last bits (usually closer to the exact result), and whether an integer `par sum` or `par product` overflows is decided by the new order. Programs that need the old bits must sum sequentially. * Faster native code: local variables and floats live in registers (dev builds of nbody go from 7.96x C’s time to 3.45x, spectral norm from 6.54x to 2.05x). * Loops that index a local array load it once and make it unique before the loop, so C compilers vectorize them (matrix multiply: 8.7x C’s time to 1.0x). * Faster strings: `s += t` grows a string in place and `s[i]` is inlined. * Faster allocation: small blocks come from Brine’s own allocator, and a value with one reference is freed without an atomic instruction (binary-trees on Windows: 1.67x C’s time to 0.67x). * Parallel regions start and end without a lock, so short regions no longer slow down as threads are added. * Division and remainder by a constant power of two compile to a shift and a mask. * `match` on a `let` variable or a by-value parameter no longer retains and releases each value it visits. ### Fixed [Section titled “Fixed”](#fixed) * Programs failed to link on AArch64 Linux when clang was the C compiler (missing `__muloti4`). * Float `min` and `max` of `-0.0` and `0.0` gave different results on different platforms; `min` now treats `-0.0` as the smaller. * A panic’s message could interleave with what other threads printed. * The build cache could reuse an object built against an older runtime header, which then failed to link. * A `BRINE_BACKEND` build could replace a default build’s executable while it ran. * `brine run --ranks N` listened on every network interface, so Windows asked to let each new build through the firewall; with every rank on this machine it listens on loopback. ## 0.3.0 [Section titled “0.3.0”](#030) Released 2026-10-27. [Release](https://github.com/brinelang/brine/releases/tag/v0.3.0) · [Changes since 0.2.0](https://github.com/brinelang/brine/compare/v0.2.0...v0.3.0) ### Added [Section titled “Added”](#added-1) * `print` takes `sep=` and `end=`: `print(a, b, sep=", ", end="")`. * Nested and parenthesized tuple unpacking in `for`, `let` and comprehensions: `for i, (k, v) in enumerate(pairs):`, `let (a, (b, c)) = t`. * `s.contains(x)` on strings and `xs.contains(x)` on arrays (the same as `x in s`). * Integer literals up to the largest `u128` (2^128 - 1); above `i128`’s maximum only a `u128` holds them. * Native code has unwind tables (`.pdata`/`.xdata` on Windows, `.eh_frame` on Linux), so debuggers and profilers can walk through Brine frames. ### Changed [Section titled “Changed”](#changed-1) * `upper()`, `lower()`, `strip()`, `split()` and `is_alpha`/`is_alnum`/`is_space` follow Unicode, as in Python (`"straße".upper()` is `"STRASSE"`). `is_digit` stays `0`-`9`, the digits `int()` reads. * The compiler parses imported modules and proves functions in parallel (building the compiler itself: 0.85 s to 0.61 s). * Faster native code: reference counting is inlined and values just stored aren’t read back from memory. * A build whose backend `BRINE_BACKEND` overrides goes to its own folder (`dev-c`), so it can’t replace a default build’s executable while that one runs. ### Fixed [Section titled “Fixed”](#fixed-1) * A function returning a call that returns nothing (`return f()` in a `-> ()` function, as in the prelude’s `Result[(), E].unwrap()`) was rejected with E0301. * Native calls whose 128-bit result is unused, and stack slots aligned to more than 16 bytes. ## 0.2.0 [Section titled “0.2.0”](#020) Released 2026-10-27. [Release](https://github.com/brinelang/brine/releases/tag/v0.2.0) · [Changes since 0.1.0](https://github.com/brinelang/brine/compare/v0.1.0...v0.2.0) ### Added [Section titled “Added”](#added-2) * A native x86-64 backend: dev builds on Windows and Linux go straight from the Brine IR to machine code and a COFF or ELF object, then link with the runtime. The compiler builds itself in under a second (it took about 20 s through C). `BRINE_BACKEND=c` or `=native` picks a backend. * The Brine IR (`compiler/ir.br`), between the verifier and code generation. Both backends read it: x86-64, and C (for `--release`, `--debug` and other platforms). * `os.write_bytes(path, bytes)` writes a `[u8]` file. * `BRINE_TIMES=2` names the functions the prover is slow on. ### Changed [Section titled “Changed”](#changed-2) * C builds split the program into one translation unit per core, compiled in parallel and cached one by one, so an edit recompiles only what it touches (`--release` stays a single unit). * Debug builds name C variables after the Brine ones (`x_3`), so gdb and lldb show them. * Faster checking: the deadlock pass skips programs that never communicate, and the prover bounds the facts it keeps where branches merge, with the same proofs (checking the whole compiler takes about half a second). ### Removed [Section titled “Removed”](#removed) * The AST-to-C generator (`codegen.br`); C comes from the IR. ## 0.1.0 [Section titled “0.1.0”](#010) Released 2026-10-27. [Release](https://github.com/brinelang/brine/releases/tag/v0.1.0) The first version. * The language: structs and methods, enums and `match`, generics, `Option`/`Result` and `?`, tuples, arrays, maps, strings, f-strings, modules, `par` constructs, multi-process `world` programs, `test` blocks, file and process I/O (`os`). * Contracts (`requires`, `ensures`, `invariant`, `prove`) checked at compile time by a built-in prover, which also removes runtime checks it can prove unnecessary. * The `brine` CLI: `new`, `init`, `run`, `build`, `check`, `test`, `fmt`, `debug`, `explain`, `guide`, `doctor`, with `--json` output. * The compiler is written in Brine and builds itself; `bootstrap/brinec.c` builds it from scratch with only a C compiler. ## Unreleased [Section titled “Unreleased”](#unreleased) Changes on `main` since 0.4.0 are listed in the [comparison with v0.4.0](https://github.com/brinelang/brine/compare/v0.4.0...HEAD) until the next release.

# Error codes

> Every Brine compiler error code with its explanation, generated from `brine explain`.

Every diagnostic Brine prints has a stable code, a primary source span, the reason, and how to fix it. This page lists every code with the same explanation that `brine explain ` prints. Search for a code with `Ctrl` `K`, or jump to it with `#e0601`-style anchors. 
```sh
brine explain E0601          # the explanation in your terminal
brine explain --json E0601   # {"code": ..., "title": ..., "explanation": ...}
brine explain                # list every code
```
 Codes are grouped by the part of the compiler that reports them. 76 codes, from brine 0.4.0. ## Syntax (E01xx) [Section titled “Syntax (E01xx)”](#syntax-e01xx) Literals, indentation, and operators or punctuation from other languages. | Code | Meaning | | --------------- | ------------------------------ | | [E0101](#e0101) | invalid literal | | [E0102](#e0102) | unterminated string | | [E0103](#e0103) | bad indentation | | [E0104](#e0104) | tab in indentation | | [E0105](#e0105) | operator from another language | | [E0106](#e0106) | braces or semicolons | | [E0110](#e0110) | syntax error | | [E0111](#e0111) | expected an expression | | [E0112](#e0112) | contract in the wrong place | | [E0113](#e0113) | misused `par` | | [E0114](#e0114) | `else if` | ### E0101 [Section titled “E0101”](#e0101) **Invalid literal** A number or string literal couldn’t be read: the integer is too large to represent, or a string contains an unknown escape such as `\q`. The largest integer literal is 340282366920938463463374607431768211455, the maximum of `u128` (literals above the `i128` range need a `u128` type). Supported escapes: \n \t \r \0 \\\ \” \’ \\{ \\} and \u{XXXX}. Integers may use `_` separators and the 0x, 0o and 0b prefixes: 1_000_000, 0xff. ### E0102 [Section titled “E0102”](#e0102) **Unterminated string** A string or f-string literal isn’t closed on the same line, or an f-string has an unbalanced brace. In f-strings, `{expr}` interpolates and `{{` / `}}` write literal braces: 
```brine
print(f"{name} has {{braces}}")
```
 ### E0103 [Section titled “E0103”](#e0103) **Bad indentation** brine uses indentation (like Python) to group statements. A block must be indented after a line ending in `:`, and nothing else may be indented. 
```brine
fn main():
    print("indented by 4 spaces")
```
 ### E0104 [Section titled “E0104”](#e0104) **Tab in indentation** Indentation must use spaces. Tabs look different in every editor, so they are not allowed. `brine fmt` uses 4 spaces per level. ### E0105 [Section titled “E0105”](#e0105) **Operator from another language** brine spells logical operators as words and has no `++`/`--`: 
```brine
a and b      (not a && b)
a or b       (not a || b)
not a        (not !a)
x += 1       (not x++)
```
 ### E0106 [Section titled “E0106”](#e0106) **Braces or semicolons** Blocks are written with `:` and indentation, and statements end at the end of the line. Remove `{ }` and `;`. ### E0110 [Section titled “E0110”](#e0110) **Syntax error** The parser expected something else here. The message says what it expected. Common causes: a missing `:` at the end of `if`/`for`/`fn` lines, a missing type on a function parameter (`fn f(x: int)`), or a construct that brine doesn’t support yet (slicing, nested comprehensions). Run `brine guide` for the full syntax. ### E0111 [Section titled “E0111”](#e0111) **Expected an expression** A value was expected here (a number, a variable, a call, …), but the parser found something else, such as a keyword or a closing bracket. ### E0112 [Section titled “E0112”](#e0112) **Contract in the wrong place** `requires` and `ensures` must be the first lines of a function body, and `invariant` must be the first lines of a `while` or `for` body: 
```brine
fn f(x: int) -> int:
    requires x > 0
    ensures result > x
    return x + 1
```
 ### E0113 [Section titled “E0113”](#e0113) **Misused `par`** `par` comes in exactly four forms: 
```brine
par for i in 0..n:            # parallel loop
par [f(x) for x in xs]        # parallel map
par sum(x * x for x in xs)    # parallel reduction (sum/product/min/max/any/all)
par(f(a), g(b))               # run two or more expressions in parallel
```
 ### E0114 [Section titled “E0114”](#e0114) **`else if`** brine writes `elif` (like Python): 
```brine
if x < 0:
    ...
elif x == 0:
    ...
else:
    ...
```
 ## Names (E02xx) [Section titled “Names (E02xx)”](#names-e02xx) Names that are not found, defined twice, or shadowed; unknown types, fields and methods. | Code | Meaning | | --------------- | -------------- | | [E0201](#e0201) | name not found | | [E0202](#e0202) | defined twice | | [E0203](#e0203) | unknown type | | [E0205](#e0205) | no such field | | [E0206](#e0206) | no such method | | [E0207](#e0207) | shadowing | ### E0201 [Section titled “E0201”](#e0201) **Name not found** A variable, function, type, or module name isn’t defined where it’s used. The message often suggests the closest existing name. Variables are only visible after their `let`/`var` line and inside the block that declares them. ### E0202 [Section titled “E0202”](#e0202) **Defined twice** Two items, fields, methods, or parameters share a name in the same scope. Rename one of them. ### E0203 [Section titled “E0203”](#e0203) **Unknown type** The type name isn’t a built-in type, struct or enum in scope. Built-in types: int (i64), i8, i16, i32, u8, u16, u32, u64, float (f64), f32, bool, str, arrays \[T], tuples (A, B). Types from another module are written `module.Name`. ### E0205 [Section titled “E0205”](#e0205) **No such field** The struct, tuple or enum variant doesn’t have this field. Tuple fields are numbered: `pair.0`, `pair.1`. Enum values have no fields of their own; read the data of a variant with `match`: 
```brine
match shape:
    case Shape.Circle(r):
        print(r)
    case _:
        pass
```
 ### E0206 [Section titled “E0206”](#e0206) **No such method** The type doesn’t have this method. Arrays have `push(x)` and `pop()`; other operations are functions, e.g. `len(xs)`. Methods are defined inside a struct body; methods with `self` are called on a value (`p.norm()`), methods without `self` are called on the type (`Point.origin()`). ### E0207 [Section titled “E0207”](#e0207) **Shadowing** brine doesn’t allow a second variable with the same name in one function, even in an inner block. This avoids a classic source of bugs. Pick a new name, or use `var` and assign to the existing variable. ## Types (E03xx) [Section titled “Types (E03xx)”](#types-e03xx) Type mismatches, missing or extra arguments, `match` coverage, generics and conversions. | Code | Meaning | | --------------- | ------------------------------------------------- | | [E0301](#e0301) | type mismatch | | [E0302](#e0302) | wrong arguments | | [E0303](#e0303) | not callable | | [E0304](#e0304) | bad indexing | | [E0305](#e0305) | bad operands | | [E0306](#e0306) | missing return | | [E0307](#e0307) | condition isn’t bool | | [E0308](#e0308) | can’t infer type | | [E0309](#e0309) | recursive struct | | [E0310](#e0310) | not a value | | [E0311](#e0311) | unsupported extern signature | | [E0312](#e0312) | range outside a loop | | [E0313](#e0313) | break/continue outside a loop | | [E0314](#e0314) | not iterable | | [E0315](#e0315) | literal out of range | | [E0316](#e0316) | generator outside a reduction | | [E0317](#e0317) | constant not computable | | [E0318](#e0318) | bad format spec | | [E0319](#e0319) | unsupported type for this operation | | [E0320](#e0320) | `match` doesn’t handle every value | | [E0321](#e0321) | names bound inside \` | | [E0322](#e0322) | constant used as a pattern | | [E0331](#e0331) | a method’s type parameter repeats its type’s | | [E0332](#e0332) | can’t infer a type argument | | [E0333](#e0333) | too many generic instances | | [E0334](#e0334) | wrong type arguments | | [E0335](#e0335) | invalid map key type | | [E0336](#e0336) | duplicate key in a map literal | | [E0337](#e0337) | a variable without a written type | | [E0340](#e0340) | `?` on a value that isn’t an `Option` or `Result` | | [E0341](#e0341) | `?` in a function that can’t return its failure | | [E0342](#e0342) | `?` inside a comprehension or `par` body | ### E0301 [Section titled “E0301”](#e0301) **Type mismatch** A value has a different type than the one required here. brine never converts implicitly; convert explicitly instead: 
```brine
float(i)     int(x)     str(n)     i32(n)     int("42")
```
 Numeric literals adapt to context, so `let x: f32 = 1` and `let y: u8 = 200` work without conversions. ### E0302 [Section titled “E0302”](#e0302) **Wrong arguments** A call has the wrong number of arguments or bad keyword arguments. Struct constructors take every field, positionally or by name: 
```brine
Point(1, 2)
Point(x=1, y=2)
```
 ### E0303 [Section titled “E0303”](#e0303) **Not callable** Only functions, methods, struct names, and built-ins can be called. ### E0304 [Section titled “E0304”](#e0304) **Bad indexing** Only arrays can be indexed, and only with an integer: `xs[i]`. ### E0305 [Section titled “E0305”](#e0305) **Bad operands** The operator doesn’t work on these types. Arithmetic needs two numbers of the same type; `and`/`or`/`not` need `bool`; `+` also joins two strings. Arrays, tuples and structs support `==` and `!=`; strings and numbers can also be ordered with `<`. ### E0306 [Section titled “E0306”](#e0306) **Missing return** Some path through a function with a return type reaches the end without a `return`. Add a `return` at the end (or make every branch of a final `if/elif/else` return). ### E0307 [Section titled “E0307”](#e0307) **Condition isn’t bool** `if`, `elif` and `while` need a `bool`. brine has no “truthiness”: write `if len(xs) > 0:` instead of `if xs:`. ### E0308 [Section titled “E0308”](#e0308) **Can’t infer type** The compiler can’t tell the type from the expression alone. Add an annotation: 
```brine
var xs: [int] = []
let n: int = world.recv(0)
```
 ### E0309 [Section titled “E0309”](#e0309) **Recursive struct** A struct can’t contain itself directly (it would be infinitely large). Use an enum for recursive data: enum values are stored behind a pointer, so a variant may contain the enum itself. 
```brine
enum Tree:
    Leaf(value: int)
    Node(left: Tree, right: Tree)
```
 An array of the struct also works: `children: [Node]`. ### E0310 [Section titled “E0310”](#e0310) **Not a value** Types, modules and functions can’t be stored in variables or passed around (first-class functions are planned). ### E0311 [Section titled “E0311”](#e0311) **Unsupported extern signature** `extern fn` declares a C function. For now extern functions may only take and return integers, floats and bools. ### E0312 [Section titled “E0312”](#e0312) **Range outside a loop** Ranges (`a..b`, `a..=b`, `range(...)`) are only allowed as the iterable of a `for` loop or comprehension. To make an array of a range use `[i for i in 0..n]`. ### E0313 [Section titled “E0313”](#e0313) **Break/continue outside a loop** `break` and `continue` only work inside `for` and `while` loops. ### E0314 [Section titled “E0314”](#e0314) **Not iterable** `for` can iterate over ranges and arrays. ### E0315 [Section titled “E0315”](#e0315) **Literal out of range** The literal doesn’t fit the type it’s used as, e.g. `let x: u8 = 300`. ### E0316 [Section titled “E0316”](#e0316) **Generator outside a reduction** `x for x in xs` (without brackets) is a generator. It can only be passed to sum, product, min, max, any or all. Use `[x for x in xs]` to build an array. ### E0317 [Section titled “E0317”](#e0317) **Constant not computable** `const` values must be computable at compile time from literals and other constants, using arithmetic, comparisons and logic. ### E0318 [Section titled “E0318”](#e0318) **Bad format spec** Format specs in f-strings follow `{value:[<|>][0][width][.precision][kind]}`: 
```brine
f"{x:.3f}"     3 decimals          f"{n:>8}"   right-align in 8 columns
f"{n:05}"      zero-pad to 5       f"{n:x}"    hexadecimal (also `b` for binary)
f"{x:e}"       scientific
```
 ### E0319 [Section titled “E0319”](#e0319) **Unsupported type for this operation** This operation only works on certain types. `world` messages can be int, float, bool, str, \[int] or \[float]; world reductions work on int and float. ### E0320 [Section titled “E0320”](#e0320) **`match` doesn’t handle every value** Every `match` must handle every possible value, so a new enum variant can never slip through unnoticed. The error shows an example value that no case handles. Add a case for it, or end with a catch-all: 
```brine
match shape:
    case Shape.Circle(r):
        return 3.14 * r * r
    case _:
        return 0.0
```
 Cases with an `if` guard don’t count toward this, because the guard may be false. Integers and strings have too many values to list, so matching on them always needs `case _:` (or a plain name, which also matches everything). ### E0321 [Section titled “E0321”](#e0321) **Names bound inside `|`** In `case A | B:` either alternative may match, so a name bound in only one of them might not have a value. Use `_` inside `|` patterns, or write a separate `case` for each alternative. ### E0322 [Section titled “E0322”](#e0322) **Constant used as a pattern** A plain name in a pattern always captures the matched value under that name; it doesn’t compare against a constant of the same name. Compare explicitly: 
```brine
match n:
    case x if x == LIMIT:
        ...
```
 ### E0331 [Section titled “E0331”](#e0331) **A method’s type parameter repeats its type’s** A method can have type parameters of its own, and a method of a generic type can also use the type’s parameters. The two can’t share a name, since `T` would then mean two things inside the method: 
```brine
struct Box[T]:
    v: T


    fn pair[T](self, x: T) -> (T, T):    # error: `T` is already Box's
        ...


    fn pair[U](self, x: U) -> (T, U):    # ok
        return (self.v, x)
```
 Type arguments of a method are inferred from its arguments, or written after the method name: `b.pair[str]("x")`. ### E0332 [Section titled “E0332”](#e0332) **Can’t infer a type argument** The compiler works out type arguments from the arguments and from the type the result must have. When neither says what a type parameter is, write it: 
```brine
let xs = make[int]()             # explicit type argument
let o: Option[str] = None        # or annotate the variable
let p = Pair[int, str](a=1, b="x")
```
 ### E0333 [Section titled “E0333”](#e0333) **Too many generic instances** Each combination of type arguments makes a separate copy of a generic function or type. A function that calls itself with an ever larger type, like `f[[T]]` inside `f[T]`, would need infinitely many. Restructure the recursion so the type arguments stay the same. ### E0334 [Section titled “E0334”](#e0334) **Wrong type arguments** Generic types need one type argument per parameter: `Option[int]`, `Result[int, str]`, `Pair[A, B]`. Types that aren’t generic take none. ### E0335 [Section titled “E0335”](#e0335) **Invalid map key type** Map keys must be compared with `==` reliably, so they can be integers, `bool`, `str`, enums, or arrays, tuples and structs made of those. Floats are not allowed because `nan != nan`; maps can’t be keys either. 
```brine
var by_id: {int: str} = {}         # ok
var cells: {(int, int): bool} = {} # ok
var bad: {float: str} = {}         # error
```
 ### E0336 [Section titled “E0336”](#e0336) **Duplicate key in a map literal** The same constant key appears twice in one `{...}` literal, so the first value would be silently replaced. Remove one of them. ### E0337 [Section titled “E0337”](#e0337) **A variable without a written type** This project asks for a written type on every variable: its brine.toml has 
```brine
[check]
typed = true
```
 (or the command was given `--typed`). Types are written after the name: 
```brine
let count: int = 0
var names: [str] = []
let (q, r): (int, int) = (17 / 5, 17 % 5)
for i: int in 0..n:
for name: str, age: int in ages.items():
```
 Comprehension variables (`[x * x for x in xs]`), `match` bindings and `_` don’t need one: their types come from the code right next to them. Without `typed = true`, writing types is optional everywhere. ### E0340 [Section titled “E0340”](#e0340) **`?` on a value that isn’t an `Option` or `Result`** `x?` unwraps an `Option` or a `Result`: it gives the value inside `Some`/`Ok`, or returns `None`/`Err` from the current function right away. 
```brine
let n = parse(s)?          # parse returns Result[int, str]
```
 Other values have nothing to unwrap, so `?` doesn’t apply to them. ### E0341 [Section titled “E0341”](#e0341) **`?` in a function that can’t return its failure** `?` returns early with the `None` or `Err` it found, so the function has to be able to return that: * `opt?` needs the function to return `Option[...]`; * `res?` needs it to return `Result[..., E]` with the same error type `E`. 
```brine
fn total(parts: [str]) -> Result[int, str]:
    var sum = 0
    for p in parts:
        sum += parse_int(p)?     # an Err(str) is returned as is
    return Ok(sum)
```
 There are no automatic conversions between error types; use `match` to turn one error into another. In `main`, return `Result[(), str]` to use `?`. ### E0342 [Section titled “E0342”](#e0342) **`?` inside a comprehension or `par` body** A comprehension or `par` body isn’t a function you can return from early, so `?` can’t be used inside it. Write a `for` loop instead: 
```brine
var out: [int] = []
for s in parts:
    out.push(parse_int(s)?)
```
 ## Mutability and references (E04xx) [Section titled “Mutability and references (E04xx)”](#mutability-and-references-e04xx) `let` and `var`, `ref` parameters and arguments, and aliasing. | Code | Meaning | | --------------- | ------------------------------------------------------ | | [E0401](#e0401) | assigning to an immutable binding | | [E0402](#e0402) | `ref` mismatch at a call | | [E0403](#e0403) | modifying a parameter | | [E0404](#e0404) | not assignable | | [E0405](#e0405) | a variable is changed and used again in one expression | ### E0401 [Section titled “E0401”](#e0401) **Assigning to an immutable binding** Variables declared with `let` (and loop variables) can’t change. Declare the variable with `var` if it needs to change: 
```brine
var total = 0
total += 1
```
 ### E0402 [Section titled “E0402”](#e0402) **`ref` mismatch at a call** Arguments are passed by value. When a function takes `ref x: T`, it can change the caller’s variable, and the caller must say so explicitly: `f(ref xs)`. Without `ref` in the parameter list, the argument may not be marked `ref`. This makes every change to a caller’s variable visible at the call site. Methods that change their receiver are declared with `ref self` and called normally: `p.move_by(1.0)`. ### E0403 [Section titled “E0403”](#e0403) **Modifying a parameter** Parameters are read-only. To change a local copy, declare the parameter `mut`; to change the caller’s variable, declare it `ref`: 
```brine
fn countdown(mut n: int):        # local copy
    while n > 0:
        n -= 1


fn fill(ref xs: [int], v: int):  # caller's variable: call as fill(ref data, 0)
    for i in 0..len(xs):
        xs[i] = v
```
 ### E0404 [Section titled “E0404”](#e0404) **Not assignable** Only variables, their fields and their elements can be assigned: `x = 1`, `p.x = 1`, `xs[i] = 1`. References are made with `let r = ref x`, where `x` is a variable. `r` is then another name for `x` for the rest of the block. A reference is always declared with `let` (it can’t be re-pointed) and names a whole variable. ### E0405 [Section titled “E0405”](#e0405) **A variable is changed and used again in one expression** An expression that changes a variable in place (`xs.pop()`, `m.remove(k)`, a `ref self` method, a `ref` argument) can’t also read that variable somewhere else, or change it a second time, because the result would depend on the order of evaluation: 
```brine
print(xs, xs.pop())         # error: read and changed
print(xs.pop(), xs.pop())   # error: changed twice
xs.push(xs.pop())           # error: changed twice
let last = xs.pop()         # ok: change first,
print(xs, last)             # then read
```
 Reading the variable in the changing call’s own receiver and arguments is fine: they are evaluated before the call (as in Rust and Swift), and a by-value argument keeps that value even while the call changes the variable: 
```brine
append_twice(ref v, v)      # ok: the second argument is v before the call
xs.push(len(xs))            # ok
```
 The two sides of `and`/`or`, and the parts of `a if cond else b`, run one after the other, so they may change and read the same variable: 
```brine
if not self.eat(Tok.Comma) or self.at(Tok.RBracket):   # ok
```
 The same variable can’t be passed as `ref` twice in one call (two names for one variable inside the function). Copy it first: `let snapshot = xs`. ## Parallel safety and communication (E05xx) [Section titled “Parallel safety and communication (E05xx)”](#parallel-safety-and-communication-e05xx) `par` rules, and the `world` checks that rule out deadlocks. | Code | Meaning | | --------------- | --------------------------------------- | | [E0501](#e0501) | writing shared state in `par` | | [E0502](#e0502) | par write array accessed elsewhere | | [E0503](#e0503) | side effect not allowed in `par` | | [E0505](#e0505) | control flow out of `par` | | [E0507](#e0507) | unsupported invariant | | [E0508](#e0508) | filter in `par [...]` | | [E0510](#e0510) | communication not reached by every rank | | [E0511](#e0511) | value must be the same on every rank | | [E0512](#e0512) | no `send`/`recv` | ### E0501 [Section titled “E0501”](#e0501) **Writing shared state in `par`** Iterations of `par` code run at the same time. To rule out data races, a `par` body may not assign variables from outside it, call functions that mutate them, or push/pop them. The one exception: in `par for i in ...`, arrays from outside may be written at index `i`: 
```brine
var out = [0.0] * n
par for i in 0..n:
    out[i] = f(i)        # OK: each iteration writes its own slot
```
 To combine results, use a reduction instead: `par sum(f(i) for i in 0..n)`. ### E0502 [Section titled “E0502”](#e0502) **Par write array accessed elsewhere** An array written as `out[i]` inside a `par for` may only be accessed as `out[i]` in that loop (with exactly the loop variable as the index), since any other index might belong to an iteration running at the same time. ### E0503 [Section titled “E0503”](#e0503) **Side effect not allowed in `par`** `exit` and `world` communication would be non-deterministic inside parallel code, and so would `write_file`, `run` and the `os` functions that change things outside the program (`os.spawn`, `os.wait`, `os.poll`, `os.kill`, `os.make_dir`, `os.remove_file`, `os.rename`, `os.sleep`, `os.enable_ansi`). Do them before or after the `par` construct. ### E0505 [Section titled “E0505”](#e0505) **Control flow out of `par`** `return` and `break` can’t leave a `par` body: other iterations are running at the same time. `continue` is fine (it ends the current iteration). ### E0507 [Section titled “E0507”](#e0507) **Unsupported invariant** Invariants describe the state between iterations, which doesn’t exist for a `par for`. Stepped ranges don’t support invariants yet; rewrite the loop as a `while` loop with an invariant. ### E0508 [Section titled “E0508”](#e0508) **Filter in `par [...]`** Parallel comprehensions need a known output size. Filter afterwards, or use a sequential comprehension: `[x for x in xs if x > 0]`. ### E0510 [Section titled “E0510”](#e0510) **Communication not reached by every rank** `world` communication is collective: every rank takes part in every call, and all ranks must make the same calls in the same order. If some ranks skip a call that others make, the others would wait forever. brine rules this out at compile time. A call is rejected when the condition that leads to it may differ between ranks: anything computed from `world.rank()`, `clock()`, values received with `world.shift`/`world.exchange`, files, processes, `os` functions (except `os.name()` and `os.arch()`), or C functions. Early `return`, `break` and `continue` under such a condition count too, as do `and`/`or`. 
```brine
if world.rank() == 0:
    world.barrier()          # error: only rank 0 gets here
```
 Compute the rank-specific part first, then communicate on every rank: 
```brine
let mine = 1 if world.rank() == 0 else 0
let total = world.sum(mine)  # every rank calls this
```
 Results of `world.sum`, `min`, `max`, `broadcast` and `gather_all` are the same on every rank, so they can be used in conditions freely: 
```brine
while world.max(err) > 1e-6:
    ...
```
 ### E0511 [Section titled “E0511”](#e0511) **Value must be the same on every rank** Some arguments must have the same value on every rank: the root of `world.broadcast`, the distance of `world.shift`, and parameters that decide whether a function communicates (for example a loop count around a `world.sum`). Compute them from values that are the same everywhere: constants, `world.size()`, `args()`, or results of `world.broadcast`/`sum`. ### E0512 [Section titled “E0512”](#e0512) **No `send`/`recv`** brine has no unmatched `send`/`recv`, because a receive without a matching send hangs forever. Every communication is collective instead: 
```brine
world.shift(v, 1)        # send v to rank + 1, receive from rank - 1 (wraps around)
world.exchange(xs)       # send xs[j] to rank j; returns what each rank sent you
world.broadcast(root, v) # everyone gets root's value
world.gather_all(v)      # everyone gets [v from rank 0, v from rank 1, ...]
```
 For example, “rank 0 sends a message to rank 1”: 
```brine
var outbox = [""] * world.size()
if world.rank() == 0 and world.size() > 1:
    outbox[1] = "hello"
let inbox = world.exchange(outbox)
# on rank 1, inbox[0] == "hello"
```
 ## Proofs (E06xx) [Section titled “Proofs (E06xx)”](#proofs-e06xx) Contracts (`requires`, `ensures`, `invariant`, `prove`) the compiler could not prove. | Code | Meaning | | --------------- | ------------------------------ | | [E0601](#e0601) | precondition not proven | | [E0602](#e0602) | postcondition not proven | | [E0603](#e0603) | invariant false on entry | | [E0604](#e0604) | invariant not preserved | | [E0605](#e0605) | claim not proven | | [E0606](#e0606) | not allowed in a specification | ### E0601 [Section titled “E0601”](#e0601) **Precondition not proven** The function’s `requires` clause must hold at every call, and the compiler must be able to prove it from what it knows at the call site. Rules can’t be broken, so an unproven claim is an error, not a runtime check. Fix it by giving the prover the facts it needs, for example: 
```brine
if 0 <= i and i < len(xs):     # the call is only made when it's safe
    print(get(xs, i))


assert 0 <= i < len(xs)        # checked at runtime, then known to the prover
print(get(xs, i))
```
 or by adding a `requires` to the calling function, which moves the obligation to its callers. The error lists the facts the prover knew and the claim it needed. ### E0602 [Section titled “E0602”](#e0602) **Postcondition not proven** An `ensures` clause must hold on every `return`. The prover reasons about each path through the function. If it can’t show the claim on some path, add a `requires` the claim depends on, an `invariant` to loops before the return, or an `assert` (runtime check) before the return. ### E0603 [Section titled “E0603”](#e0603) **Invariant false on entry** A loop `invariant` must hold before the first iteration. For `for i in a..b` loops, invariants may mention `i`; on entry they’re checked with `i = a`. Initialize variables so the invariant holds, or weaken the invariant. ### E0604 [Section titled “E0604”](#e0604) **Invariant not preserved** Assuming the invariant (and the loop condition) at the start of an iteration, the prover couldn’t show it holds again at the end. For `for i in a..b`, the invariant is checked at the end of the iteration with `i + 1`. The prover only knows what the invariant says about variables the loop changes, so invariants often need to carry bounds such as `0 <= i <= n`. ### E0605 [Section titled “E0605”](#e0605) **Claim not proven** `prove e` asks the compiler to show `e` is always true at that point. Unlike `assert`, it generates no runtime code, so it must be provable. Use `assert` if a runtime check is acceptable. ### E0606 [Section titled “E0606”](#e0606) **Not allowed in a specification** Contracts (`requires`, `ensures`, `invariant`, `prove`) are pure logical statements. They may use arithmetic, comparisons, `and`/`or`/`not`, `len`, `abs`, `min`, `max`, fields, indexing, constants, and `result` (in `ensures`). Function calls and other side effects aren’t allowed. ## Driver (E07xx) [Section titled “Driver (E07xx)”](#driver-e07xx) Missing modules, no `main`, and C compiler or linker problems. | Code | Meaning | | --------------- | ---------------------------------- | | [E0701](#e0701) | module not found | | [E0702](#e0702) | no C compiler | | [E0703](#e0703) | no main function | | [E0704](#e0704) | cannot write build files | | [E0705](#e0705) | building the generated code failed | | [E0706](#e0706) | invalid `main` signature | | [E0707](#e0707) | cannot embed a file | ### E0701 [Section titled “E0701”](#e0701) **Module not found** `import a.b` loads `a/b.br` relative to the directory of the main file. Check the file name and location. ### E0702 [Section titled “E0702”](#e0702) **No C compiler** brine uses the system C compiler (clang or gcc) to build its runtime and link executables, and for optimized builds it compiles the program through C. Install one, or point BRINE_CC at it. `brine doctor` checks your setup. WebAssembly (`--target wasm` or `web`) takes the clang and C library of wasi-sdk instead: download it from  and set WASI_SDK_PATH to its folder (or unpack it to /opt/wasi-sdk). ### E0703 [Section titled “E0703”](#e0703) **No main function** A program starts at `fn main():` in its main file. `main` may also return an `int`, which becomes the process exit code. (Libraries and test-only files can be checked with `brine check` and tested with `brine test`.) ### E0704 [Section titled “E0704”](#e0704) **Cannot write build files** The compiler couldn’t create its output or cache files. Check permissions and free disk space for the `target/` directory. ### E0705 [Section titled “E0705”](#e0705) **Building the generated code failed** The generated code didn’t compile or link. That’s a bug in brine, not in your program. Please report it with the source file and the output of `brine doctor`. Building with `BRINE_BACKEND=c` (or `=native`) tries the other backend. ### E0706 [Section titled “E0706”](#e0706) **Invalid `main` signature** `main` takes no parameters (command-line arguments come from `args()`) and returns one of: 
```brine
fn main():                      # exit code 0
fn main() -> int:               # the exit code
fn main() -> Result[(), str]:   # Err(e) prints "error: e" and exits with 1
```
 The `Result` form lets `main` use `?`: 
```brine
fn main() -> Result[(), str]:
    let text = read_file("config.txt")?
    print(len(text))
    return Ok(())
```
 ### E0707 [Section titled “E0707”](#e0707) **Cannot embed a file** `embed(path)` puts a file’s text into the program when it is compiled, so the file must exist then, be UTF-8 text, and its path must be a string literal (not a variable or an f-string). The path is relative to the directory of the `.br` file that contains the call; “\r\n” line endings become “\n”. 
```brine
const HELP = embed("help.txt")      # help.txt next to this file
fn main():
    print(HELP)
```
 To read a file while the program runs instead, use `read_file(path)`.