Skip to content

The language

Built-in functions

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

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 floats
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 has more for files, directories and processes, and world is for programs that run as many processes.

print writes its arguments separated by sep (default " ") and followed by end (default "\n"). eprint does the same on stderr.

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

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.

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

There are no implicit conversions. Call the type as a function:

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
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.
  • ord(c) is the code point of a one-character string and chr(n) the string for a code point.
greet.br
fn main() -> int:
let a = args()
if len(a) == 0:
eprint("usage: greet <name>...")
return 2
for name in a:
print(f"hello, {name}")
return 0
Terminal window
brine run greet.br -- ann "bob smith"
Output
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.
  • 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).
  • clock() is a monotonic time in seconds as a float. Subtract two readings to time something:
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
29999994 0.013 s

(The time differs from run to run.)

fn main():
let n = 3
panic(f"bad value: {n}")
Output
panic: bad value: 3
--> main.br:3:5

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:

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 consts, as above, and anywhere an expression can go.
  • A missing file or a non-literal path is error E0707:
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
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
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.

With ? these read naturally in a function that returns Result:

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
3

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
fn main():
par for i in 0..4:
let r = write_file(f"out{i}.txt", "hi")
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.

Related: strings, arrays, the os module, C interop.