Every Polyscript source file is a module. A module offers names to other modules with export, and
takes names from them with import. There is no shared global namespace: a name is visible in a file
only if that file declares it or imports it.
An export statement lists the names a module makes available. Functions, structs, enums, traits,
type aliases, and constants may all be exported. This is the whole of stdlib/process.ps:
import { endproc } from "@intrinsics";
fn exit(code: int): never {
endproc(code);
}
export { exit };Several names may be exported at once, and the statement may appear anywhere in the file. Placing it last, where it reads as a summary of the module's surface, is the convention the standard library follows.
const PI: float = 3.141592653589793;
const TAU: float = 6.283185307179586;
const E: float = 2.718281828459045;
fn area(radius: float): float { PI * radius * radius }
export { PI, TAU, E, area };Whatever a module does not export stays private to it.
An import statement names the items to take and the file to take them from.
import { print, println, panic } from "stdlib/io.ps";
import { Vec } from "stdlib/vec.ps";
import { INT_MIN } from "stdlib/consts.ps";Imported names are bound straight into the importing file's scope, and are used exactly as though they
had been declared there. There is no module object and no qualified module.name form.
import { Vec } from "stdlib/vec.ps";
let numbers = [Vec<int>].new(); // Used unqualified.How an import path is resolved depends on how it starts.
| Path | Resolved against |
|---|---|
"stdlib/io.ps" |
The standard library directory. |
"helpers.ps", "../shared/util.ps" |
The directory holding the importing file. |
"@intrinsics" |
The compiler itself. |
import { Vec } from "stdlib/vec.ps"; // From the standard library.
// import { helper } from "helpers.ps"; // From a file alongside this one.A module that has already been loaded is not loaded again, so two modules may import each other without difficulty.
@intrinsics is a module supplied by the compiler rather than by a file. It holds the primitive
operations the language is built from: allocation, reference counting, raw character output, process
control, and the mathematical operations that map onto hardware.
import { calloc, realloc, memcpy, free, decref, print_char, strlen, ref, deref } from "@intrinsics";@intrinsics is available to the standard library, which is what allows the standard library to
implement safe types like Vec and String on top of raw memory. Programs reach those operations
through the types and functions the standard library exports rather than through @intrinsics
directly. The full list is in
the standard library reference.
Two mechanisms control access, and they answer different questions.
export decides which names leave a module.
public and private, described in Implementations, decide which
members of a type are reachable, and they apply the same way everywhere — a private field is out of
reach outside its own implementations, including within the file that declared it.
The two compose, which is how a module hands out a usable type while keeping its internals sealed.
struct Vec<T> {
private size: int;
private capacity: int;
private data: int;
}
impl<T> Vec<T> {
public fn length(self): int {
self.size // Reachable: inside an implementation of `Vec<T>`.
}
}
export { Vec };Importers receive Vec and can call length, but size, capacity, and data remain unreachable.
Exporting a type exports its public surface, never its private members.