Bait Documentation

May 7, 2026 ยท View on GitHub

Documentation of the standard library is work in progress ๐Ÿšง

Entry Point

The entry point of any program is the main function. It is automatically called when the program starts.

fun main() {
	println('hello world')
}

Functions

fun add(a i32, b i32) i32 {
    return a + b
}

The return type can be omitted, if the function returns nothing.

fun print_monday() {
    println('monday')
}

Deprecation of Functions

Functions can be marked as deprecated to trigger compiler warnings when they are used.

@deprecated('Use bar() instead.')
@deprecated_after('2023-06-12')
pub fun foo() {}

Calling this function will cause a message like:

warning: function "foo" will be deprecated after 2023-06-12; Use bar() instead.

Variables

Variables are declared with the := operator.

greeting := 'hello'
num := 17

By default variables are immutable. Use the mut keyword if their value has to change.

mut count := 2
count = 3

Constants

Constants can only be in the top level scope and are declared with the const keyword.

const ANSWER := 42

Comments

Use // to start a comment.

// Line comment
x := true // Inline comment

Unlike many other languages, Bait does not support block comments (see syntax design).

Types

Primitive Types

bool

string

i8 i16 i32 i64
u8 u16 u32 u64

f32 f64

Numbers

num := 123

Integers default to i32 and floats default to f64. To get a different type, see as casting.

Number Promotion

These implicit type promotions are also supported:

u8 โ†’ u16 โ†’ u32 โ†’ u64
    โ†˜     โ†˜     โ†˜   โ†˜
i8 โ†’ i16 โ†’ i32 โ†’ i64 โ†’ f32 โ†’ f64

Base Prefixes

It's also possible to define numbers with a different base by using a prefix:

NameBasePrefix
Binary20b
Octal80o
Hexadecimal160x
b := 0b1010
o := 0o755
h := 0x12a

Casting

To perform type casting, use var as Type:

n := 123 as i16

Arrays

An array is a zero-indexed list of values of the same type.

names := ['John', 'Max']

mut arr := []i32
arr = [1, 2, 3]

arr.push(0)
arr[3] = 4

Array Initialization

The array type is inferred from the first element:

enum MyEnum {
    A
    B
}

arr := [MyEnum.A, .B]

It's also possible to preallocate an array with a fixed length:

a := []i32{ length = 3 }

Array Slicing (Range Indexing)

nums := [0, 10, 20, 30]

println(nums[1..3]) // [10, 20]
println(nums[..3]) // [0, 10, 20]
println(nums[2..]) // [20, 30]
println(nums[..]) // [0, 10, 20, 30]

Maps

You can initialize a map with the short literal syntax:

grades := map{
    'Alice': 92
    'Bob': 87
    'Charlie': 95
}

Or create an empty map and assign values later:

mut airports := map[string]string
airports['lax'] = 'Los Angeles'
airports['jfk'] = 'New York'

Result Type

Result types are used for functions that possibly return an error. They are declared by prpending ! to the type name: !Type.

For more information see Error Handling.

Error Handling

Propagation

fun get_res() ! {
    return error('failure')
}

fun other() ! {
    get_res()!
}

Stop Execution

fun get_res() ! {
    return error('failure')
}

fun stop_on_error() {
    get_res() or {
        exit(1)
    }
}

Provide a default value

fun int_or_fail() !i32 {
    return error('no num')
}

fun main() {
    num := int_or_fail() or { 42 }
}

Importing packages

Packages are imported using the import keyword. All public symbols can be accessed using the package name as prefix:

import os

fun main(){
    // Call `read_file` using the `os.` prefix
    t := os.read_file('myfile.txt')
}

Please note that imports are on a per-file basis.

Selective imports of specific symbols are not supported.

Subpackages

For subpackages only the last part of the name is used as prefix:

import term.color

fun main(){
    r := color.bold("hello")
}

This is regardless of the nesting depth, so a function from foo.bar.baz would be called like baz.func().

Import aliasing

Any imported package can be aliased using the as keyword. This is commonly used to avoid name conflicts.

import hash.crc32
import custom.crc32 as mycrc32

Package Declaration

package my_pkg

Symbol Visibility

By default all symbols are private to the package they are declared in. This includes functions, constants, structs and enums.

To allow other packages to use a symbol, prepend the pub keyword:

package my_pkg

pub fun my_public_function() {}

fun my_private_function() {}

For type aliases the visibility is derived from the underlying type:

package other_pkg

type MyInt := i32 // MyInt is usable in other packages

pub struct PubStruct {}
type PubAlias := PubStruct // PubAlias is public just like PubStruct

struct PrivStruct {}
type PrivAlias := PrivStruct // PrivAlias is private

If

a := 5
b := 10
if a > b {
    println('a is greater than b')
} else if a < b {
    println('a is less than b')
} else {
    println('a equals b')
}

Match

day := 0
match day {
  5 { println('Saturday') }
  6 { println('Sunday') }
  else { println('Not weekend') }
}

For Loops

for/in

arr := [1, 2, 3]
for x in arr {
  println(x)
}

mut mymap := map[string]string
mymap['a'] = 'A'
mymap['b'] = 'B'

for key, val in mymap {
}

Conditional for

mut i := 100
for i >= 0 {
    if i % 2 == 0 {
        continue
    }
    println(i)
    i -= 1
}

for true {
    break
}

Classic for

for i := 0; i < 10; i += 1 {
    println(i)
}

Labelled break and continue

By default break and continue affect the innermost loop.
To break or continue an outer loop, you can use labels:

outer: for i := 0; i < 5; i += 1 {
    for j := 0; j < 5; j += 1 {
        if i == 2 {
            break outer
        }
        if j == 2 {
            continue outer
        }
        println(j)
    }
}

The above code will print:

0
1
0
1

Structs

struct Rect{
    width i32
    height i32
}

r := Rect{
    width = 5
    height = 10
}

Struct fields might be assigned default values:

struct Adult {
    name string
    age i32 := 18
}

Methods

struct Rect{
    width i32
    height i32
}

fun (r Rect) area() i32 {
  return r.width * r.height
}

Access Modifiers

By default struct fields are private and immutable. The access modifiers for each field can be changed using the following options:

pub struct Foo{
	a i32 // private immutable (default)
	mut b i32 // private mutable
    pub c i32 // public readonly
	pub mut d i32 // public readonly but mutable in the parent package
	global e i32 // mutable by other packages
}

Required Fields

struct FooBar {
    @required a i32
    b i32
    @required
    c i32
}

Enums

enum Language {
  english
  german
  french
}

mut lang := Language.french
lang = Language.english

If you want to compare a enum value to an integer, you have to do a explicit cast:

enum Color {
    red
    green
}

if 1 == Color.green as i32 {
}

Enum fields may be declared with a custom integer value:

enum AnsiColor {
    black := 30
    red // 31
    green // 32
}

Type Declarations

Type Alias

type NewType := ExistingType

Function Type

Type aliases can also name a specific function signature:

type CheckInt := fun (i i32) bool

// You can use the alias like any other type, for example as parameter
fun check_nums(nums []i32, c CheckInt) {
    for n in nums {
        c(n)
    }
}

// This implicitly matches the signature of `CheckInt`
fun greater_two(n i32) bool {
    return n > 2
}

// You can use `greater_two` everywhere, where `CheckInt` is expected
check_nums([2,3,4], greater_two)

Also take a look at the complete example.

Sum Type

struct Triangle{}

struct Rectangle{}

struct Circle{}

type Shape := Triangle | Rectangle | Circle

Assert and Unit Testing

Unit testing is built right into the Bait Compiler with the test command.

Test files are recognized by the _test.bt suffix and are ignored for normal compilation. They may contain at least one test function. These are identified by a test_ name prefix.

Inside the test functions use assert some_expr to check wheter this Expr is true. Failed asserts will result in an error and test failure.

For example a really simple test:

// my_test.bt

fun sum(a i32, b i32) i32 {
    return a + b
}

fun test_sum() {
    assert sum(1, 2) == 3
}

Test files can right now not be scoped to the package they are in. Everything has to be imported.

Execute a Function before and after all Test Cases

There are two special functions testsuite_begin() and testsuite_end(), that can run code before/after all test cases.

This is for example useful to create and clean up temporary directories.

Attributes

Various attributes are supported that change the behaviour of functions, struct fields and other statements. They are defined with @name or @name('value') before the statement they should apply to.

List of all supported Attributes

Apply to functions:

NameDescriptionValue
@deprecatedMarks a function as deprecated.Custom message (opt)
@deprecated_afterMark as deprecated after a certain date.Date (req)
@exportExport a function under a different name.Name (req)
@overloadUse a method to overload the given operator.Operator (req)

Apply to struct fields:

NameDescriptionValue
@requiredThe field must be initialized with a value.-

Apply to package declaration:

NameDescriptionValue
@silent_mismatchSuppress the package mismatch info message.-

Compile Time Code Evaluation (Comptime)

Compile Time Pseudo Variables

Bait supports a few pseudo variables, which are replaced by their actual values during compilation. They are all of the type string.

VariableDescriptionExample
$PKGCurrent package namemain
$ABS_FILEAbsolute file path/path/to/lib/file.bt
$FILEFile path relative to working dirlib/file.bt
$DIRAbsolute path to file's directory/path/to/lib
$LINELine number of the variable123
$FILE_LINERelative path and the linetests/my_test.bt:27
$FUNCurrent function nametest_read_line
$BAITEXEAbsolut path to the Bait compiler/path/to/bait/bait.js
$BAITDIRDirectory of the compiler/path/to/bait
$BAITHASHThe compiler's short commit hash5e7fd6e

They are useful for running external tools or debugging. For example:

eprintln('error in file ${$FILE}, line ${$LINE}, function ${$PKG}.${$FUN}')

Conditional Compilation

$if C {
    println('hi from c backend')
} $else {
    println('hi from non-c backend')
}

Important

Infix and prefix operations are not yet implemented for compile time ifs. Support for combining conditions using and, or and not will be added in a future version.

The following conditions are currently supported:

BackendOperating System
C, JSLINUX, WINDOWS

Static Variables

While Bait has no support for global variables, you can use static package variables for a similar purpose.

If marked as public, they are mutable from everywhere:

// demo.bt
package demo

pub static shared_var := 123
// main.bt
import bait.test_pkgs.demo

demo.shared_var += 7
println(demo.shared_var) // 130

Calling JavaScript from Bait

JS Imports and Declarations

It's possible to import JavaScript packages and call JS code from Bait.

import 'fs' as #JS.fs

fun #JS.fs.existsSync(path #JS.String) #JS.Boolean

fun main() {
    x := #JS.fs.existsSync('hello_world.bt'.str) as bool
    println(x)
}

Embed Raw Code

JS code can be embeded using the #JS. prefix and is only allowed in .js.bt files.

Calling Bait from JavaScript

Bait functions can be exported to JS using the export attribute:

package lib

@export('func')
fun some_func() {}

In the generated code, this won't change the function name but add the line module.exports.func = lib__some_func.

Appendix A: Keywords

Bait has 29 reserved keywords:

and
as
assert
break
const
continue
else
enum
false
for
fun
global
if
import
in
interface
is
match
mut
not
or
package
pub
return
static
struct
true
type
typeof

Appendix B: Operators

OperatorDescriptionApplicable Types
+additionintegers, floats, strings
-subtractionintegers, floats
*multiplicationintegers, floats
/divisionintegers, floats
%remainder / modulointegers
~bitwise NOTintegers
&bitwise ANDintegers
|bitwise ORintegers
^bitwise XORintegers
<<left shiftintegers
>>right shiftintegers
notlogical NOTbools
andlogical ANDbools
orlogical ORbools
==equalsall
!=not equalsall
<less thanintegers, floats
<=less than or equalintegers, floats
>greater thanintegers, floats
>=greater than or equalintegers, floats

Assignment operators:

:=  =
+=  -=
*=  /=  %=

Precedence

Highest to lowest:

  • not
  • * / % & << >>
  • + - | ^
  • == != < <= > >=
  • as is
  • and or