Modules
Files as modules, where the compiler finds them, imports, pub, where impls go, and how each module becomes its own C file.
A file is a module. A program is its main file and every module that file imports, directly or not.
// file: main.calximport shapes.{Square, area_of}
fn main() { let s = Square { side: 3.0 } println("{area_of(s)} {s.side}")}
// file: shapes.calxpub struct Square { pub side: f64}
pub fn area_of(s: Square) -> f64 { return s.side * s.side}9.0 3.0
The examples on this page are several files each. A line // file: NAME starts each one, and the first is the one given to calx run.
Where modules are
Section titled “Where modules are”Module a.b.c is the file a/b/c.calx under the source root. The source root is the main file’s folder, unless the main file starts with a module line, as in module game.main. Then the root is the folder that holds game/.
// file: game/main.calxmodule game.main
import game.world.{spawn}
fn main() { println("{spawn(3)}")}
// file: game/world.calxmodule game.world
pub fn spawn(n: i32) -> i32 { return n * 2}6
- A file in a folder starts with its
moduleline, and the line must match the file’s place. A file at the root can leave the line out, and its name is the module’s name. This way any file can be checked on its own, as editors do, and still find the root. - Module and folder names match exactly, case included, on Windows as on Linux.
- Imports can’t form a cycle. The error names the modules in the cycle.
The standard library
Section titled “The standard library”Module std.x is std/x.calx under the Calx home (CALX_HOME, the calx command), wherever the program is. It’s imported like any module: import std.io.{read, write}. std is the standard library’s name, so a program’s own modules can’t use it. Its modules have a page each (std.io).
They’re compiled like the program’s own modules, each to its own C file, which the object cache keeps, so using one costs a build almost nothing after the first.
Importing
Section titled “Importing”import a.b.{X, Y} brings the pub items X and Y of module a.b into this one, to use as if they were declared here.
- Each imported name must be used, and it can’t be a name this module declares or has already imported, since there’s no shadowing.
import a.b.{X as Y}bringsXin asY, for two modules that use one name. An item is imported once, under one name, andX as Xis an error.
// file: main.calximport geo.{area as square_area}import circle.{area}
fn main() { println("{square_area(2.0)} {area(1.0)}")}
// file: geo.calxpub fn area(side: f64) -> f64 { return side * side}
// file: circle.calxpub fn area(r: f64) -> f64 { return 3.0 * r * r}4.0 3.0
import a.balone is an error: say what to import. Reaching items through a module’s name, as inb.f(), isn’t built yet.- A type’s methods and its interfaces’ functions come with the type. Once a module has a
Square, itspubmethods work, and so do the functions of everypubinterface it implements, without importing anything more.
What other modules can see:
| Declared | Seen from other modules when |
|---|---|
| A function, struct, enum, interface or constant | It’s pub |
| A struct’s field | The field is pub |
A function in an impl block |
It’s pub |
| An enum’s variants | Always, with the enum |
| An interface’s functions and constants | Always, with the interface |
A private field can be read and set only in its module. A literal in another module can leave it out only if it has a default, so a struct with a private field and no default can be built only in its own module, by the functions it provides:
// file: main.calximport bank.{Account}
fn main() { var a = Account.open() a.deposit(5) println("{a.balance()}")}
// file: bank.calxpub struct Account { cents: i64}
impl Account { pub fn open() -> Account { return Account { cents: 0 } } pub fn deposit(a: mut Account, n: i64) { a.cents += n } pub fn balance(self) -> i64 { return self.cents }}5
// file: main.calximport bank.{Account}
fn main() { let a = Account.open() println("{a.cents}")error: field cents of Account is private to bank}
// file: bank.calxpub struct Account { cents: i64}
impl Account { pub fn open() -> Account { return Account { cents: 0 } }}A pub function’s parameters and result, a pub struct’s pub fields and a pub enum’s variants can’t have a type that isn’t pub, since other modules couldn’t name it. pub items are never reported as unused.
Where impls go
Section titled “Where impls go”- Methods for a type go in the type’s module:
impl Squarebelongs withstruct Square. - An impl of an interface goes in the interface’s module or the type’s. An impl for a primitive, such as
impl Shape for i32, goes in the interface’s.
So there’s one place to look for each impl, and two modules can’t implement one interface for one type in different ways.
Each module becomes one C file, named after it, such as game_world.c. It’s compiled to its own object, and the objects are linked into the program. A module’s file holds:
- the module’s functions;
- a
staticcopy of each generic instance those functions use, so every file compiles by itself, in parallel with the others; - the type definitions, constants and declarations those name, and nothing else.
Since a file holds exactly what it uses, its text changes only when something it uses changes, and the object cache compiles only those files again. The front end still checks the whole program every time.
A function is static unless another module’s file calls it, which lets clang inline it and drop it. Release builds inline across files too, when they link (ThinLTO), so splitting code into modules costs nothing at run time. An item’s C name starts with its module’s: Square in geo.shapes is geo_shapes__Square. calx emit-c prints each file after a line // ---- NAME.c, and -o with a folder writes them there.
Packages (calx.toml) and reaching items through a module’s name are designed but not built yet (spec).