Skip to content
All guides

learn

Modules and packages

A module is one .bq file. A package is a directory with buraaq.pkg. That is the whole module system for the programs you write on day one.

You do not write mod.rs. You do not write __init__.py. You do not write a CMake graph so two files can see each other. The path is the name.

This is how Buraaq stays Python-shaped: the default path still emits one native binary. Optional scripting compiles the same language through clang for a fast edit loop. The files are just how you think.

Why this beats the usual tax

What other stacks make you doWhat Buraaq does
C / C++: headers, include guards, a build fileFile under src/ is enough
Python: __init__.py, sys.path, a venvburaaq.pkg + a binary
Rust: mod declarations, use crate:…Path is the module; use sibling.item
Go: package clause must match the directoryOptional module line, must match the path
JS: import paths, bundlers, node_modulesUnique std.* names import themselves

The compiler frontend that performs this resolution is itself written in Buraaq (lexer, parser, names, MIR, LLVM text). Install covers winget, the Release zip, and the clone scripts.

Path is the module name

FileModule
src/main.bqmain
src/tx.bqtx
src/account.bqaccount
src/http/server.bqhttp.server
src/lib.bqlib

Optional:

module account

If you write that line, it must match the path or the compile fails. Most files omit it.

Auto-import: stay in the program

In a project, unique stdlib names import themselves. If only one stdlib module exports println, you do not write use. Drop the std. prefix when you do: use io, use keel.

fn main() {
    name = "Ada"
    println("Ledger for {name}")
    println(5000)
}

That is a complete program. println comes from std.io (text, int, float, or bool). page, api, and run come from std.keel. hold, stow, zeros, dot: same rule.

Auto-import does not pull in your own modules. A name that exists in two stdlib modules is not unique (connect, show, keep); write use db or use net. A name you defined locally wins over std. std.service is keel, so page / run auto-import.

You still write use when you want it explicit:

use io
use keel.{page, api, run}
use std.io.println

Ceremony is optional. The program is not.

Your files: use the sibling

Stdlib can auto-import. Your modules cannot: that is deliberate. You name the dependency so the graph stays visible.

src/tx.bq:

pub struct Tx {
    amount: int
    note: text
}

pub fn signed(amount: int, note: text) -> Tx {
    Tx { amount: amount, note: note }
}

src/main.bq:

use tx.{Tx, signed}

fn main() {
    t = signed(5000, "payroll")
    println(t.note)
    println(t.amount)
}
buraaq run

Default visibility is private to the file. Export with pub or the other module cannot see it.

pub fn listen(addr: text) -> Server {
    ...
}

fn helper() {
    ...
}

use tx.{Tx, signed} only works for pub items.

A complete two-module compile

This is the shape the compiler tests: two files, one package, one native binary.

src/util.bq:

pub fn add(a: int, b: int) -> int {
    a + b
}

src/main.bq:

use util.add

fn main() {
    print(add(2, 3))
}

No extra config. buraaq.pkg already named the package when you ran buraaq new.

Nested directories

src/http/server.bq    →  module http.server
use http.server.listen

The dots follow the folders. That is the whole convention.

Cycles are errors, not mysteries

error: cyclic module dependency
  main → account → tx → account

Break the cycle by putting shared types in a third file (src/types.bq) that both import. The compiler prints the cycle. It does not guess.

buraaq.pkg

[package]
name = "ledger"
version = "1.0.0"
entry = "main"
type = "bin"

That is the only config file for a normal app. Stdlib (std.*) is always available. You do not add it as a dependency.

buraaq.lock pins versions when you have external packages. You do not need a hosted registry to write a ledger.

Packages you can fetch today

The public index is the repo tree packages/index.json (example: {"hello":"0.1.0"}).

buraaq add hello
buraaq index

add copies packages/hello/0.1.0/pkg.bq into .buraaq/cache. index serves that tree on :7423. buraaq fetch URL copies a file:// or HTTP pkg.bq. Set BURAAQ_REGISTRY if you point at another tree. Hosted packages.buraaq.dev is next, not a 1.0 requirement.

Tests live beside the code

tests/smoke.bq

Or a test "…" { expect … } block in src/. See Testing.

What you never write

  • A second language to glue files together
  • Lifetime parameters so module A can mention a type from module B
  • A Docker build stage so the modules exist on a server

The package compiles to one native executable. Ship hashes that executable. Modules are how you think. The binary is what the OS runs.

Next: take the banking CLI you have been extending and split it, then serve it.