Language reference
Modules and packages
Programs and modules#
A module is a .nox file. A program is an entry file plus every module it imports, directly or transitively. All modules of a
program are compiled together as one unit: there is no separate compilation, no linking of precompiled Nox objects and therefore no
ABI to keep stable between your own modules.
A project is a directory with a nox.json manifest:
{
"name": "demo",
"entry": "main.nox",
"requires": []
}noxc init demo creates one. name and entry are required; requires lists third-party packages (see Packages).
Single-file programs need no manifest and can import only the standard library.
The entry point#
There is no main function. The top-level statements of the entry file run in order and form the program. A top-level function named
main is rejected because the compiler synthesises its own entry symbol with that name. The top-level statements of imported modules run too, once, before the statements of the module that imports them (modules are merged in dependency order, imports first); keep imported modules to declarations unless you want
that side effect. Module-level variable initialisers always run before any other top-level statement.
import#
import nox.strings # qualified use: nox.strings.upper(s)
from nox.math import sqrt, pi # direct use: sqrt(2.0)
from nox.strings import upper as up # alias
import nox.time as t # module aliasimport util # <project>/util.nox
import app.models # <project>/app/models.nox
from app.models import User, greet
x: util.Box = util.Box(2) # qualified type names work in annotationsForms:
| Statement | Effect |
|---|---|
import a.b |
makes the module's functions and classes available as a.b.name(...) and a.b.Class |
import a.b as m |
the same, under the alias m |
from a.b import x, y |
makes x and y available unqualified |
from a.b import x as z |
the same, renamed to z |
There is no from … import *. Imports are static: they must be at the top level of a module, are resolved before anything runs, and an
unresolved module is a compile error. Importing the same module twice is harmless. Cyclic imports are rejected.
How names are resolved#
A dotted module path is looked up in this order:
- The standard library — the
nox.*modules (Standard library). - The current project —
a/b.noxunder the project root (the directory containingnox.json);import helpersis<root>/helpers.noxandimport utils.mathyis<root>/utils/mathy.nox. - A dependency — the
aliasof an entry inrequiresis a package name:import nyx.apploads moduleappof the package aliasednyx.
Qualified access has two quirks#
- A module's functions and classes can be used qualified (
util.double(4),util.Box), but its module-level variables are read withfrom util import LIMIT—util.LIMITis not valid. extern deffunctions (raw native bindings, such assqrtinnox.math) are never name-mangled, so they are used unqualified after the import — see the notes on each standard-library page.
Module-level state#
Variables declared at the top level of a module are module-level variables. Functions of the same module read and assign them without
any global keyword (Statements). Initialisers run before any other top-level statement, in source order.
counter: int = 0
def bump() -> int:
counter += 1
return counter
print(bump(), bump())1 2A module-level variable that a function reads is initialised before main runs. Whatever its initialiser needs comes with it: a variable
the initialiser mentions is initialised in the same step, together with the attribute assignments (cfg.port = 80) and method calls
(names.append("a")) on those variables that precede the last such declaration in the file. This is what makes the common
"configure, then boot" shape work:
class Config:
name: str
def __init__(self, name: str) -> None:
self.name = name
cfg: Config = Config("dev")
cfg.name = "prod"
label: str = cfg.name + "!"
def show() -> None:
print(label)
show()prod!Statements after the last such declaration, and compound statements such as if or for, run in main after all initialisers. An
initialiser may not read a module variable that a function reads but that is declared after it; the compiler rejects that with a
message naming both variables instead of reading uninitialised memory.
Under the multi-core runtime each worker keeps its own copy of module state; share data between tasks with channels, nox.atomic or
nox.sharedmem (Concurrency).
Packages#
A package is an ordinary project whose repository can be listed in another project's requires. Dependencies are fetched into a cache,
pinned to an exact commit in nox.lock, and compiled into your program with everything else. The registry, the lock file, noxc add,
noxc publish and the trust implications are in Packages and The noxpkg registry.
A package that declares extern def bindings runs native code with full authority — see Security.
Visibility#
All top-level names of a module are importable; Nox has no public/private keywords. By convention a leading underscore marks a name as
internal, and names beginning with __nox_ belong to the compiler's prelude. A leading-underscore name is not hidden from importers.