Skip to content

The language

Bindings

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

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

fn main():
let x = 1
x = 2
print(x)
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 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.

const SIZE = 2 * 50
const LIMIT = SIZE + 1
const RATE: float = 0.25
const NAME = "brine"
fn main():
print(SIZE, LIMIT, RATE, NAME)
Output
100 101 0.25 brine

A function call or an array isn’t a constant expression (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).

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.

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
0 7
1 7
["ann"] {"ann": 1}
fn main():
var names = []
names.push("ann")
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); only local variables are inferred.

A let or var can take a tuple apart. Patterns nest.

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

_ 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. It may appear any number of times and can never be read.

fn main():
let (q, _) = (17 / 5, 17 % 5)
print(q, _)
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

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.

fn main():
let count = 1
if count > 0:
let count = 2
print(count)
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).

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.

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
big
5

Some teams want every variable’s type on the page. Put this in brine.toml:

brine.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 names each one that’s missing, with the type to add:

fn main():
let n: int = 3
let xs = [1, 2]
for i in 0..n:
print(i, xs)
Terminal window
brine check --typed main.br
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.

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