Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Modules

A program can be split into files. Each file has its own top level: names it declares are private unless marked export (fun, let, const, struct, enum), and another file reaches them only through the module, as m.name.

A small shop, in three files:

shop/
├── main.rn
├── data/prices.txt
└── lib/
    ├── cart.rn
    └── money.rn
// lib/money.rn
export struct Money { cents, currency = "USD" }
export struct CurrencyError { message }

export fun format(m) {
    return "{m.cents / 100} {m.currency}";
}

export fun add(a, b) {
    check(a, b);
    return Money { cents: a.cents + b.cents, currency: a.currency };
}

fun check(a, b) {
    if a.currency != b.currency throw CurrencyError { message: "{a.currency} vs {b.currency}" };
}
// lib/cart.rn
import .money;

export let mut items = [];

export fun add(name, cents) {
    items.push({ name, price: money.Money { cents } });
}

export fun total() {
    return items.reduce((sum, it) => money.add(sum, it.price), money.Money { cents: 0 });
}
// main.rn
import lib.cart;
import lib.money as m;

cart.add("book", 1250);
cart.add("pen", 199);

print(cart.items.len());       // 2
print(m.format(cart.total())); // 14.49 USD

let eur = m.Money { cents: 500, currency: "EUR" };
print(eur is m.Money);         // true

try {
    m.add(cart.total(), eur);
} catch e is m.CurrencyError {
    print("cannot add: " + e.message); // cannot add: USD vs EUR
}

rune run shop/main.rn runs it from any directory. Two modules may declare the same name: cart.add and money.add do not clash.

Import Paths

import a.b; loads a/b.rn, named from the directory of the file passed to rune, and binds it as b; import a.b as m; binds it as m.

Leading dots make it relative to the importing file: import .money; is money.rn next to it, import ..c.d; is c/d.rn in its parent directory, one more dot per level up. The program’s directory cannot be left:

import ...shared.util; // Error at '...': Module '...shared.util' is outside the program's directory.

Imports come first in a file:

print("start");
import lib.money; // Error at 'import': Imports must come before other statements.

module.dir and module.file

module.dir is the directory of the file it is in, and module.file its path, both absolute. A program uses them to find the files it comes with wherever it is run from:

// main.rn
let prices = fs.readText(fs.join(module.dir, "data", "prices.txt"));
print(prices.trim()); // book 12.50

module is a namespace of each file’s own: in lib/cart.rn, module.dir is the lib directory.

What a Module Allows

Members are bound when the program is compiled, so a mistake is caught before anything runs. A member the module does not export is an error:

import lib.money;
money.check(1, 2); // Error at 'check': Module 'money' has no export 'check'.

A module is not a value:

import lib.money;
print(money); // Error at 'money': Module 'money' is not a value; use its members, as in money.name.

A module’s variables cannot be assigned from outside:

import lib.cart;
cart.items = []; // Error at 'items': Cannot assign to a member of module 'cart'.

The array or object a let mut export holds may be changed, as cart.items.push(...) would; one held by an immutable let may not:

// lib/consts.rn
export let tags = ["sale"];
import lib.consts;
consts.tags.push("new"); // Error at 'tags': Cannot mutate immutable variable 'consts.tags'.

Importing a module shadows a built-in namespace of the same name in that file, so import lib.math; hides math.floor. Import it as another name to keep both:

// lib/math.rn
export fun floor(x) { return "mine"; }
import lib.math as mymath;

print(mymath.floor(1.5)); // mine
print(math.floor(1.5));   // 1

Running Order

Each file runs once, after the files it imports. An import cycle is an error:

// lib/a.rn
import .b;
export let x = 1;
// lib/b.rn
import .a;
export let y = 1;
import lib.a; // Error at '.': Import cycle: lib/a.rn -> lib/b.rn -> lib/a.rn.

An imported file may only declare things at its top level (fun, let, struct, impl); statements such as print(...) belong in the entry file:

// lib/noisy.rn
print("loaded"); // Error at 'print': Only declarations can be at the top level of a module.