Skip to content

Modules

Chloë Strainge edited this page Oct 14, 2026 · 1 revision

Modules and visibility

A source file is a module. Declarations are private unless preceded by pub. An import loads a module once, retains its scope, and exposes its public interface.

Qualified and selected imports

counter.shy:

let $count = 0
pub fn next(): Integer
{
    $count = $count + 1
    $count
}
pub type Ticket = Integer

main.shy:

import counter
import counter::{ next, Ticket }
echo (counter::next) (next)
let $ticket: Ticket = 17
echo $ticket.type
1 2
Ticket

import counter makes counter::next and counter::Ticket resolvable without adding unqualified next and Ticket. Selective imports bring only listed public names into the current scope. Variables use $counter::name.

Imported bindings share the registered object through Rc references. Updating an imported mutable binding can update the module's binding; a new local let creates a shadowing declaration. Module caching also prevents repeated top-level side effects. Cyclic loading is an error.

Methods and re-exports

Public extension methods become available when their module is imported. A pub import re-exports the selected interface; directory packages use this to assemble an API from smaller files. See Module packages.

Imports are module-level declarations. Use conditional imports when a dependency is platform specific.

Rust registrations

Native registration is separate from module visibility. Hidden native types and functions remain registered internally. visible symbol exposes a native symbol locally; pub visible symbol also exports it. A type can be named by a String, and a function by a validated reference. Literal visibility declarations are processed early enough for annotations in the same module.

This lets a standard-library file publish wrappers around a private Rust API, using the same import rules as scripted definitions.


Related: Packages · Conditional code · Standard library

Clone this wiki locally