13 — Modules and Imports
Chapter goal: organise code across files and namespaces. Reference:
../language-reference/modules.md.
Files are modules
Any .aura file can be imported. A file imported as a module needs no main.
// lib.aura
def greet(name: str) -> str {
return "hi " + name
}
const VERSION = "1.0"
// app.aura
import lib
from lib import greet as g
def main() {
print(lib.greet("ana")) // hi ana
print(g("bob")) // hi bob
print(lib.VERSION) // 1.0
}
import lib binds the file lib.aura; import pkg.util binds pkg/util.aura. The runtime installs an import hook that maps the dotted Aura path to the sibling file.
The module declaration
module Name { ... } creates a namespaced group whose functions are static. Members are private to the declaring file unless marked export:
module Greeter {
export def hello(name: str) -> str {
return "hi " + name
}
let mut count = 0
export def bump() -> int {
count = count + 1
return count
}
}
def main() {
print(Greeter.hello("ana")) // hi ana
print(Greeter.bump()) // 1
print(Greeter.bump()) // 2
}
Rules:
exportprecedes a named declaration (def,class,trait,enum,type,let,const, a nestedmodule) or introduces a re-export.- A non-exported member is mangled in the generated Python, so privacy is enforced at runtime too; reaching it from outside is
E308. - Module state is not writable from outside (
E303); mutate it through an exported function. - A
maininside a module body isE312.
module M {
def hidden() -> int { return 1 }
}
def main() {
print(M.hidden()) // E308: 'hidden' is not exported from module 'M'
}
Dotted module names
module App.Services { ... } nests, reached as App.Services.member:
module Outer.Inner {
export def value() -> int { return 3 }
}
def main() { print(Outer.Inner.value()) } // 3
Import spellings
| Form | Example | Binds |
|---|---|---|
| Module | import stdlib.math | stdlib.math path |
| Module alias | import stdlib.math as m | m |
| Names | from stdlib.math import sqrt, PI | sqrt, PI |
| Name alias | from stdlib.math import sqrt as root | root |
| Brace form | import stdlib.math { sqrt, PI } | sqrt, PI (≡ from) |
| Wildcard | from stdlib.math import * | all public names |
| Multiple | import a, b as c | a, c |
import stdlib.math as m
from stdlib.math import sqrt, PI
def main() {
print(m.sqrt(16)) // 4.0
print(sqrt(36)) // 6.0
print(PI) // 3.141592653589793
}
The brace form cannot be combined with as on the same statement. There is no :: separator — module paths are dotted. * is written only as from module import *.
Facades (re-exports)
A module can re-export symbols from sibling files. Put the facade in a folder named after itself:
App/
App.aura module App { export Components, Utils }
components.aura class Components { ... }
utils.aura def double(...) / const VERSION
main.aura import App
// App/App.aura
module App {
export Components, Utils
}
// main.aura
import App
def main() {
print(App.Components("header").describe())
print(App.Utils.double(21))
print(App.Utils.VERSION)
}
An explicit source is accepted with export Name from "module". The path must be a plain dotted name; separators, .. and absolute paths are rejected. An unresolved re-export is E313.
Name resolution
Outside a module, a bare name resolves through the local scope, then the enclosing function, then top-level declarations, then imports, then a sibling .aura file, then a py. host module. Inside module M, M’s own members win. UNSPECIFIED: Aura does not diagnose an ambiguous import; the emitted Python binding decides (“last binding wins”).
What you learned
- Any
.aurafile is importable; imported files need nomain. module Name { export ... };exportand privacy (E308,E303,E312).- Dotted modules; all import spellings; facades and re-exports.
- Roughly how names resolve.