Packages and modules organize code that is distributed across many files and folders. There are three techniques for joining together code that spans multiple source files:
- include other files into the current namespace
- Create a new module namespace that is nested and usable within the current namespace
- import an external package whose namespace(s) are usable by the current namespace
By convention, the names and structure for packages and modules typically maps closely to how source files are named and organized into folders.
include: Combining Source Files
The include statement is very straightforward. It incorporates another source file's contents as if were made a part of the current source file. For example, suppose we had a source file called 'useme.cone':
fn inc(a i32) i32 a+1
Another source file can now use its contents:
include useme fn timesnext(a i32) a * inc(a) // uses a function defined by 'useme.cone'
'useme' is the name of the source file to include (the extension of .cone is assumed, if not specified). By default, it looks for this file in the same folder as the source file which includes it. Failing to find it there, it will look for a file named 'mod.cone' in the subfolder named 'useme'. This latter convention makes it convenient to organize source files in nested folders.
Sometimes, a source file's name or path may need to use punctuation characters not legal in an identifier, such as '/' or '.'. When this is the case, simply enclose the file/path name in double quotes:
A specified path may be absolute or relative to the current source file's folder.
mod: Module Namespaces
A module is essentially a namespace that holds a collection of uniquely named types, functions, global variables, constants and other modules. Modules are valuable for isolating the names used by large programs. Modules help to avoid situations where different source programs accidentally collide, using the same name to refer to different things.
A module may lie within a source file, be a whole source file, or even span multiple source files. Modules are nestable.
A module block may be specified within a source file using the mod statement. Its named namespace is effectively nested within the enclosing program's namespace:
mod frontend fn get() ... fn main() frontend::get() // refers to the get function within the frontend namespace
In this example, frontend is a module containing its own function(s), including get. To refer to get() outside that module, we must prefix its name with the name of the module it belongs to, using :: to separate these names.
Modules as separate files
More commonly, a nested module is defined in its own source file and then referenced by any module that needs access to its named members. Using the above example, we would move the frontend module's code to a separate source file called frontend.cone:
fn get() ....
Then we can shorten the source file that needs access to frontend to have mod simply refer to the 'frontend.cone' source file:
mod frontend; // this retrieves the module found in 'frontend.cone' fn main() frontend::get()
As with include, file and path names may be specified within double quotes. Likewise, if the file 'frontend.cone' does not exist, the program would automatically next look for 'frontend/mod.cone', thereby enabling subfolders to represent nested modules. If we were to move get() into the 'loader' sub-module within 'frontend', the main program would need to refer to it as frontend::loader::get();
A reference to a named entity in another module (namespace) is called a path name. Typically, path names are relative to the current module. This behavior can be overridden with a prefix:
- A path name beginning with '::' begins with the current package's main (root) namespace.
- A path name may begin with super::, which refers to the module that holds the current module.
Simplifying namespaced names
It can become tedious to repetitively use fully-qualified path names for frequently used names. To fold one or more specific names from a module in the current module's namespace, simply specify these names on the mod command. For example:
mod frontend::loader::get fn main() get() // 'get' is now part of main's namespace
An alias may be given for an imported name to avoid potential conflicts:
use frontend::loader::get as load fn main() load() // We must use the alias
All public names within a namespace may be folded into the current namespace using a glob (*):
use frontend::loader::* fn main() get()
Private vs. public
The names within a module's namespace may be public or private:
- Public names are visible to any other module
- Private names are only visible to code within the same module
Private names begin with an underscore. Public names do not.
A package is a collection of source files that are transformed by a build system into a specific program or library. An entire package is the basic unit of compilation. A package may contain one or more modules.
The filename for a program package's main source file is typically "main.cone". Similarly, the file for a library package's main source file is typically "lib.cone".
To make use of named entities defined by another package library, use import rather than mod. The name of the package effectively becomes the highest level module name.
So, if we moved "frontend" to its own library package, the example would now be:
import frontend; fn main() frontend::loader::get()
The build system uses information in the package's configuration file to retrieve the code for the desired version of the referenced package from its home on the Internet.
It is not necessary to import the standard library (std) and fold its names into the current namespace, as this happens automatically for every defined module.