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”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)6.06.0hello, ann3 2true- Parameter types are required. Local variable types are inferred.
- A function without
-> Treturns nothing (its type is());returnon 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:
fn sign(x: int) -> int: if x > 0: return 1 elif x < 0: return -1
fn main(): print(sign(5))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 errorerror: could not compile due to 1 errorFunctions can be generic (fn first[T](xs: [T]) -> Option[T]); see
generics. They can also carry requires and ensures
contracts that the compiler proves; see contracts.
Parameters are read-only
Section titled “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:
fn dec(n: int) -> int: n -= 1 return n
fn main(): print(dec(3))error[E0403]: cannot assign to parameter `n` --> main.br:2:5 |2 | n -= 1 | ^ parameters are read-only1 | 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 errorerror: could not compile due to 1 errormut parameters
Section titled “mut parameters”mut makes the parameter a local copy that the function may change. The caller’s value
is untouched.
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)[3, 2, 1] 3This works for arrays too, and it’s cheap: the array is copied only when the function first changes it (copy-on-write).
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)[1, 2, 3] [11, 12, 13]ref parameters
Section titled “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.
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)[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:
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)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 errorerror: could not compile due to 1 errorMethods that change their receiver take ref self; see
structs.
Local references
Section titled “Local references”let d = ref data makes d a second name for the variable data. Reading or
changing d reads or changes data.
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)[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 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”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:
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])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 errorerror: could not compile due to 1 errorSwap 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”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.
fn main(): var xs = [1, 2, 3] print(xs.pop(), xs)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 errorerror: could not compile due to 1 errorMake 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.
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)[1, 2] 24main 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 anErr(e)printserror:and the error to stderr and exits with 1. This letsmainuse the?operator.
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(())error: not a port number: 80xSee Option and Result for ?. A panic exits with code
101, and exit(code) ends the program at once from anywhere.
Functions aren’t values (yet)
Section titled “Functions aren’t 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.
fn square(x: int) -> int: return x * x
fn main(): let f = square print(f(3))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 errorerror[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 errorerror: could not compile due to 2 errorsTo choose behavior at run time, use an enum and
match on it.
- Every parameter has a type; the return type follows
->(none means()). - Parameters are read-only.
mut p: Tis a changeable local copy;ref p: Tis the caller’s variable, and the caller writesreftoo. let r = ref vmakesranother name for the variablev.- References can’t be stored or returned, so they never dangle.
- A variable can’t be passed as
reftwice in one call. - An expression that changes a variable can’t read it elsewhere or change it again.
mainreturns nothing, anintexit code, orResult[(), E].- Functions aren’t values.