Module Structure
A Reussir package is a tree of source files. The root file declares which children belong to that tree, paths connect items across files, and one #[main]#[main] function marks where an executable starts. This chapter grows the small package from the Rene tutorial into a three-file program, then makes its long paths easier to read with imports.
Rene uses src/lib.rrsrc/lib.rr as a package’s root source file. Create two files below it so the source tree looks like this:
src/├── lib.rr└── geometry/ ├── mod.rr └── rectangle.rr
src/├── lib.rr└── geometry/ ├── mod.rr └── rectangle.rr
A file does not join the package merely because it has a .rr.rr extension. Its parent must declare it with a top-level modmod item. Begin src/lib.rrsrc/lib.rr with:
mod geometry;
mod geometry;
From src/lib.rrsrc/lib.rr, mod geometry;mod geometry; searches first for src/geometry.rrsrc/geometry.rr and then for src/geometry/mod.rrsrc/geometry/mod.rr. The layout above uses the second form because geometrygeometry has a child of its own. Put that declaration and the module’s own items in src/geometry/mod.rrsrc/geometry/mod.rr:
mod rectangle;pub struct Point { x: i64, y: i64}pub fn margin() -> i64 { 1}
mod rectangle;pub struct Point { x: i64, y: i64}pub fn margin() -> i64 { 1}
The nested declaration searches beside its declaring file, finding src/geometry/rectangle.rrsrc/geometry/rectangle.rr. Define the rectangle operations there:
pub fn area(width: i64, height: i64) -> i64 { width * height}pub fn area_with_margin(width: i64, height: i64) -> i64 { area(width + super::margin(), height + super::margin())}
pub fn area(width: i64, height: i64) -> i64 { width * height}pub fn area_with_margin(width: i64, height: i64) -> i64 { area(width + super::margin(), height + super::margin())}
The files now have the module paths geometrygeometry and geometry::rectanglegeometry::rectangle. File location determines those paths; modmod declarations tell the compiler which files to discover. A missing child produces a diagnostic naming both locations that were searched:
error: module 'geometry' declared in .../src/lib.rr has no source file; expected one of: .../src/geometry.rr, .../src/geometry/mod.rr
error: module 'geometry' declared in .../src/lib.rr has no source file; expected one of: .../src/geometry.rr, .../src/geometry/mod.rr
There are no inline mod name { ... }mod name { ... } blocks at present. Use mod name;mod name; and a separate file. For a deeper tree, a name/mod.rrname/mod.rr file may declare further children in exactly the same way.
The root can call the nested function by its path:
fn sample_area() -> i64 { geometry::rectangle::area(6, 7)}
fn sample_area() -> i64 { geometry::rectangle::area(6, 7)}
Inside a module, an unqualified name is looked up in the current module first and then at the package root. Qualified paths have two useful anchors:
root::root::starts at the package root, regardless of the current file;- each leading
super::super::moves up one module.
That is why area_with_marginarea_with_margin can call super::margin()super::margin(): rectanglerectangle and marginmargin both live immediately below geometrygeometry. From the same file, root::geometry::margin()root::geometry::margin() names the same function with an absolute path. Additional super::super:: segments may walk through additional parents, but a path cannot move above the package root.
Within one package, modules may refer to its private or pubpub items. pubpub becomes important at the package boundary: it makes an item part of the interface available to dependent packages. The examples use pubpub for the small API that another package could consume.
Repeated paths are precise but can become noisy. An importimport creates a file-scoped abbreviation for one path:
import path;import path as short_name;
import path;import path as short_name;
Without asas, the final path segment becomes the local name. With asas, the name after asas is used instead. There is no separate aliasalias or type aliastype alias declaration in the current language; path renaming is always written with import ... as ...;import ... as ...;.
Replace src/lib.rrsrc/lib.rr with this complete example:
mod geometry;import geometry::rectangle;import geometry::rectangle as rect;import geometry::rectangle::area as rectangle_area;import geometry::Point as Position;fn three_areas() -> i64 { let first = rectangle::area(6, 7); let second = rect::area_with_margin(6, 7); let third = rectangle_area(6, 7); first + second + third}fn translated_origin() -> Position { Position { x: 10, y: 20 }}#[main]pub fn entry() { let total = three_areas(); let point = translated_origin();}
mod geometry;import geometry::rectangle;import geometry::rectangle as rect;import geometry::rectangle::area as rectangle_area;import geometry::Point as Position;fn three_areas() -> i64 { let first = rectangle::area(6, 7); let second = rect::area_with_margin(6, 7); let third = rectangle_area(6, 7); first + second + third}fn translated_origin() -> Position { Position { x: 10, y: 20 }}#[main]pub fn entry() { let total = three_areas(); let point = translated_origin();}
All four imports use the same mechanism:
| Import | Binds | Example use |
|---|---|---|
import geometry::rectangle;import geometry::rectangle; |
rectanglerectangle |
rectangle::area(6, 7)rectangle::area(6, 7) |
import geometry::rectangle as rect;import geometry::rectangle as rect; |
rectrect |
rect::area(6, 7)rect::area(6, 7) |
import geometry::rectangle::area as rectangle_area;import geometry::rectangle::area as rectangle_area; |
rectangle_arearectangle_area |
rectangle_area(6, 7)rectangle_area(6, 7) |
import geometry::Point as Position;import geometry::Point as Position; |
PositionPosition |
PositionPosition as a type and Position { ... }Position { ... } as its constructor |
An import is a path abbreviation, not a new definition. Its target may therefore be a module, a function, or a record. A record alias works wherever that record path is expected: in a type, in a constructor, and as the qualifier of an enum variant such as L::ConsL::Cons. The original full path remains valid beside the abbreviation.
Imports belong only to the file that contains them. They apply throughout that file regardless of declaration order, but a sibling module must write its own imports. They cannot be re-exported:
pub import geometry::rectangle;
pub import geometry::rectangle;
error: imports are file-scoped and cannot be `pub`; re-exporting is not supported
error: imports are file-scoped and cannot be `pub`; re-exporting is not supported
Each imported name must be unique within its file, and the path anchors corecore, rootroot, and supersuper cannot be rebound. For example:
import geometry::rectangle as shape;import geometry::Point as shape;import geometry::Point as root;
import geometry::rectangle as shape;import geometry::Point as shape;import geometry::Point as root;
error: `shape` is already bound by an earlier import in this fileerror: `root` is a reserved path head and cannot be bound by an import
error: `shape` is already bound by an earlier import in this fileerror: `root` is a reserved path head and cannot be bound by an import
Names of loaded dependency packages are reserved for the same reason. There are currently no grouped imports or wildcard imports; introduce each binding with its own statement. A parameter or letlet binding may shadow an imported function name in expression position, and a generic type parameter may shadow an imported record name in type position.
The final item in src/lib.rrsrc/lib.rr uses #[main]#[main]:
#[main]pub fn entry() { let total = three_areas(); let point = translated_origin();}
#[main]pub fn entry() { let total = three_areas(); let point = translated_origin();}
#[main]#[main] marks the Reussir function that the runtime calls when this package is linked as an executable. The function’s written name is not special; entryentry, startstart, or another valid name works. The attribute is the part that makes it the entry point. pubpub is conventional for an entry item but is not required by #[main]#[main] itself.
An entry function must:
- have a body;
- take no parameters;
- have no generic parameters; and
- be the only function marked
#[main]#[main]in the whole package.
The compiler turns the marked function into the fixed runtime entry symbol. You do not need to write an externextern trampoline for it. Conversely, a library does not need #[main]#[main]; the diagnostic for a missing entry point appears only when asking the compiler to produce an executable.
The checks happen across the complete module tree, not one file at a time. For example, placing a second #[main]#[main] in geometry/mod.rrgeometry/mod.rr reports:
error: a program can have only one `#[main]` function; another is already declared
error: a program can have only one `#[main]` function; another is already declared
A parameterized entry point reports why it cannot be called by the runtime:
#[main]fn entry(argument: i64) {}
#[main]fn entry(argument: i64) {}
error: `#[main]` takes no parameters, but this one takes 1; the runtime calls the entry point with no arguments
error: `#[main]` takes no parameters, but this one takes 1; the runtime calls the entry point with no arguments
Run rene buildrene build after saving the three files. Rene passes the source root and package name to rrcrrc; the compiler follows the modmod declarations, resolves the aliases and paths as one package, and links the single marked entry point into the executable.