Cone Language Reference

Modules and Namespaces

On this page

A module spans the files of its folder, its designated file declares it with mod, and a subfolder holding its own designated file is a submodule of it, as is a file of the folder that opens with its own mod. Every file that makes a module opens with its mod line, and its imports come right after it. A module reaches its own submodules by name, folds their names in with a use statement, and reaches a sister by importing her, but never a name of its parent: modules depend on each other in one direction only. A module may extend another, reusing its names as its own, and may name on its mod line the names a bare import of it folds. A module is never declared inside a source file: nesting is by files and folders. Only a package's generated include file writes a module block, for each submodule it reaches. A module may be C-named with @c on its mod line. A package is a folder with a manifest that the build tool, Congo, builds: each package on its own, from a build description that lists its modules and files; a library built so is named in its symbols, writes its include file, and links with a program compiled against that file. A module may conform to a module trait, taking the trait's defaults it does not declare. A module may be generic, instantiated with type arguments where it is used, each instance with its own globals; a generic module may not yet hold submodules. A module may declare its own initializer and finalizer, and every global without an initial value must be assigned by its initializer; the entry the compiler builds for a program runs every module's initializer before the program's main and every finalizer after it (below). A global may be thread-local, one copy per thread. The purity rules of initpure are not checked. The framework traits a program plugs into, whose entries would be configurable, are not implemented.

Many things are given names: variables, functions, types, fields, methods, etc. The collection of all names defined for a scope is called a namespace. Blocks and types are two different kinds of namespaces.

Namespaces facilitate a modular architecture by:

  • hiding implementation details visible within the namespace behind the simpler, durable API shown outside. This is called encapsulation.
  • lowering the accidental risk of name collisions, where two different items (possibly created by different authors) are given the same name.

The rules governing names vary according to the type of namespace it belongs to. Let's review those here, beginning with the introduction of a new kind of namespace: the module.

Modules#

A module is a global namespace. You have seen code examples for modules already:

// A type declaration
struct Handler {
  pub id u32;
}

// a global variable
imm handler = new Handler(0);

// A function
fn main() {
  // do some stuff
}

As this example shows, modules hold global variables, functions, and type declarations. They can also hold other named things, such as macros and even other named modules. Further, modules can import names from other modules: an import binds the other module's name, and its use clause folds that module's public names in as names of this one.

Every program or library has a single main module. That main module may import other modules or types offering reusable, isolated capabilities it depends on.

A module is a folder's files#

A module's source files are the files of a folder. One of them is the folder's designated file: the file named for the folder, as geometry/geometry.cone is for geometry. It is the file that declares the module, with mod written as its first statement:

mod geometry;

pub struct Point {
  pub x f32;
  pub y f32;
}

Point the compiler at that file and it finds the rest itself. Every other .cone file in the folder belongs to the same module, unless it opens with a mod of its own and so is a module of its own, and so does every file of every subfolder that holds no designated file of its own, at any depth. Nothing lists them: putting files in a folder is what brings them together, and moving a file to another folder moves it to another module. Such a subfolder groups files without making a namespace of them, so a large module can be organized by topic with no new names to write and none to path through.

The folder names the module. A name written after mod may restate the folder's name and may not change it, because a name on the filesystem is one that any tool can read without parsing Cone. That name is what another module binds when it imports this one, the name a path through the module is written with, and the name the module's declarations carry in the symbols the linker sees.

Only the designated file declares the module, and it must. Because the declaration claims it, it comes before everything else in that file, and a module declares itself once. A file the folder brought in declares nothing: a file whose first statement is a mod is not brought in at all, but is a module of its own, and a mod anywhere later in a file is an error.

The folder is the only thing that brings a file into a module: nothing written in one file brings another in, so a file's module can be told from where the file sits. include, which once brought a named file in, is retired.

Two files of one module may not share a name, however deep in the folder tree they sit, and a file belongs to one module: a file another module already holds cannot be brought into a second one.

A module's own name is one of the names in reach inside it. That matters where a local variable, a parameter or a type's member hides a name the module declares or a module it imports, since it is the only way to reach past the nearer one without renaming anything (a method named like an imported module is the common case):

mod counters;

mut total = 0;

fn addTo(total i32) i32 {
  counters.total + total;   // the parameter hides the global; the module's name reaches it
}

A file the compiler is pointed at that is not its folder's designated file is a module of one file: a complete program or library as it always was, named after the file, with the files beside it none of its business. It opens with a mod line too, naming the file, as mod counters; opens counters.cone. For now a mod declaration in such a file may still rename its module.

Every file that makes a module opens with its mod line: a folder's designated file, a one-file module, and a file the compiler is pointed at or an import reaches that is a module of one file. The line names what the filesystem already names, the folder or the file, so that the name is the first thing a reader of the file sees. A file that makes a module and opens with anything else, a mod trait included, is an error. The other files of a folder's module have no mod line, since the folder is what names the module they join.

A module's imports come right after its mod line, ahead of everything else it declares, and so they are all in its designated file. Comments may sit anywhere among them:

// geometry: points, and the shapes built on them

mod geometry;

// What this module uses
import stdio;
import units use metre;

pub struct Point {
  pub x f32;
  pub y f32;
}

An import written after any other declaration, a standalone use included, is an error. So is an import anywhere in the module's other files: they have no mod line, so there is no place for one. A use is not an import, and any file of the module may write one. So the top of the designated file says which module it opens and everything that module depends on, and the build tool, Congo, reads nothing more of it to find a package's modules and what they import.

Submodules#

A subfolder that holds its own designated file is not organizing files: it is a submodule, a module of its own inside the module whose folder holds it. One rule decides both kinds of subfolder — does it hold the file named for it? — and it is the same rule that made the folder a module in the first place.

geometry/
  geometry.cone      the module 'geometry'
  shapes.cone        a file of 'geometry'
  detail/util.cone   also a file of 'geometry': 'detail' holds no detail.cone
  scaling/
    scaling.cone     the module 'geometry.scaling'
    matrix.cone      a file of 'geometry.scaling'

Its parent reaches into it by writing a path, the same way a path reaches into any module: scaling.twice(n) from inside geometry. What the parent may reach is what the submodule declares pub, because a submodule is a module and its private names are its own.

A parent folds a submodule's names in with use. A submodule is never imported — its subfolder or its file is what brings it in — so a parent that wants its child's names bare writes a use statement naming the submodule, and the same clause an import's use takes follows the name:

mod geometry;

use scaling;                     // every public name of scaling: twice(n), not scaling.twice(n)
use measure inches as toInches;  // one name, spelled another way here
use units { metre, yard };       // a list, written as a block
use shapes but Hexagon;          // every public name but one
use scaling.fixed;               // a submodule's pub submodule, reached by a path
pub use detail;                  // detail's public names, re-exported as geometry's

Only a submodule's public names fold, and naming a private one is an error, whether in a list or after but, where there is nothing to leave out. The folded names are this module's own and private to it unless the statement is written pub use, which re-exports them: the parent's neighbours then reach them as names of the parent, while the submodule itself may stay private. A submodule is folded once: a second use of it is an error, so say everything in one.

A standalone use folds what a module reaches without importing it: an enum, below, or a submodule. A module brought in by import is folded by that import's own clause, import math3d use Point3;, and naming it in a use statement is an error — one spelling for each situation.

A submodule is private to its parent unless it says otherwise. Written pub mod scaling; it can be named from outside its parent as well, as geometry.scaling.twice; written mod scaling; only geometry may name it. The pub goes on the submodule's own declaration, since the parent declares nothing about a subfolder or a file, and it means what it means everywhere else: this entry is visible outside this namespace. It opens a submodule to the rest of its package, never beyond: a program importing the package reaches a submodule's names only where the package's root re-exports them with pub use, since the package's include file declares each submodule in a private block.

A submodule of one file needs no folder. A folder is only worth making when a module has more than one file, so a file of a module's folder whose first statement is a mod declaration is a submodule by itself:

geometry/
  geometry.cone      the module 'geometry'
  shapes.cone        a file of 'geometry': no 'mod' of its own
  units.cone         'mod units;' -- the module 'geometry.units'
  scaling/
    scaling.cone     the module 'geometry.scaling'
    matrix.cone      a file of 'geometry.scaling'

units is a submodule in every respect scaling is: private to geometry unless it is written pub mod units;, reached by a path, units.metre(), folded by use units;, and imported by its sisters by name. Its own imports follow its mod, as a designated file's do. It does not join geometry, and none of geometry's files join it. It is named for its file: mod units; is written in units.cone, and any other name there is an error.

When it grows past one file, move units.cone to units/units.cone and put the new files beside it. Nothing that names it changes: not its name, not a path through it, and not the symbols the linker sees. A units.cone that declares a module beside a units/ folder that holds its own units.cone is an error, since that is two modules of one name.

A module must sit directly in its parent module's folder — as a file there, or as a folder that is a direct child of it — so the module tree has the shape of the folder tree. A designated file further down — inside a folder that holds none of its own — is an error rather than a deeper submodule, and so is a file that opens with mod inside such a folder.

Two submodules of one module may name each other, by importing. The module that holds them is the registry they are found in: they are public to each other and to it, and invisible outside it unless it publishes them. So import scaling; written inside a sister of scaling finds her there — see importing a sister module — and nothing arrives unasked, because a name nobody imported is not a name of the module.

A submodule does not import its parent's names. A module depends on each of its own submodules, so a submodule that depended on its parent — import Point; inside scaling, naming geometry's Point — would make the two depend on each other, and that is an error (see below). What a parent and its submodules share belongs in a sister of theirs, which each of them imports. A submodule cannot name its parent either — containment runs one way.

The reach between sisters is one level and does not accumulate as you go down. A module sees its own parent's submodules, and not its grandparent's.

Modules depend on each other in one direction#

Imports never go round in a loop, between the modules of a package as much as between packages. A module depends on each module it imports, on the module holding a name it imports, on the module it extends, and on each of its own submodules. If those dependencies ever lead from a module back to itself, the program is in error, and the error names the modules round the loop:

Import loop between modules: geometry -> geometry.scaling -> geometry.
Imports between modules must not loop (geometry contains geometry.scaling;
geometry.scaling imports Point of geometry at scaling.cone:3). A module may
not depend on a module that contains it: move what they share into a sister
both import.

So every program's modules have an order, each after everything it depends on. That is the order their initializers run in, and their finalizers run in the reverse.

A module extending a module#

A module can be built out of another one: it takes in everything the other declares, and adds its own functionality beside it. The module's declaration names the module it extends:

mod solids extends shapes;

// area, Point and count are shapes' public names, and now names of solids too;
// scale is private to shapes, and solids may use it as well
pub fn volume(w i64, h i64, d i64) i64 {
  area(w, h) * d * scale();
}

Everything shapes declares is now a name of solids, and inside solids it is written bare. So is every name a use clause of shapes folded in: if shapes writes import units use metre, then metre is a name of solids too. solids is inside shapes' boundary, the way a type that extends a type is inside its base's, so it reaches shapes' private names too.

What shapes imports stays shapes'. A module's imports are what it depends on, not what it contains, and each module says for itself what it depends on. So the name units, which that import binds in shapes, is not a name of solids: to write units.inch, solids imports units itself.

What solids shows is what shapes shows. A public name of shapes is a public name of solids: a module that imports solids reaches it as solids.area, or bare after import solids use *, exactly as it reaches what solids declares itself. A private name of shapes is private in solids, and no module outside solids reaches it through solids. A chain works the same way: if shapes extends another module, that module's names reach solids too, each as visible as it was.

For a module, extending is inheriting. A module has one instance, so there is nothing to copy: each name is a second name for the same thing, and the function, the type and the global stay shapes's. A global changed through solids.count is the one shapes.count reads.

The module it extends must already be in reach — a sister, found where an import of her would find her, or a module this one imports — and is named by that one name. The rest of the rules:

  • A module extends one module.
  • It adds and does not redeclare. A declaration spelled like one of the extended module's names, public or private, is an error, as it is for a type that extends a type: one name would otherwise mean two things. That includes a name the extended module folded in with a use clause, but not the name of a module it imports, which is not one of the names it passes on.
  • extends brings names, not the module's own name. shapes is a name of solids only if solids imports it.
  • A module may not extend itself, a module it contains, or its parent. A module depends on the module it extends, so a chain of extends may not come back round, and a module may not import one that extends it: each is a loop.
  • extends names a module to reuse. A module conforming to a module's abstraction is a different reading, spelled is: mod arena is Region.

A module's default fold#

A package named for the one thing it provides would put that name in every path twice: bigint.BigInt. So a module may say, on its own mod line, what an importer gets when it imports the module and asks for nothing in particular. A package holding one thing then just becomes the thing:

mod bigint use BigInt;

pub struct BigInt {
  pub digits i64;
}

pub fn helper(n i64) i64 {
  n + 100;
}

Now a module that writes a bare import bigint; has BigInt directly, and bigint as well, so bigint.helper still reaches what the default leaves out. With extends, the clause comes last:

mod bigint extends base use BigInt;
  • The list is the default, however many names it holds. mod units use metre, foot; makes a bare import fold both. * and * but may be written too. Adding a name later only adds to what importers see.
  • An importer's own clause replaces the default entirely. import bigint use *, import bigint use helper or import bigint use * but BigInt folds what it says, and nothing of the default.
  • Here use runs the other way. Everywhere else, use folds names into the module where it is written. On the mod line it names what importers fold.
  • It is not an export list. pub still decides what is reachable, and every name the clause lists must already be a public name of the module: one the module has not got, or keeps private, is an error at the mod line. The clause only chooses which public names a bare import folds.
  • The importer decides the rest. The names are the importer's, private to it unless it writes pub import bigint;, which re-exports them with bigint. A default name that collides with one of the importer's own is an error there, settled by writing a clause of its own. For the same reason neither as nor pub may be written in the clause on a mod line.

The default applies wherever a module is imported with no clause of its own: a sister, or a module found by its file. A use of a submodule is not an import, and still folds every public name.

A module conforming to a module trait#

A program plugs into a larger framework — a shell that runs it, a web server that hands it requests — and the framework says what every program plugging into it must provide. That interface is a module trait: the abstraction of a module, as a trait is the abstraction of a struct. It declares functions and global variables. One written with a body, or with an initial value, is a default; one written without is a requirement:

mod hosts;

pub mod trait Runner {
  pub fn step(n i64) i64;        // required
  pub mut scale i64;             // required

  pub mut offset = 100i64;       // a default
  pub fn run(n i64) i64 {        // a default
    step(n) * scale + offset;
  }
}

A default's body is a template, as a generic's is: it is type-checked in each module that conforms, where the requirements it calls are known, and not where the trait is written. So a mistake in a default is reported at the trait's line, as instantiated by the first module that conforms; a framework package that no module of its own conforms to does not see it. Give such a package a small conforming module in its tests.

A module trait is a declaration of the module whose file writes it, like a type: it is imported, folded and made public as any declaration is. A module says it conforms on its mod line, with is:

mod plain is Runner;

import hosts use Runner;

pub fn step(n i64) i64 {
  n + 1i64;
}

pub mut scale = 2i64;
  • Conformance is declared, never inferred. A module whose declarations happen to match a trait does not conform to it unless its mod line says so. A module conforms to one trait, named by one name that the module can reach; a trait further away is imported and folded in first.
  • It is checked where it is written. For each requirement the module must declare a function with exactly the trait's signature, or a global of the trait's type and permission. A requirement the module does not declare, or declares differently, is an error at the is, naming the member.
  • Defaults are copied into the module. A default the module does not declare becomes the module's own: plain.run and plain.offset are functions and globals of plain, reached like any other. Where a default's body names another member of the trait, the copy names the module's — so plain.run calls plain.step. A default global is storage of each module that takes it.
  • A module may override a default by declaring its own, with the same shape: the copies of the other defaults then use it.
  • Conformance is static. A module is one instance, so nothing is dispatched at run time, and there is no virtual reference to a module. A member of the trait has no code or storage of its own, so Runner.run is an error: name it through a module that conforms.
  • A module trait holds functions and globals only. A trait requiring a type is not implemented, and neither is a generic function or an overload name among its members.

On the mod line, is comes after extends and before a default fold:

mod layered extends plain is Runner use run;

A module that extends a conforming module takes the defaults that module took as that module's, as it takes all its names.

Generic modules#

A module may be generic, written as a generic type is: its type parameters in square brackets after its name on the mod line.

pub mod stack[T];

pub mut count i64 = 0i64;
pub mut top T = 0;

fn @initpure init() {
  // runs once for each instance
}

pub fn push(x T) {
  top = x;
  count = count + 1i64;
}

A generic module is used through an instance, named with type arguments where it is used, as Box[i64] names an instance of a generic type:

import stack;

fn main() {
  stack[i64].push(3i64);
  stack[f64].push(1.5d);
  imm n = stack[i64].count;    // 1
}
  • Each instance is a module of its own, with its own global variables and its own initializer and finalizer: stack[i64].count and stack[f64].count are two globals.
  • The same arguments are the same instance, wherever they are written: stack[i64] named in two modules is one module with one set of globals.
  • Nothing of a generic module is compiled until an instance is named. It has no members of its own to reach, so its name without type arguments names no member, and nothing of it can be folded or extended: stack.push, use stack;, import stack use * and extends stack are errors. Inside its own body, its name is the instance being defined: stack.count there is that instance's global.
  • An instance initializes after its generic's dependencies and after the modules its type arguments come from, and finalizes in the reverse. Given a type of the module that uses it, the instance's initializer runs after that module's.
  • Across packages, a package's include file carries its generic module's whole source, and each package or program that uses an instance compiles it; the copies merge when the program is linked, so an instance still has one set of globals. A library's root module may be generic; a program's may not.
  • A generic module conforms to a module trait on its mod line, as any module does, and each instance is checked against it.

Type parameters only: parameters with constraints or of other kinds are not implemented. A generic module may not yet hold a submodule, a generic function or type, a trait or an enum, a macro, a module trait, or a global with a use clause, and it may not be C-named.

Packages and Congo#

A package is one top-level module and the modules inside it, built on its own and shared as a unit. It is a folder holding a manifest, congo.toml, and the source under src, whose root module's designated file is named for the package:

hello/
    congo.toml
    src/hello.cone
    tests/
[package]
name = "hello"
version = "0.1.0"
output = "executable"

The manifest gives the package's name, its version (MAJOR.MINOR.PATCH), and whether it builds into an executable or a library. It lists no dependencies: the package's import statements are its dependencies. Under src the module rules above apply unchanged, with src/hello.cone as the root's designated file.

A package may also have a gpu folder beside src, holding the part of it that runs on a GPU as well: more submodules of the root, each a file opening with its own mod or a folder holding its designated file, which src reaches by name as it reaches any submodule, and which may not use src in return. The GPU Compute page has the rules.

The build tool, Congo, builds packages. congo new hello makes the folder above; congo run, from anywhere inside it, builds it and runs it; congo build only builds, and --release optimises. A single file with no manifest is run with congo run hello.cone. Congo finds each package a program imports by its name, in a list of package folders, compiles each one on its own, and links them. A package is imported through its include file, which compiling the package generates from its source: build/debug/hello.cone, beside its object. Congo compiles every package before what imports it, and compiles each importer against that generated file; nothing is written by hand. The packages that come with Cone, core, stdio, libc and posix, are packages of this shape, the last two C packages (below). Imports between packages may not loop: a package never imports, directly or through others, a package that imports it. Nor may the modules inside one (above). Congo checks both before it compiles anything.

A C package wraps a C library. Its source is a C-named module, src/name.cone, declaring what the library defines, and its include file is generated from it as any package's is. Its manifest names the C library under [link], and Congo links that library into every program that imports the package:

[package]
name = "winstr"
version = "0.1.0"
output = "library"

[link]
libraries = ["shlwapi"]      # shlwapi.lib on Windows, -lshlwapi elsewhere
paths = ["clib"]             # folders to look in first, relative to the package

A program imports it by name, import winstr;, with no C declarations of its own. Any package may have a [link] table, a program's included.

A package's build description#

A package can be built on its own. The build tool, Congo, reads each file's mod line and imports, and hands the compiler a build description: a file ending in .conebuild that names each module of the package, lists its files, and says where each of its imports is found. The compiler is given that file in place of a source file:

build: debug
output: library
import core: "core.cone"
import stdio: "stdio.cone"
geometry: {
    "../../src/geometry.cone"
    "../../src/shapes.cone"
    import stdio: "stdio.cone"
    scaling: {
        "../../src/scaling/scaling.cone"
    }
}
  • The settings come first. build is debug or release, and output is executable (the default) or library.
  • Then the package lines, one import name: "path" for every package the package depends on, directly or through another package, each naming that package's include file. They are where an include file's own imports are found: an include file is a module file like any other, so it may import, and a package whose functions take or return another package's types imports that package in its include file. They do not add to what the package's own modules import. The line for core, where there is one, is also where the prelude every module imports is loaded from. Congo writes the description into the build folder, beside the include files the earlier compiles generated.
  • Then the package's module, a name and a braced body. In it, a quoted path is a file of the module, name: { ... } is a module inside it, and import name: "path" says where this module's import name is found. A relative path is relative to the description's own folder, and is written with /, since the description is read as Cone source and a backslash begins an escape.
  • The description names each module, and a mod line opening any of a module's files must restate that name. Only the first file listed may declare the module, as only a designated file may; a module inside the package is private to it unless its own mod line says pub, exactly as a submodule is.
  • Nothing is searched for. A file the description does not list is not compiled, and an import is found only where its import line says.
  • A library's module names its symbols. Built with output: library, every declaration of the package is spelled after its module — a function area as geometry.area — as a program importing geometry spells it. A program's module is not, so its main is the entry point the linker expects.
  • A library exports what its importers need. Its public functions and globals, and the public functions of its public types, are visible to the linker, so a program compiled against the library's include file links with the library's own object. So is a private function, global or type that an inline, generic or macro body, or a trait's default, names: the importer builds its own copy of that body, and the copy calls what it names. Everything else the package defines stays private to its object.
  • A generic is made by every package that uses it. Each object holds the versions of a generic that its own code uses, the library's and the program's alike, as it holds the table a type's trait methods are reached through. Where two objects hold the same one, linking keeps a single copy.

The include file declares each function, method and global the library defines with extern and no body. Its module is Cone-named like the library's, so each declaration is spelled as the library defines it; a generic is written whole.

C-named modules#

A module's declarations are spelled with Cone names by default: the name the linker sees carries the module, and the type, it is declared in. A module is C-named by writing @c after mod: its functions and global variables then take their names unchanged, as C spells them, after the prefix a string gives, and @c(system) gives its functions the platform's system calling convention.

mod @c("SDL_") sdl;

pub extern fn Init(flags u32) i32;      // SDL_Init

This is what a binding to a C library is: a module whose names bind to the library's symbols, imported like any other and reached as sdl.Init. Naming, and the C calling convention that comes with a C name, are the only things it changes about its declarations (and it has no prelude, below): whether each declaration is defined elsewhere is its own extern, and a C-named module may hold bodies too, which it compiles and publishes under C names when they are pub. A type's methods and a generic function keep Cone names, since a C name has no room for an owner or type arguments. Such a module, alone in its package, is a C package, whose manifest names the C library to link.

A C-named module does not import core automatically, as every other module does: it declares what a C library defines, in C's types, and core itself imports one, libc. So core's names, such as Option and malloc, are not in reach in it unless it imports core itself, and declaring one of them there is no clash.

Name rules#

Module names are:

  • unique. no two items in a module may have the same name. Several functions may nonetheless share one overload name, which is itself a single name in the module that maps to those functions. A module may not bring one name in twice, even for the same item: listing a name twice, writing the same use twice, importing a module twice, or importing a module and also listing it through another module that re-exports it, is an error. Core is imported into every module automatically, so import core in any module but a C-named one is a second import of it, and the error says so. What a module never wrote is different. When a name arrives by a wildcard use *, by extends, or by the automatic import of core, and it is the same item the name already has, it is not two items: when two wildcard imports each re-export the same function, a module that extends this one hands this one's own names back, or import libc use * brings the malloc core already re-exports from it, the name is bound once, and it is public if either route made it public.
  • order-independent. One can reference functions, variables, or types whose names are not defined until later in the module. The only exception to this are permissions and regions whose definition must precede their use.
  • private unless pub. A name declared pub may be referenced outside the module. Any other name may not: it is private to the module, whatever it is spelled. The interface of a module is what it says it is, and nothing more. A type's members that are not pub are private to its module in the same way (Types as Namespaces).

Global variables#

Global variables across all modules (and types) are managed by the global region. Memory space for them is allocated automatically and all-at-once when a program is loaded for execution. The lifetime of all global variables is the same as the program's lifetime, since global variables come into being when the program starts and only disappear once the program stops. The lifetime annotation for this scope is 'static.

Global variable declarations have two important constraints:

  • The mut permission is not permitted if the program makes use of multiple threads.
  • Variables must be explicitly initialized with a valid value. In many cases, the initial value is a statically-computable constant value whose exact size is also known to the compiler: a literal, a named constant, or a constant expression. Otherwise the variable is declared with its type and no value, and the module's initializer assigns it.

A global variable may also fold its type's members in as names of the module, with the same use clause a struct's field carries:

mut config Config = new Config(80i64, 24i64) use *;

Every public member of Config is now a name of this module, reached through config, so a function here writes area() where it would have written config.area(). A global is to a module what a field is to a type, with one difference that makes this the cheaper fold: there is exactly one of it, at an address known when the program is compiled. See Folding through a global for the whole of the clause, including what pub use lets another module see.

Thread-local global variables#

A global variable written with @threadlocal after its permission is one copy per thread: every thread that runs has its own, and a thread's code reads and writes only its own.

mut @threadlocal depth i64 = 0i64;

fn enter() {
  depth = depth + 1i64;   // this thread's depth, whatever the others' are
}

Each thread's copy starts from the initial value written on the declaration, which is why a thread-local global is constrained more than other globals:

  • It must be written with an initial value, a literal as every global's is. It cannot be left for the module's initializer to assign, since that runs on one thread and would give a value to that thread's copy alone.
  • It must be mut. A copy per thread of a value no thread can change would be the same value in every copy.
  • Its type may not need finalizing: not a struct with a finalizer, nor anything holding one, nor an owning reference whose region has work to do when it is dropped. Nothing finalizes a thread's copy yet when its thread ends.
  • Only a global declared in a module may be thread-local: not a local, a function's or a type's static, a parameter, a field, or a module trait's global.

A thread-local global is declared extern like any other, with @threadlocal kept and no value, and a library's include file declares its thread-local globals that way. Every declaration of it must say @threadlocal, because a compiled object reaches a thread's copy by a different route than it reaches ordinary storage. The compiler checks this only among the declarations of one compile.

Implemented. The language itself starts no thread; a thread the thread package starts, or one started through the operating system directly, sees its own copy as described.

A global written with @workgroup after its permission is a GPU workgroup's: one copy for each workgroup of a compute dispatch, which its invocations share, and an ordinary global on the CPU (Workgroup memory). It is written as a thread-local is, but with no initial value; a global is one or the other, never both.

Folding an enum's variants in#

An enum's variants are names of the enum, reached as Colors.Red. A module that wants them as names of its own writes a use statement naming the enum, and optionally which variants — the same statement that folds a submodule's names in:

use Colors;                  // Red, Green and Blue are names of this module
use shapes.Shape Circle;     // one variant of an enum another module declares
pub use Suit;                // and names other modules may reach through this one

The names are this module's own and private to it unless the statement is written pub use, which is how a module re-exports an enum's variants: another module reaches them through it, and an import of it with use * brings them in bare. The core library does exactly this for Option and Result, which is why Some, None, Ok and Error are bare in every program. See Naming a variant.

Dynamic initialization of global variables#

Sometimes, the initial value of a global variable is unknown, or too complex to specify, at compile-time. In such cases, we want to initialize the variable using run-time logic invoked when the program or library is loaded. To accomplish this, declare the variable without an initial value and then initialize that variable inside the module's initializer, a function called init.

imm handler Handler;

fn @initpure init() {
  handler = new Handler(80);
}

The variable is declared with its type, since there is no value to infer it from. Inside init, such a variable starts out holding nothing, exactly as a local declared without a value does: init may assign it once, even when it is imm, and may not read it before it does. Every other function sees it holding the value init gave it.

The initializer is declared with initpure, a less constrained version of pure. Any function marked as initpure may not read any uninitialized global variables in the current module and may only call functions marked as pure or initpure.

To ensure safe and complete dynamic initialization, the compiler:

  • Verifies that every uninitialized global variable is assigned a value at some point within the same module's initpure-marked init function.
  • Verifies that pure and initpure functions comply with their constraints.
  • Runs the initializers for any dependent modules before running the initializer for the module.

The first and last are implemented. The second is not: a function marked @initpure is accepted, and nothing yet checks what it calls, since pure functions are not implemented.

The initializer is written fn @initpure init(), taking no parameters and returning nothing. A module may also declare a finalizer, a function called final, written fn final(), to release its own state when the program is shutting down:

fn final() {
  log.flush();
}

Each module declares only its own initializer and finalizer, for its own state; it cannot set up another module's. A module may declare either, both or neither. At module scope the names init and final mean these two, and naming anything else by them is an error.

Where a module's global variables hold values whose type has a finalizer, or owning references, the compiler gives the module a finalizer that runs the module's own final, if it declares one, and then finalizes each such global, releasing an owner as a local's end would, in the order they are declared: the way a type's final runs before its fields are finalized. Such a module may not declare a function named drop, which is the name the compiler gives it.

Running every module's initializer and finalizer#

The compiler stitches every module's initializer together, in the order the modules depend on each other: each module's runs after the initializers of every module it depends on, and the program's own module's runs last. Every module's finalizer runs in exactly the reverse order, the program's own first. A module with neither is left out.

A program does not start them. The compiler builds the program's entry, the C main the system calls, round the program's own main: the entry runs every module's initializer, calls main (with the command line's argc and argv if it takes them), runs every module's finalizer, and exits with the value main returned (0 if it returns nothing). Everything has been initialized when main begins, and a program that opened files in an initializer has them closed by a finalizer when main returns. The program's main is a function of the module that is the program, taking nothing, or an i32 and a **u8, and returning an integer or nothing:

fn main() i32 {
  run();
  0;
}

The names initAll() and finalAll(), which every module can use, run the same two stitched sequences, and each runs its modules once: a program that calls them itself, as programs did before the entry was built, finds nothing left to run at initAll(), and runs the finalizers at its first finalAll() (so it may finalize early) and not again when main returns.

A program ends without running any finalizer when it panics, or calls the C library's exit or abort: the entry's final runs only when main returns. A library compiled on its own has no entry of its own and keeps its initializer and finalizer for the program that imports it, whose include file for the library declares them, with extern, and declares the globals its finalizer finalizes. A C program that calls such a library is on its own for setting it up. A main that takes any other parameters, a function with a C name that is the program's own C entry, and a WebAssembly or GPU module, get no entry from the compiler.

Types as Namespaces#

Declarations for struct and enum types are also namespaces. At a minimum, these hold the names of the type's fields and methods. They may also hold the names for the type's static functions, its static variables and the names of other types (e.g., enums). An enum's namespace holds its variants, which is why a variant is reached through its enum, Colors.Red, unless a module folds it in with use.

Static Functions#

In addition to methods, a type may declare useful static functions that are not methods and do not work with a specific object of that type. A static function is declared among the type's methods, and what makes it static is that it has no self parameter. It is not marked static: that word declares a static variable, and before a function it is an error.

struct Counter {
  count i32;

  pub fn zeroFactory() Counter {
    new Counter(0);
  }
}

To make use of the function outside the type, qualify the name with the name of the type:

mut cnt = Counter.zeroFactory();

Name Rules#

A type's names are:

  • unique for every field, function and method. Every method and function has its own name, unique within the type. Several methods or functions may additionally share one overload name, which they declare with overload.
  • order-independent. A name may be referenced before its declaration, and the order in which the methods or functions sharing an overload name are declared never affects which one a call selects.
  • private to the module unless pub. A field, method or function declared pub may be used anywhere the type's values reach. Any other member is private to the module that declares the type: every function, method and type of that module may use it, through any value of the type, in any file of the module's folder, and nothing outside the module may. A sister, a submodule and the parent are other modules, as is a file that opens with its own mod line. A type that extends another, a struct enriching its base or an enum extending its base, is the one exception: it is inside its base's boundary, so its code reaches the base's private members wherever it is declared. Fields and methods are accessed on a value using the dot (.) operator. A static function is reached with the same operator, on the type's name.
  • a pub field of a pub type names only types the outside can name. Such a field is reached from outside the module, so a type private to the module may not be its type, alone or behind a reference, a pointer, an array or a type argument. Either the field is made private, which the module still reaches, or the type is made pub.

A type's members and a module's names share one privacy boundary, the module. The module is the unit of trust: the code that may see a type's private members is decided by where it is written, not by a list the type declares, so there is no friend declaration. A type whose state must be kept even from its neighbours is declared in a module of its own.

Function Blocks#

All logic blocks inside functions and methods are namespaces that hold local (and parameter) variables.

Every block's names are:

  • unique. no two variables in a block may be declared with the same name. However, variables in a block might shadow same-named variables in outer scopes.
  • order-dependent. Logic may not reference a local variable until after it is declared.
  • private. Only the block logic may see and use local variables.