Language: syntax, types and the standard built-ins # 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.