Skip to content

Reference

Error codes

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 <code> prints. Search for a code with Ctrl K, or jump to it with #e0601-style anchors.

Terminal window
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.

Literals, indentation, and operators or punctuation from other languages.

Code Meaning
E0101 invalid literal
E0102 unterminated string
E0103 bad indentation
E0104 tab in indentation
E0105 operator from another language
E0106 braces or semicolons
E0110 syntax error
E0111 expected an expression
E0112 contract in the wrong place
E0113 misused par
E0114 else if

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.

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:

print(f"{name} has {{braces}}")

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.

fn main():
print("indented by 4 spaces")

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.

Operator from another language

brine spells logical operators as words and has no ++/--:

a and b (not a && b)
a or b (not a || b)
not a (not !a)
x += 1 (not x++)

Braces or semicolons

Blocks are written with : and indentation, and statements end at the end of the line. Remove { } and ;.

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.

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.

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:

fn f(x: int) -> int:
requires x > 0
ensures result > x
return x + 1

Misused par

par comes in exactly four forms:

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

else if

brine writes elif (like Python):

if x < 0:
...
elif x == 0:
...
else:
...

Names that are not found, defined twice, or shadowed; unknown types, fields and methods.

Code Meaning
E0201 name not found
E0202 defined twice
E0203 unknown type
E0205 no such field
E0206 no such method
E0207 shadowing

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.

Defined twice

Two items, fields, methods, or parameters share a name in the same scope. Rename one of them.

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.

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:

match shape:
case Shape.Circle(r):
print(r)
case _:
pass

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()).

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.

Type mismatches, missing or extra arguments, match coverage, generics and conversions.

Code Meaning
E0301 type mismatch
E0302 wrong arguments
E0303 not callable
E0304 bad indexing
E0305 bad operands
E0306 missing return
E0307 condition isn’t bool
E0308 can’t infer type
E0309 recursive struct
E0310 not a value
E0311 unsupported extern signature
E0312 range outside a loop
E0313 break/continue outside a loop
E0314 not iterable
E0315 literal out of range
E0316 generator outside a reduction
E0317 constant not computable
E0318 bad format spec
E0319 unsupported type for this operation
E0320 match doesn’t handle every value
E0321 names bound inside `
E0322 constant used as a pattern
E0331 a method’s type parameter repeats its type’s
E0332 can’t infer a type argument
E0333 too many generic instances
E0334 wrong type arguments
E0335 invalid map key type
E0336 duplicate key in a map literal
E0337 a variable without a written type
E0340 ? on a value that isn’t an Option or Result
E0341 ? in a function that can’t return its failure
E0342 ? inside a comprehension or par body

Type mismatch

A value has a different type than the one required here. brine never converts implicitly; convert explicitly instead:

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.

Wrong arguments

A call has the wrong number of arguments or bad keyword arguments. Struct constructors take every field, positionally or by name:

Point(1, 2)
Point(x=1, y=2)

Not callable

Only functions, methods, struct names, and built-ins can be called.

Bad indexing

Only arrays can be indexed, and only with an integer: xs[i].

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 <.

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).

Condition isn’t bool

if, elif and while need a bool. brine has no “truthiness”: write if len(xs) > 0: instead of if xs:.

Can’t infer type

The compiler can’t tell the type from the expression alone. Add an annotation:

var xs: [int] = []
let n: int = world.recv(0)

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.

enum Tree:
Leaf(value: int)
Node(left: Tree, right: Tree)

An array of the struct also works: children: [Node].

Not a value

Types, modules and functions can’t be stored in variables or passed around (first-class functions are planned).

Unsupported extern signature

extern fn declares a C function. For now extern functions may only take and return integers, floats and bools.

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].

Break/continue outside a loop

break and continue only work inside for and while loops.

Not iterable

for can iterate over ranges and arrays.

Literal out of range

The literal doesn’t fit the type it’s used as, e.g. let x: u8 = 300.

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.

Constant not computable

const values must be computable at compile time from literals and other constants, using arithmetic, comparisons and logic.

Bad format spec

Format specs in f-strings follow {value:[<|>][0][width][.precision][kind]}:

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

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.

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:

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).

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.

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:

match n:
case x if x == LIMIT:
...

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:

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").

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:

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")

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.

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.

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.

var by_id: {int: str} = {} # ok
var cells: {(int, int): bool} = {} # ok
var bad: {float: str} = {} # error

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.

A variable without a written type

This project asks for a written type on every variable: its brine.toml has

[check]
typed = true

(or the command was given --typed). Types are written after the name:

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.

? 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.

let n = parse(s)? # parse returns Result[int, str]

Other values have nothing to unwrap, so ? doesn’t apply to them.

? 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.
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 ?.

? 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:

var out: [int] = []
for s in parts:
out.push(parse_int(s)?)

let and var, ref parameters and arguments, and aliasing.

Code Meaning
E0401 assigning to an immutable binding
E0402 ref mismatch at a call
E0403 modifying a parameter
E0404 not assignable
E0405 a variable is changed and used again in one expression

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

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).

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:

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

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.

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:

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:

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:

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.

par rules, and the world checks that rule out deadlocks.

Code Meaning
E0501 writing shared state in par
E0502 par write array accessed elsewhere
E0503 side effect not allowed in par
E0505 control flow out of par
E0507 unsupported invariant
E0508 filter in par [...]
E0510 communication not reached by every rank
E0511 value must be the same on every rank
E0512 no send/recv

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).

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.

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.

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).

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.

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].

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.

if world.rank() == 0:
world.barrier() # error: only rank 0 gets here

Compute the rank-specific part first, then communicate on every rank:

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:

while world.max(err) > 1e-6:
...

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.

No send/recv

brine has no unmatched send/recv, because a receive without a matching send hangs forever. Every communication is collective instead:

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”:

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"

Contracts (requires, ensures, invariant, prove) the compiler could not prove.

Code Meaning
E0601 precondition not proven
E0602 postcondition not proven
E0603 invariant false on entry
E0604 invariant not preserved
E0605 claim not proven
E0606 not allowed in a specification

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:

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.

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.

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.

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.

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.

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.

Missing modules, no main, and C compiler or linker problems.

Code Meaning
E0701 module not found
E0702 no C compiler
E0703 no main function
E0704 cannot write build files
E0705 building the generated code failed
E0706 invalid main signature
E0707 cannot embed a file

Module not found

import a.b loads a/b.br relative to the directory of the main file. Check the file name and location.

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 https://github.com/WebAssembly/wasi-sdk/releases and set WASI_SDK_PATH to its folder (or unpack it to /opt/wasi-sdk).

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.)

Cannot write build files

The compiler couldn’t create its output or cache files. Check permissions and free disk space for the target/ directory.

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.

Invalid main signature

main takes no parameters (command-line arguments come from args()) and returns one of:

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 ?:

fn main() -> Result[(), str]:
let text = read_file("config.txt")?
print(len(text))
return Ok(())

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”.

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).