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.
A module is a global namespace. You have seen code examples for modules already:
// A type declaration struct Handler: id u32 // a global variable imm handler = Handler // 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.
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.
Module names are:
- unique. no two items in a module may have the same name.
- 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.
- public or private. Public names may be referenced outside the module. Private names (those beginning with an underscore) may not.
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.
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 fn @initpure init(): handler = Handler()
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.
A finalizer might be automatically be created, where needed, to finalize any dynamically-initialized global variables. This is run when the program is shutting down.
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 and the names of other types (e.g., enums).
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. All such functions should be declared within a static block.
struct Counter: static: fn zeroFactory() Counter: Counter
To make use of the function outside the type, qualify the name with the name of the type:
mut cnt = Counter::factory()
A type's names are:
- unique for fields and functions, but not methods. Types allow the definition of multiple methods with the name.
- largely order-independent. A name be be referenced before its declaration. However, the order in which overloaded functions are declared can matter.
- public or private. Public names may be referenced by functions or methods not a part of the type. Private names (those beginning with an underscore) may not. Fields and methods are publicly accessed using the dot (.) operator. Functions are accessed using the double-colon operator.
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.