Module System

Organize code into modules with explicit imports and namespace control.

Module System

Sounio organizes code into modules. A file can declare itself as a module with an explicit module statement, or remain in the flat namespace if no declaration is present.

Declaring a module

// file: ir/profile.sio
module ir::profile

fn sprof_parse(data: &str) -> SprofProfile {
    // ...
}

fn sprof_lookup(profile: &SprofProfile, name: &str) -> i64 {
    // ...
}

The module ir::profile declaration places all definitions in this file under the ir::profile namespace.

Importing from modules

Use use to bring names into scope:

// Wildcard import — brings all public names into scope
use ir::profile::*

// The functions are now available directly
let profile = sprof_parse(data)
let count = sprof_lookup(profile, "main")

Selective imports are also supported:

use ir::profile::{sprof_parse, sprof_lookup}

Important: Wildcard imports (use module::*) are currently more reliable than selective imports for modules that have internal helper functions. Some internal helpers may fail to resolve with selective imports — prefer wildcard when in doubt.

Flat namespace files

Files without a module declaration are in the flat namespace. Their definitions are available directly to any file that can see them, without an explicit use statement:

// file: ir/normalize.sio
// (no module declaration)

fn normalize_instruction(instr: &!IrInstr) {
    // available without import in other flat-namespace files
}

Files like normalize.sio, const_prop.sio, dce.sio, and ssa.sio in the self-hosted compiler use this pattern.

Module resolution rules

  1. A file with module X::Y must be imported with use X::Y::* or use X::Y::{name}.
  2. A file without module is in the flat namespace — its names are visible without imports.
  3. Forward references are not supported: helpers must be defined before callers in the source file.
  4. Module paths map to file system paths: module ir::profile corresponds to ir/profile.sio.

Combining modules and flat files

The self-hosted compiler mixes both patterns:

// main.sio — uses both module and flat-namespace imports
use ir::ir::*           // module import: IrFunction, IrModule, etc.
use ir::profile::*      // module import: SprofProfile, sprof_parse, etc.
use ir::inline::*       // module import: inl_run_pass, etc.

// normalize.sio, const_prop.sio are flat — no import needed

Practical guidance

  • Use module for libraries and reusable components with clear interfaces.
  • Use flat namespace for tightly coupled implementation files that share many internal helpers.
  • Always use use parser::ast::* (wildcard) when importing AST types — selective imports can break operator comparisons on enum variants.