A program is a package
Faber has no bare source file as its unit. A program is a package: a
faber.toml manifest and a source tree, compiled as one analyzed whole. The
manifest names the package, points at the entry module, and selects the target;
the entry module declares main, and that is where the program starts.
Making the package the unit has a consequence a reader can rely on: the
compiler always sees the whole program. Imports resolve against real files, and
nothing depends on the order in which a shell happened to pass arguments.
Names are brought in explicitly — import from "./math" duplica imports exactly
duplica, and public re-exports it when the widening is deliberate.
Module scope is compile-time#
A top-level binding is a compile-time constant. const LIMIT = 255 at module
scope is known while compiling; module-level mutable state does not exist.
Runtime state starts at main and lives in functions. This is the glyph law
extended to the module: = at the top level is a fact, not an initialization
that runs. Because the top level holds no state, a package can be analyzed,
checked, emitted, or tested without running any of it — which is what lets one
package target many backends.
faber.toml
[package]
name = "demo"
version = "0.1.0"
edition = "2026"
[paths]
source = "src"
entry = "main.fab"
[build]
kind = "bin"
target = "rust"
[locale]
locale = "en"fn duplica(int n) → int {
return n * 2
}
main {
print duplica(21)
}$ faber run
42A mutable binding at module scope is rejected, because there is no run time for it to belong to:
var int counter ← 0See Packages with Cista for resolution and the
command line for faber check, faber run and
faber test.