Introduction
Burn standards
These are the conventions the standard library, the compiler written in Burn and the official packages follow. The compiler accepts code that breaks them, but sticking to them means every Burn project looks familiar: you know where the code is, what a name refers to and how to import it.
Package names
A package is named after the place its code lives, so the name is also how to download it:
github.com/owner/project
└──┬─────┘ └─┬─┘ └──┬──┘
domain owner project- Use the real address of the repository.
github.com/ada/colors,gitlab.com/team/parserandgit.example.org/tools/lintare all valid. Never make up a domain:ashdownloads from the name. - The domain is lowercase and has at least one dot (
github.com, notgithub). - Name the project in lowercase words joined by
-:burn-colors,http-server,json5. Avoid_, capital letters and aburn-prefix unless the package is about Burn itself (a formatter plugin, an editor tool). - Keep the owner as the host spells it,
github.com/S42yt/burn-appincluded; package names compare without regard to case, soashfinds it either way. - Name sub-packages after their folder. A workspace member is
<workspace>/<folder>, such asgithub.com/ada/game/common, and a package inside a repository is the repository name plus the path to its folder. - A name says what the package is, not how it is built. Prefer
github.com/ada/markdownovergithub.com/ada/markdown-liborgithub.com/ada/burn-markdown-impl.
The manifest repeats the name:
[package]
name = "github.com/ada/colors"
version = "1.2.0"
kind = "lib"Versions
A version has three numbers, MAJOR.MINOR.PATCH, and can end in a pre-release suffix. Tags add a v in front
(v1.4.0); version in burn.toml is the same number without it (version = "1.4.0"). ash install, burnup and
the lock file read the tags, and both schemes below compare the same way, number by number.
Semantic versions
Most packages use semantic versions: 1.4.0.
- Bump the major version when code that used the package may stop compiling or behave differently, the minor version for new features, and the patch version for fixes.
- Versions before
1.0.0may change anything in a minor release. - Pre-releases add a suffix:
2.0.0-beta.1.
Yearly versions
Burn itself and the toolchain released with it (the compiler, the standard library and bvm) use yearly versions:
YY.RELEASE.PATCH.
26.1.0-experimental-9
│ │ │ └─────┬──────┘
│ │ │ pre-release
│ │ └── patch: fixes to that release
│ └──── release: the first, second, … release of the year
└─────── year: 2026- The first number is the year of the release, written with two digits:
26for 2026. A new year starts again at release1:27.1.0. - The second number counts the releases of that year, and the third the fixes to one release:
26.1.2is the second fix of the first release of 2026. - Pre-releases end in
-experimental-N, counting up from1, before the release is final:26.1.0-experimental-9comes before26.1.0. burnupalso accepts short forms such as26.1and^26.1; see installation.- Packages that move with the year, such as bindings that follow Burn's releases, can use yearly versions too. Libraries with their own pace should use semantic versions, so the major number still says when code breaks.
Burn's releases
Every release of Burn is tagged v<version> and listed in the
changelog.
| Version | Tag | What it is |
|---|---|---|
26.1.0-experimental-1 |
none | the version of the first development builds; it was never published |
26.1.0-experimental-2 |
v26.1.0-experimental-2 |
the first published release and the last whose compiler is written in Rust; it was the stage0 that built the compiler written in Burn for the first time, until the boot compiler in the repository replaced it |
26.1.0-experimental-3 |
v26.1.0-experimental-3 |
the first release written in Burn: the compiler, the command line and the language server run on bvm, and the compiler written in Rust is gone; it builds bvm bytecode and archives only |
26.1.0-experimental-4 |
v26.1.0-experimental-4 |
native executables and JavaScript are back, generated by the compiler written in Burn |
26.1.0-experimental-5 |
v26.1.0-experimental-5 |
low-level code: unsafe, inline assembly for every target and raw memory with std/memory; native executables for arm64; Burn builds itself from the boot compiler in its repository, so building it only needs Rust |
26.1.0-experimental-6 |
v26.1.0-experimental-6 |
modules: import aliases (import "std/memory" as Mem) and imports inside functions and blocks |
26.1.0-experimental-7 |
v26.1.0-experimental-7 |
no Rust: bvm and the runtime are written in Burn, the toolchain is one native executable, and Burn builds from assembly seeds with only a C compiler; calling C with @Extern |
26.1.0-experimental-8 |
v26.1.0-experimental-8 |
the JIT: bvm compiles hot code to x86-64 and arm64 machine code while it runs |
26.1.0-experimental-9 |
v26.1.0-experimental-9 |
editors explain keywords and built-in types on hover; ash init asks step by step |
26.1.0 |
v26.1.0 |
the first stable release of 2026, after the experimental ones |
26.2.0, 26.3.0, … |
v26.2.0, … |
the next releases of 2026; fixes to a release are 26.1.1, 26.1.2, … |
27.1.0 |
v27.1.0 |
the first release of 2027 |
A project names the Burn it was written for with burn = "26.1" in burn.toml, and burnup uses the newest
26.1.x for it (details).
Tags
- Never move a tag after publishing it; tag a new version instead.
burn.lockpins the exact commit, so a moved tag silently gives people different code.
Project layout
Every project has the standard layout:
colors/
├── burn.toml
├── burn.lock written by ash; commit it
├── README.md
├── src/
│ ├── lib.bn the library (or main.bn for an app)
│ ├── palette.bn the module `palette`
│ └── convert/
│ ├── mod.bn the module `convert`
│ └── hsl.bn the module `convert/hsl`
├── tests/ one program per file, run by `burn test`
├── examples/ small programs that show how to use it
└── build/ output of `burn build`, never committedsrc/lib.bnis the public face of a library, andsrc/main.bnis the entry point of an app. Code that imports the package by its name gets thepubdeclarations ofsrc/lib.bn, so put the main API there. Imports are not passed on: a module only sees what it imports itself.- One topic per module. A file holds the types and functions that belong together, such as
palette.bnfor colours andconvert/hsl.bnfor one conversion. Split a file when it covers two topics, not because it is long. - Folders group related modules. Give a folder a
mod.bnwith its main declarations when other code mostly needs those, so it can writeimport "@/convert"; the other files stay importable by their own path (import "@/convert/hsl"). - Extra programs go in
src/bin/: one file for a small tool (src/bin/migrate.bn), or a folder with amain.bnfor a bigger one (src/bin/server/main.bn). - Tests and examples are programs. Name a test after what it covers (
tests/convert.bn,tests/parse_errors.bn) and an example after what it shows (examples/gradient.bn). - Put shared code of several projects in a workspace, with a
commonmember that the others import, rather than copying files between repositories. - Never commit
build/. Add it to.gitignore.
Naming code
| What | Style | Examples |
|---|---|---|
| Files and folders | lowercase, words joined by _ |
smart_casts.bn, src/bin/import_data.bn |
| Types, structs, interfaces, enums, annotations | PascalCase |
Point, HTTPResponse, Shape, @Route |
| Enum variants | PascalCase |
Color.Red, Token.Ident(name) |
| Functions, methods, variables, fields, parameters | camelCase |
readFile, toUpper, lineCount |
Constants and static const values |
UPPER_SNAKE_CASE |
PI, MAX_RETRIES, Limits.MAX_SIZE |
| Type parameters | one capital letter, or a short PascalCase word |
T, K, V, Item |
- Acronyms keep their capitals, as in the standard library:
HTTP,JSON,HTTPResponse,parseJSON,userID. AcamelCasename that starts with one writes it in lowercase:jsonText,httpStatus. - Functions say what they do with a verb:
parse,readFile,addEdge. Functions that only compute a value can be named after it:length,area,distanceTo. - Booleans read as a question:
isEmpty,hasChildren,canRetry,fileExists. - Conversions name the result:
toString,toPoint,asMap; constructors from another value usefrom:Color.fromHex("#ff0000"). - Avoid abbreviations except the usual short ones (
ifor an index,nfor a count,x/yfor coordinates,errfor an error). - Don't repeat the type in its members:
Point.distanceTo, notPoint.pointDistanceTo.
Types
def typefor plain data, values made withName { field: value }that have no behaviour of their own.def structfor objects: values with methods, a constructor (new Name(...)), inheritance or a destructor.def interfacefor an ability that several types share (Shape,Drawable,Reader), anddef abstract structwhen the types also share code.def enumfor a fixed set of cases; give the cases data (Circle(radius: float)) instead of keeping a kind field next to optional fields.- Prefer exact types to
any. Useanyonly at the edge of a program, such as parsed JSON, and convert it to a real type as soon as possible. - Make absence explicit with
T?and handle it where it happens withif (x != null),?.or??.
Visibility
- Mark the API of a module
puband leave helpers without a marker orpriv, so callers only see what they are meant to use (see modules). - Keep fields
privwhen a struct has rules about them, and expose methods that keep the rules. - A library's
src/lib.bnholds its main API. Code that importsgithub.com/ada/colorsshould get what most users need from that one import, and reach into the folders (github.com/ada/colors/convert/hsl) only for the less common parts.
Imports
- One import per line at the top of the file, sorted alphabetically.
- Inside a project, import its own modules with
@/(import "@/convert/hsl"), so moving a file does not break the files that use it. Relative paths ("../palette.bn") are fine inside one folder. - Import other packages by their full name (
import "github.com/ada/colors") and the standard library bystd/<module>(import "std/fs"). - Import only what a file uses; the compiler points out missing standard library imports and offers a fix.
Formatting and comments
- Run
burn fmt(or burnfmt) before committing: four spaces, braces on the same line, one statement per line. The language server formats files too, so editors can format on save. - Keep lines readable: the formatter does not wrap lines, so break long expressions and long argument lists yourself when they get past about 120 characters.
- Document every
pubdeclaration with a Burndoc comment (/** ... */) that says what it does, its parameters (@param) and result (@return), and shows an@examplewhen the use is not obvious.burn docturns them into the documentation of the package. - Write comments for the reason, not the mechanics. Good names explain what code does; comments explain why it does it that way.
Errors
- Report expected failures through the result: return
T?(nullwhen there is nothing), aboolfor success, or a smalldef typewith the value and an error message. The standard library never stops the program on a failed file or network operation, and libraries should not either. - Keep
panicandassertfor bugs: states that should be impossible if the code is correct. - Error messages say what went wrong and what to do, in lowercase without a final period:
cannot read config.toml: the file does not exist.
Tests
- Every library has tests in
tests/; each file is a program that exits with an error when something is wrong, usually throughassertfromstd/testing. - A test checks one area and is named after it. Keep tests independent: one test must not rely on files or state left behind by another.
- Run
burn testbefore tagging a release, andburn checkto type-check every source, test and example.
Before you publish
burn fmthas nothing to change,burn checkreports nothing, andburn testpasses.burn.tomlhas the rightname,version,kindandtarget.- Every
pubdeclaration has a doc comment, and the README shows how to install and use the package. - The repository is tagged with the version (
v1.4.0) and the tag is pushed.
See projects and packages for the manifest, workspaces and ash.