Cone Language Reference

Packages: modules compiled and shared on their own

On this page

A module that other programs depend on is worth compiling once, giving a version, and sharing. That is a package: modularity at the size of a library. Everything the last two pages said about a module holds inside a package, since a package is a module and the modules inside it. This page adds what changes at the package's edge: how a program depends on one, what one shows, how it is compiled apart from the programs that use it, and the rule that holds at every size, that dependencies run one way. Here is a small library, and a program that uses it:

measure/                       room/
  congo.toml                     congo.toml
  src/                           src/
    measure.cone                   room.cone
    convert.cone
// measure/src/measure.cone
// A library package: a program imports it by name
mod measure;

// convert is a submodule; pub use passes its names on as measure's own
pub use convert;

pub fn perimeter(width i32, depth i32) i32 {
  twice(width + depth);
}

// Private: no program that imports measure can call it
fn twice(n i32) i32 {
  n * 2;
}
// measure/src/convert.cone
// A submodule of the library, public to the rest of the package
pub mod convert;

pub fn toFeet(metres i32) i32 {
  metres * 328 / 100;
}
// room/src/room.cone
// A program that depends on the measure package
mod room;

import stdio use *;
import measure;

fn main() {
  print <- "perimeter: ", measure.perimeter(4, 5), " m\n";
  print <- "in feet:   ", measure.toFeet(18), " ft\n";
}

Congo finds measure by its name, in the folders it searches for packages, so the folder that holds measure is listed as one of them (Growing a program with Congo shows how). Then congo run, inside room, compiles measure on its own, then room, links them, and runs the program:

perimeter: 18 m
in feet:   59 ft

Try this: call measure.twice(3) from room, or measure.convert.toFeet(18). Both are refused, for reasons this page explains.

What a package is#

A package is one top-level module and the modules inside it, built on its own and shared as a unit. On disk it is a folder holding a manifest, congo.toml, which gives its name, its version and whether it builds into a program or a library, and its source under src. There the module rules apply unchanged, with src/name.cone, named for the package, as the root module's designated file. The build tool, Congo, builds packages: congo new makes one, congo run builds and runs it, and congo test runs its tests (Testing). Growing a program with Congo walks through using them.

A package's imports are its dependencies. The manifest lists none: an import of a name that is not a module of the package is another package, found by its name, and that is all it takes to depend on it. The packages that come with Cone, core, stdio, collections and the rest, are packages of exactly this shape.

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 has the rules.

A C package wraps a library written in C. Its source is a C-named module, src/name.cone, declaring what the C library defines, and its manifest names the C library, which is then linked into every program that imports the package; what differs by system (the library's name on Windows and on Linux, say) is said there too. A program imports it by name, import winstr;, with no C declarations of its own. Talking to C covers the module's side.

What a package shows#

A program that imports a package sees one module, the package's root, and of that only what is pub. twice is private to measure, so to room it does not exist at all:

Error 1019: The name twice does not refer to a declared name
Error 1042: convert is private to its module and may not be named from outside it.

The second refusal is the package's edge. A submodule written pub mod is public to the rest of its package, so measure's other modules could name convert, but never beyond the package. A program reaches a submodule's names only where the package's root re-exports them with pub use, as measure does for toFeet. So the root module's public names are the package's whole interface, however the package is divided inside, and the package may reorganize its submodules freely without any program noticing.

Compiled apart: the include file#

A package is compiled on its own, before anything that imports it, and a program is compiled against a description of the package rather than its source. Compiling a library writes that description, its include file, beside its object. It is Cone source, generated, never written by hand. Here is the one compiling measure wrote:

// Generated by conec: the include file of package measure, from measure.cone.
// A program importing measure compiles against this file in place of that source.
// Do not edit it: change measure's source and compile measure again.

//#line 1 "measure.cone"
// A library package: a program imports it by name
mod measure;

//#line 1 "convert.cone"
// A submodule of the library, public to the rest of the package
mod convert {
//#line 4 "convert.cone"
pub extern fn toFeet(metres i32) i32;
}

//#line 4 "measure.cone"
// convert is a submodule; pub use passes its names on as measure's own
pub use convert;

pub extern fn perimeter(width i32, depth i32) i32;

It declares each public function, method and global the library defines with extern and no body, so a program compiled against it calls into the library's own object when the two are linked. (This is the one place a module block, mod convert { ... }, is ever written, and its submodules are private blocks, which is why convert cannot be named from room.) The rest of what the build tool does, and the build description it hands the compiler, which lists each module's files and says where each import is found, belongs to Congo; how an import is found in one is in Where an import is found.

Some rules follow from compiling apart:

  • A library's module names its symbols. Every declaration of a library is spelled after its module (a function perimeter as measure.perimeter), as a program importing it 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 is a private function, global or type that an inline, generic or macro body, or a trait's default, names: such a body is written whole in the include file, so the importer builds its own copy of it, 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 (Generic functions).

Dependencies run one way#

Imports never go round in a loop, between packages or between the modules of one package. 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. A package depends on every package it imports. 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. Congo checks this before it compiles anything, as here, where a submodule tried to import a name of the module that contains it:

congo: error: import loop between modules of package loopy: loopy -> loopy.scaling -> loopy. Imports
between modules must not loop (loopy contains loopy.scaling; loopy.scaling imports Point of loopy at
src\scaling\scaling.cone:2). A module may not depend on a module that contains it: move what they
share into a sister both import.

The rule is what makes the rest of this part work. A package can be compiled on its own only if everything it needs was compiled first, which needs an order. So every program's packages, and every package's modules, have one, each after everything it depends on. That is also the order their initializers run in, and their finalizers run in the reverse. And a loop is a design smell as much as a build problem: two parts that each need the other are really one part, or share a third that both should depend on, which is exactly the cure the message gives.

You can now build a library as a package, choose what it shows, and depend on it. A module holds more than names, though: it can hold state, and that state has to be set up before anything uses it.