Module Bindings and Re-exports
This section specifies how the result of @import is bound to a name, the re-export idiom (pub const m = @import(...)), and the (non-)status of modules as runtime values. The imported module may be bound to any name (rule 4.13:87).
Binding Forms
The result of @import MAY be bound at the top level of a file with a const item, or locally inside a function body with a let statement. Member access through either binding form has the same semantics.
const math = @import("math"); // top-level const binding
fn main() -> i32 {
let m = @import("math"); // local let binding
math.add(1, m.add(2, 3)) // equivalent access through either
}
Per-File Scoping
A top-level const module binding is scoped to the file that declares it. Two files MAY bind the same name — whether to the same module or to different modules — without conflict; references in each file resolve to that file's own binding. This is an exception to the shared flat namespace of rule 10.5:1, because every file writes its own imports. Declaring two module bindings with the same name in one file remains a compile-time error (E0418).
// a.rue
const utils = @import("utils"); // fine
pub fn from_a() -> i32 { utils.one() }
// b.rue
const utils = @import("utils"); // fine: per-file, no collision
pub fn from_b() -> i32 { utils.one() }
Re-exports
A top-level const whose initializer is an @import expression makes the bound name a member of the enclosing module — a re-export. Accessing that member through an import of the enclosing module yields the inner imported module, and such access chains through multiple levels of re-export.
A const re-export follows the same visibility rules as any other item (10.3): a pub const re-export is accessible from any directory, while a non-pub re-export is private to the directory of its defining file.
// outer/_outer.rue
pub const inner = @import("inner"); // pub re-export
const hidden = @import("inner"); // private outside outer/
// outer/inner.rue
pub fn value() -> i32 { 42 }
// main.rue
fn main() -> i32 {
let outer = @import("outer");
outer.inner.value() // 42, chained through the re-export
// outer.hidden.value() // error: `hidden` is private (E0706)
}
Module Aliases
A top-level const whose initializer evaluates to a module — an alias of another module binding in the same file (const m2 = m;) or a member-access chain ending at a module (const math = std.math;) — is itself a module binding, with the same per-file scoping (10.4:8) and re-export semantics (10.4:3) as a direct @import binding.
const std = @import("std");
const math = std.math; // alias of a nested re-export
const m2 = math; // alias of an alias
fn main() -> i32 {
math.abs(-7) - m2.abs(7) // 0: all three name the same module
}
Value Constants as Module Members
A value constant declared at the top level of a module's file is a member of that module. Accessing it through the module yields the constant's value, with the constant's declared (or inferred, 6.5:4) type. The access is itself compile-time evaluable, so it may appear in another constant's initializer.
A value constant member follows the same visibility rules as any other item (10.3): a pub const is accessible from any directory, while a non-pub constant is private to the directory of its defining file (E0706).
// math.rue
pub const ANSWER: i32 = 42;
const SECRET: i32 = 99; // private outside math.rue's directory
// main.rue (same directory)
const math = @import("math");
const COPY: i32 = math.ANSWER; // const-evaluable member access
fn main() -> i32 { math.ANSWER + COPY } // 84
Modules Are Not Runtime Values
A module is not a runtime value. It is a compile-time error to use a module binding as a function argument, as a struct field value, or as an operand of an operator such as ==.
In a few value positions (for example, the tail expression of a block whose type is ()) a module expression is currently accepted and treated as the unit value. Programs must not rely on this; it is an artifact of the current implementation, not a guarantee.