burn / docs

Language

Keywords and built-in types

Every keyword and built-in type of Burn, with an example. Editors show the same text when you hover over one, and go to it when you go to its definition (lib/doc/language.md in the Burn repository).

def

keyword: starts a definition of a new type: def type, def struct, def interface, def enum or def annotation. Everything Burn knows about a type comes from one def.

def type Point {
    int x
    int y
}

def enum Color { Red, Green, Blue }

print(Point { x: 1, y: 2 }, Color.Red)

More: types

type

keyword: def type Name { ... } defines a record type: named fields, created with a literal and compared by value. def type Name = Other gives an existing type another name.

def type Person {
    string name
    int age = 0
}

def type UserId = int

var ada = Person { name: "Ada", age: 36 }
UserId id = 7
print(ada.name, ada.age, id)

More: types

struct

keyword: def struct Name(params) { ... } defines a struct: an object with a constructor, fields, methods and static members, created with new. Structs are passed by reference. : Parent(args) extends another struct and :: Interface implements an interface or an abstract struct.

def struct Counter(start: int) {
    int count = start

    fun add(n: int) {
        count += n
    }
}

var c = new Counter(10)
c.add(5)
print(c.count)

More: structs and interfaces

interface

keyword: def interface Name { ... } lists the methods a struct must have. A struct declares that it implements it with ::, the compiler checks that every method is there, and calls through the interface are dispatched to the struct's method.

def interface Shape {
    fun area(): float
}

def struct Square(side: float) :: Shape {
    fun area(): float {
        return side * side
    }
}

Shape s = new Square(3.0)
print(s.area())

More: structs and interfaces

enum

keyword: def enum Name { ... } defines a type with a fixed set of variants. Variants can carry data, and match takes them apart.

def enum Shape {
    Circle(radius: float),
    Rect(w: float, h: float)
}

fun area(s: Shape): float {
    return match s {
        Shape.Circle(r) => 3.14159 * r * r
        Shape.Rect(w, h) => w * h
    }
}

print(area(Shape.Rect(2.0, 3.0)))

More: enums

annotation

keyword: def annotation Name { ... } defines an annotation, which is written as @Name(...) in front of a definition and read at runtime with annotationsOf.

def annotation Route {
    string path
    string method = "GET"
}

@Route("/users", method: "POST")
def struct CreateUser(name: string) {
}

for a in annotationsOf(new CreateUser("ada")) {
    if (a is Route) {
        print(a.method, a.path)
    }
}

More: annotations

abstract

keyword: def abstract struct defines a struct that cannot be created with new, only extended. Its abstract fun methods have no body; every struct that extends it with :: must write them.

def abstract struct Animal(name: string) {
    abstract fun sound(): string

    fun describe(): string {
        return "${name} says ${sound()}"
    }
}

def struct Dog(name: string) :: Animal(name) {
    fun sound(): string {
        return "woof"
    }
}

print(new Dog("Rex").describe())

More: structs and interfaces

static

keyword: a static field or method belongs to the struct itself, not to its objects, and is used as Name.member. def static struct defines a struct that only has static members, like a namespace.

def struct Temperature(celsius: float) {
    static var created = 0

    static fun freezing(): Temperature {
        return new Temperature(0.0)
    }
}

var t = Temperature.freezing()
print(t.celsius, Temperature.created)

More: structs and interfaces

new

keyword: creates an object of a struct by calling its constructor: new Name(args).

def struct Point(x: int, y: int) {
}

var p = new Point(3, 4)
print(p.x + p.y)

More: structs and interfaces

destroy

keyword: destroy name(args) destroys the object in a variable now instead of when it is no longer used. If the struct defines fun destroy(...), that function runs first with the arguments. Using the variable afterwards is a compile error until a new object is assigned to it.

def struct File(path: string) {
    fun destroy(reason: string) {
        print("closing", path, "because", reason)
    }
}

var f = new File("data.txt")
destroy f("done")

More: structs and interfaces

fun

keyword: declares a function, a method inside a struct, or an anonymous function used as a value. Parameters are name: Type; the result type comes after : and can be left out when the body makes it clear.

fun add(a: int, b: int): int {
    return a + b
}

var twice = fun(x: int): int {
    return x * 2
}

print(add(2, 3), twice(21))

More: functions

var

keyword: declares a variable. Its type comes from the value (var n = 3 is an int) or is written after :. A variable can also be declared type first, without var: int n = 3.

var count = 3
var ratio: float = 2.5
count += 1
print(count, ratio)

More: variables

const

keyword: declares a constant: a name for a value that never changes.

const LIMIT = 10
const NAMES = ["ada", "alan"]
print(LIMIT, NAMES[0])

More: variables

if

keyword: runs a block when a condition is true; else and else if add the other cases. The condition must be a bool, and checks such as x is T or x != null narrow the type of x inside the block (smart casts).

var n = 7
if (n > 10) {
    print("big")
} else if (n > 5) {
    print("medium")
} else {
    print("small")
}

More: control flow

else

keyword: the part of an if that runs when the condition is false, and the arm of a match that fits every value that no other arm fits.

var n = 3
if (n % 2 == 0) {
    print("even")
} else {
    print("odd")
}
print(match n { 1 => "one", else => "more" })

More: control flow

while

keyword: repeats a block as long as a condition is true.

var n = 3
while (n > 0) {
    print(n)
    n -= 1
}

More: control flow

for

keyword: loops over a range (0..3, or 0..=3 with the end included), an array, a map or a string, with for x in ... or for i, x in ... for the index too. for (init; condition; step) is the loop of C.

for i in 0..3 {
    print(i)
}
for i, name in ["ada", "alan"] {
    print(i, name)
}

More: control flow

in

keyword: separates the variables of a for loop from what it loops over.

for c in "abc" {
    print(c)
}

More: control flow

return

keyword: ends the function and gives its result, or just ends it in a function without one.

fun sign(n: int): int {
    if (n < 0) {
        return -1
    }
    return 1
}

print(sign(-5))

More: control flow

break

keyword: leaves the innermost loop right away.

for i in 0..10 {
    if (i == 3) {
        break
    }
    print(i)
}

More: control flow

continue

keyword: skips the rest of the loop's body and goes on with the next round.

for i in 0..5 {
    if (i % 2 == 0) {
        continue
    }
    print(i)
}

More: control flow

match

keyword: compares a value with patterns, top to bottom, and gives the result of the first arm that fits. Patterns are values, ranges, enum variants (with their data), types (is string s) and else; if after a pattern adds a guard. A match on an enum must cover every variant.

fun size(n: int): string {
    return match n {
        0 => "zero"
        1..=9 => "small"
        else => "large"
    }
}

print(size(5))

More: match

true

keyword: the bool value true.

var done = true
print(done && !false)

More: types

false

keyword: the bool value false.

var found = false
print(found || true)

More: types

null

keyword: no value. Only nullable types, written T?, can hold it; ?., ?? and != null checks work with them.

string? nickname = null
print(nickname ?? "none")
nickname = "Ada"
if (nickname != null) {
    print(len(nickname))
}

More: errors

import

keyword: loads a module: the standard library ("std/strings"), a file of the project ("utils.bn", "@/net/http"), a package or a bytecode library ("lib.bvmc"). as Name imports it under an alias; imports inside a function or block are only visible there.

import "std/math" as M

print(M.sqrt(16.0))

More: modules

as

keyword: import "module" as Name names a module; value as T converts a value to a type, and value as? T gives null instead of an error when it is not one.

fun show(v: any): string {
    var s = v as? string
    return s ?? "not a string"
}

any n = 41
print((n as int) + 1, show("burn"), show(3))

More: smart casts

is

keyword: value is T checks whether a value is of type T, and inside the if it narrows the value to T (smart cast).

fun describe(v: any): string {
    if (v is string) {
        return "a string of length ${len(v)}"
    }
    return "something else"
}

print(describe("burn"))

More: smart casts

pub

keyword: makes a name of a module visible to the files that import it. Names are public by default; pub says so explicitly.

pub fun greet(name: string): string {
    return "hi " + name
}

print(greet("ada"))

More: modules

priv

keyword: keeps a name private to its module: files that import the module cannot use it. Inside a struct, priv members can only be used by the struct itself.

priv fun helper(): int {
    return 41
}

print(helper() + 1)

More: modules

async

keyword: async fun runs on its own thread when it is called and gives a Future<T> at once; await waits for its result.

async fun double(n: int): int {
    return n * 2
}

var a = double(20)
print(await a + 2)

More: async

await

keyword: waits until the Future<T> of an async fun has its result, and gives it.

async fun answer(): int {
    return 42
}

print(await answer())

More: async

unsafe

keyword: unsafe { ... } marks code the compiler cannot check: raw memory (std/memory), asm blocks and calls to C (@Extern). unsafe fun makes a function that can only be called in unsafe code.

import "std/memory"

unsafe {
    var p = Memory.alloc(8)
    Memory.store64(p, 42)
    print(Memory.load64(p))
    Memory.free(p)
}

More: unsafe

asm

keyword: an asm { ... } block in unsafe code holds assembly for each target in its own section (x86_64, arm64, bvm, js), with an optional else section in Burn for every other target. {name} stands for a variable.

var x = 20
var r = 0
unsafe {
    asm {
        bvm {
            load {x}
            const 2
            iadd
            store {r}
        }
        else {
            r = x + 2
        }
    }
}
print(r)

More: inline assembly

int

type: a 64-bit signed integer, from -9223372036854775808 to 9223372036854775807. Overflow is a runtime error. Literals: 42, 0x1F, 0b1010, 1_000_000. Also written Int, int64 or long.

int n = 0x10
print(n * 2, toString(255, 16))

More: types

float

type: a 64-bit floating point number (IEEE 754 double). Literals: 3.14, 2.5e-3. Also written Float, float64 or double.

float r = 2.0
print(3.14159 * r * r)

More: types

bool

type: true or false, the type of conditions. Also written Bool or boolean.

bool ready = 3 > 2
print(ready, !ready)

More: types

string

type: an immutable sequence of Unicode characters. + joins strings, ${...} inserts a value into one, and len, indexing and for work on characters. Also written String or str.

string name = "Burn"
print("hello ${name}", len(name), name[0])

More: strings

any

type: a value of any type. To use it as a specific type, check it with is (which narrows it) or convert it with as. Also written Any.

any v = "text"
if (v is string) {
    print(len(v))
}

More: smart casts

void

type: the result type of a function that returns nothing. Also written Void.

fun log(msg: string): void {
    print("[log]", msg)
}

log("started")

More: functions

int8

type: an 8-bit signed integer, from -128 to 127. Arrays of it ([int8]) use one byte per element.

int8 t = -100
print(t)

More: types

int16

type: a 16-bit signed integer, from -32768 to 32767.

int16 sample = -1200
print(sample)

More: types

int32

type: a 32-bit signed integer, from -2147483648 to 2147483647.

int32 n = 2000000000
print(n)

More: types

uint8

type: an 8-bit unsigned integer, from 0 to 255, also called byte. Arrays of it ([uint8]) use one byte per element.

[uint8] pixels = [255, 128, 0]
print(pixels)

More: types

uint16

type: a 16-bit unsigned integer, from 0 to 65535.

uint16 port = 8080
print(port)

More: types

uint32

type: a 32-bit unsigned integer, from 0 to 4294967295.

uint32 color = 4294967295
print(color)

More: types

uint64

type: a 64-bit unsigned integer, from 0 to 18446744073709551615.

uint64 hash = 14695981039346656037
print(hash)

More: types

float32

type: a 32-bit floating point number, with about 7 significant digits. Arrays of it use 4 bytes per element.

float32 ratio = 0.5
print(ratio)

More: types

Future

type: Future<T> is the result of calling an async fun: the value T the function will give, which await waits for.

async fun slow(): string {
    return "done"
}

Future<string> f = slow()
print(await f)

More: async