Brass cheat sheet
Use this sheet to look up Brass syntax through short examples from a dinosaur park. Each section links to the rule in the specification, which decides what is valid.
The rejection notes describe language rules, including checks still to be implemented. Today's brass tool checks program structure, runs examples and answers questions about the code. Proving function contracts requires the full checker.
The smallest program
A module declares and implements a greeting. The system file makes it callable by the host.
# hello/greet.brass
module hello.greet
interface
fn greet(name: Text) -> Text
example greet("Ada") == "Hello, Ada"
body
fn greet(name) = "Hello, " ++ name
# system.brass
system hello
grants hello.greet.greet: none
limit cpu_ms 1000
limit memory_bytes 16777216
limit transcript_bytes 1048576, drop oldest
A program is its modules plus one system.brass. The host calls an entry point by its full name, hello.greet.greet; there is no main. Only a function with a grants line can be called from outside. There is no print: output goes through an access (Authority).
Comments
# park/ids.brass — declaration excerpt
# a comment runs from # to the end of the line
type PaddockId = Int where it in 1 to 40 # at the end of a line, too
A comment directly above a declaration is shown by brass info.
Modules and imports
# park/doors.brass — layout
module park.doors
import park.enclosures as enclosures
interface
…
private
…
body
…
- One module, one file.
interfaceandbodyare always written;privateis optional. - Placement is visibility.
interfaceis what other modules see.privateis for this file only. There are no visibility modifiers and no export list. - Every import has an alias, and imported names are always written through it:
enclosures.Paddock, neverPaddock. bodyholds implementations only. Every function is declared ininterfaceorprivate.
Names
| What | Case | Example |
|---|---|---|
| types, constructors, states | Uppercase | Paddock, Species.Rex |
| modules, functions, parameters, fields, aliases | lowercase | park.doors, secure, p.id |
Constructors are qualified by their type: Species.Rex, FailureCode.NotFound. Ok, Err, Done and None stay bare. A keyword is never a name.
Built-in types
| Type | Meaning |
|---|---|
Int |
signed 64-bit integer; also Int8 Int16 Int32 Int64 |
Float |
64-bit IEEE float |
Bool |
true or false |
Text |
text; length counts characters |
Bytes |
a byte sequence |
Array<T> |
ordered; literals [], [a, b] |
Set<T> |
for membership and bounds; literal {a, b}; not iterable |
Maybe<T> |
a T or None |
Outcome<T> |
Ok(T) or Err(FailureCode, Text) |
Done |
the one value of a call with nothing to return: Outcome<Done> |
No Map, no function values, no null. Spec §2
Bindings and constants
Use const for a value known to the checker and let for a local binding.
# park/enclosures.brass — excerpts
interface
const max_heads: Int = 12
body
fn total(…) =
let count: Int = 0 # every let names its type
count = count + 1 # rebinding: same type, local lets only
count
- Values never change. Rebinding points the name at a new value.
- Rejected: an untyped
let, an unusedlet, rebinding a parameter. - Literals take their type from where they stand.
0is anIntin anIntposition and aFloatin aFloatone.1.5in an integer position is rejected.
Operators
| Kind | Operators |
|---|---|
| arithmetic | + - * / %, -x |
| powers | a ^ b, constants only |
| join text or arrays | ++ |
| compare | == != < <= > >= |
| logic | and or not |
| membership, type test | in, is |
| pass up a failure | ? |
+never joins text."a" ++ "b".IntandFloatnever mix in one operation.- Overflow never wraps. On an integer without a range, it aborts the task.
- Integer
/and%need a divisor the checker can prove nonzero./truncates toward zero. - Rejected:
a < b < c. Writea < b and b < c. - Rejected:
a and b or c. Write(a and b) or c.
Full precedence table: spec §2.2
Text
# park/signs.brass — body excerpt; needs `import std.text as text`
fn sign(p) =
let label: Text = "Paddock " ++ text.of(p.id)
if label.length <= 20 then label else "Paddock"
text.of turns an Int into its decimal text. Using std needs requires std = brass.std v0.2 in system.brass.
Records and sums
# park/enclosures.brass — excerpts
type Species = Goat | Raptor | Rex # a sum: bare names, no payloads
type Paddock = { id: PaddockId, species: Species, status: Enclosure }
let p: Paddock = { id: 2, species: Species.Rex, status: Enclosure.Secured }
let q: Paddock = p with { species: Species.Raptor } # a copy; p is unchanged
withcopies a record and replaces only the fields named.- Equality is structural. Two records with equal fields are equal.
- Sums carry no data.
OkandErrare the only arms that do.
If and else
# park/enclosures.brass — body excerpt
fn ration(species) =
if species in {Species.Goat} then 2000
else if species in {Species.Raptor} then 12000
else 50000
ifis an expression. Used as a value, it needs anelse.- Blocks are indented. A block's last line is its value.
- There is no
match; test withinandis.
Loops
# park/doors.brass — body excerpt
for attempt in 1 to 3 # both ends included: 1, 2, 3
last = doors.close(operator, p.id)
if last is Ok then break
for p in paddocks # paddocks: Array<Paddock> where it.length in 0 to 40
…
# park/enclosures.brass — body excerpt
any p in ps where p is OpenPaddock and p.species in {Species.Rex}
all p in ps where p.status in {Enclosure.Secured}
- Every loop is bounded.
forwalks a range or an array whose type caps itslength. - No
while, no recursion. breakleaves the nearest loop and carries no value.anyandallreturn aBool. Over an empty array,anyis false andallis true.
Functions
# park/enclosures.brass
module park.enclosures
interface
fn ration(species: Species) -> Grams # declaration: types and promises
body
fn ration(species) = # body: parameter names only
if species in {Species.Goat} then 2000
else 50000
- No
return. The last line is the result. - A call whose result is discarded must yield
Done, after?if there is one. Discarding any other value is rejected. - A long declaration may continue while a
(,<or{is open. - No overloads and no function values.
Promises: before, after, example
A function can state a general condition, or give one concrete call and the answer it must give.
# park/enclosures.brass — declaration excerpt
fn secure(p: OpenPaddock) -> SecuredPaddock
after result.id == p.id
fn ration(species: Species) -> Grams
example ration(Species.Goat) == 2000
| Line | Who makes it true | Checked |
|---|---|---|
before |
the caller | proven at check time |
after |
the body, on success | proven at check time |
example |
the body | run by brass check |
- A function's
beforeandafterare proved, or the program is rejected. An access operation's authority checks and reply promises are enforced when the program runs instead. The runtime checks proved clauses again as the code runs; a clause proved from an enforced fact, such as anallow'safter, reports enforced (spec §6). - Promises are optional. A function without them is still checked for types, loops, authority and states.
- Conditions use a small set of operations: integer
+ -(and* / %in limited cases), comparisons,in,and or not. No calls, floats,anyorall. exampleonly on a function that returns noOutcomeand takes no access.
Types with conditions: where and is
# park/enclosures.brass — declaration excerpts
type PaddockId = Int where it in 1 to 40
type OpenPaddock = Paddock where it.status in {Enclosure.Open}
type Pen = Array<Paddock> where it.length in 0 to 40
# park/doors.brass — body excerpt
if p is enclosures.OpenPaddock then
Ok(enclosures.secure(p)) # p counts as an OpenPaddock here
else Err(FailureCode.Conflict, "paddock is not open")
itis the value being described; its fields areit.field.x is Tis the only test that gives a value a named type. There are no casts.intests membership:x in 1 to 40,x in {A, B}.- A field test narrows the field only.
p.status in {Enclosure.Open}does not makepanOpenPaddock.
Errors: Outcome and ?
# park/doors.brass — body excerpt
fn lockdown_as(doors, operator, paddock) =
let p: enclosures.Paddock = doors.enclosure(paddock)? # the Ok value, or return the Err
if p is enclosures.OpenPaddock then
doors.close(operator, p.id)?
Ok(enclosures.secure(p))
else Err(FailureCode.Conflict, "paddock is not open")
- A function that can fail says so:
-> Outcome<T>. No exceptions, notry. ?passes anErrup. It works only inside a function that returnsOutcome.- Test without passing up:
if r is Ok then …,if r is Err then …. - Failure codes:
Unauthorized InvalidInput Conflict Unavailable ResourceExhausted Other Unknown NotFound. - Nothing is undone. If the door closed and a later step fails, it stays closed.
- Some failures abort the task and can't be caught: overflow on an unranged integer, running out of memory or CPU time, a host reply breaking its
after, a proven promise failing at run time.
Absence: Maybe
# park/enclosures.brass — excerpt
fn signers(first: BadgeId, second: Maybe<BadgeId>) -> Int
fn signers(first, second) =
if second != None and second != first then 2 else 1 # right of and: a plain BadgeId
- Only a
Maybe<T>can be absent. x != Nonemakesxa plainTwhere it holds.x is Noneis rejected.- No unwrap operator and no default value.
State machines
# park/enclosures.brass — excerpts
machine Enclosure
states Secured, Open, Breached
initial Secured
Secured enters Open
Open enters Secured | Breached
fn secure(p) = p with { status enters Enclosure.Secured }
entersis the only way to change a state, and only along a declared move. Every state the value might be in needs a move to the target.- New values start in an
initialstate. A literal can't invent a later one. - Rejected:
p with { status: Enclosure.Secured }. Useenters.
# park/enclosures.brass — a variant of Paddock
type Paddock = { id: PaddockId,
status: Enclosure,
breached_at: Int when it.status in {Enclosure.Breached} }
whenmakes a field exist only in the states listed: required there, absent elsewhere. Its type is a named type.
Authority: access, allow, grants
# park/doors.brass — declaration excerpt
access Doors { paddocks: Set<enclosures.PaddockId>, deadline_ms: Int }
allow close(operator: enclosures.DoorOperator, paddock: enclosures.PaddockId) -> Outcome<Done>
before paddock in paddocks
fn lockdown_as(doors: Doors, operator: enclosures.DoorOperator, paddock: enclosures.PaddockId) -> Outcome<enclosures.SecuredPaddock>
# system.brass — grant excerpt
grants park.doors.lockdown_request: doors = Doors { paddocks: {1, 2, 3}, deadline_ms: 30000 }
- A function reaches outside only through an access parameter. No parameter, no effect. No global connections.
allowdeclares one operation the host implements. It always returnsOutcome.- Call it as
doors.close(operator, p.id). - Accesses are created only by
grantsinsystem.brass. They can't be stored, returned or put in a record. - A
beforethat names a bound, likepaddock in paddocks, is checked by the runtime on every call.
The system file
# system.brass
system park
requires std = brass.std v0.2
grants park.doors.lockdown_request: doors = Doors { paddocks: {1, 2, 3}, deadline_ms: 30000 }
limit cpu_ms 5000
limit memory_bytes 268435456
limit transcript_bytes 67108864, drop oldest
limit impact_modules 4
| Line | Meaning |
|---|---|
requires |
an exact release of an outside dependency |
grants |
an entry point and the access it gets; : none if it needs none |
limit cpu_ms, memory_bytes, transcript_bytes |
required; the first two abort the task when exceeded |
limit impact_modules |
optional review notice when a change reaches too many modules |
brass writes a lock file beside the system file. Nobody edits it by hand.
Holes
# park/doors.brass — body excerpt
doors.close(@@operator, p.id)?
@@name marks an expression not written yet. The program is rejected until it's filled; brass check names the type that fits.
What brass reports
| Command | What it does |
|---|---|
brass check |
checks the whole program and runs every example |
brass info <name> |
one declaration: its promises, their labels, who uses it |
brass show <module> … |
the declarations of the modules named, exactly as written |
brass apply |
applies a patch, checking it before anything is written |
Every promise is labeled:
- proven: the checker proved it before the program runs.
- enforced: the runtime checks it on every call.
- assumed: nothing checks it.
Not in Brass
| Instead of | Brass has |
|---|---|
| classes and inheritance | records and projects to |
| exceptions | Outcome<T> and ? |
| null | Maybe<T> |
return |
the last line |
while, recursion |
bounded for |
match |
if with is and in |
| mutation | with |
| casts | x is T |
| global services | access parameters |
pub / private per item |
interface and private sections |
"a" + "b" |
"a" ++ "b" |
| overloading, macros | one form for each thing |
Coming from Python
- Every
letand parameter has a type. Nothing is inferred from use. - Indentation marks blocks, as in Python, but there is no
:at the end of a line. ifis an expression and the last line is the result. Noreturn.foronly. It walks a range,1 to 3(both ends included), or an array with a capped length.- No
Noneby default. Only aMaybe<T>may be absent. - No exceptions. A function that can fail returns
Outcome<T>. - No
print,openorrequests. Anything outside comes in as an access parameter.
Coming from Rust
Outcome<T>is likeResult, and?works much the same. ButErralways holds a fixedFailureCodeand a message.Maybe<T>is likeOption, withoutunwrapormatch. Test with!= None.- No borrowing or mutation. Values are immutable;
withmakes an updated copy. - Enums carry no data, except
OkandErr. - Overflow never wraps, in any build. On an integer without a range, it aborts the task.
- No traits, closures or function values. Behavior is a plain function.