Cone Language Reference

import and extern

On this page

Note: Without a build description, a module found beside the importing file is only declared, so a program importing one cannot be linked. A C-named function passes a struct by value as C does on 64-bit Windows only.

After a program's source code gets big enough, it may become easier to work with when partitioned across multiple files. The files of one module need nothing to stitch them together: a module is the files of its folder, and the compiler finds every one of them itself. On this page, we introduce the two mechanisms that reach beyond a module: import, for another Cone module, and extern, for what is defined in another compiled unit — a separately built Cone package, or code written in another language such as C.

include is retired#

A file joins a module by being in the module's folder, so no statement brings one in. include, which once did, is a reserved word, and the compiler reports an include statement wherever it is written. Put the file in the folder of the module it belongs to instead.

import#

Use import to import another module that has its own namespace. For example:

import math3d;
import opengl;

An import may fold the module's names in with a use clause, as Namespace folding describes.

Where an import is written#

An import comes right after its file's mod line, with the file's other imports, so that every dependency a module has is stated at its declaration; one written after any other declaration is refused. A file of a folder's module other than its designated file has no mod line, and its imports come first.

File names and Paths#

An import names a module, or where the module's name is not found, a file path. If the file path includes characters that are not legal for a Cone name (such as periods or slashes), it should be enclosed in double quotes (and make use of the appropriate escape sequences where needed). If the file's extension is not specified, .cone is assumed.

In the simplest case, the file path specifies just the name of a source file to be found in the same folder as the source file which imports it. If a file of that name is not found there, the compiler will look for it in a folder of the same name (e.g., "useme/useme.cone"). This behavior allows source files to be organized across nested folders, where related source files are grouped together.

If desired, the file path may explicitly specify folders in front of the source file name. This can be an absolute path name, such as "/libs/math/trigfns". It can also be relative to the source file doing the importing, for example "nomnom/useme" or "../lib/useme".

If the file is not found in the current folder, the compiler searches the package search path for it: first each folder given to the compiler with --path, in order, and then the packages folder. The packages folder holds one folder per package, whose source is its src folder: src/name.cone is the designated file of the package's module, and the folder's other files and submodules are the package's by the module rules. It is where the packages that come with Cone live: stdio, which import stdio reaches; core, which every module imports without asking; and libc and posix, raw bindings to the C library and to the POSIX functions beyond it (Windows first), which core rests on for malloc; and, over those two, fs (files and folders), path (paths taken apart and put together) and env (environment variables), written over ISO C and POSIX. What is Windows-only beneath them is posix's: its POSIX names map onto the Windows C runtime, and its folder listing (opendir, readdir) and lstat are written over the runtime's _findfirst64; path reads Windows' path syntax (drive letters, shares, both separators). Beside them, process runs a program and waits for it (its arguments, a working folder, a time limit; its exit code and what it wrote to standard output and standard error, captured apart): its interface belongs to no one system, but its insides are Windows only, written over kernel32 (CreateProcessW, pipes, a job object), since neither ISO C nor POSIX alone can do it; a POSIX version comes with Linux. The compiler finds its packages folder from where it is installed: the nearest packages folder holding core, in the compiler's own folder or one above it. The CONE_PACKAGES environment variable names another. This allows the name of an imported module to correspond to some externally-hosted packaged library.

A name is looked for in that order, and a sister comes before all of it, since the enclosing module is asked before any file is looked for. So inside a submodule whose sister is named stdio, import stdio reaches her, and a package in a --path folder is found ahead of the packages folder's package of the same name.

A package built from a build description searches for nothing. After a sister, an import is found where the description's import line for its name says, and an import the description provides nothing for is an error, import stdio included. The file such a line names is only declared, as another package's include file is: its name is the import's. Its functions are called, and its globals read, in the object of the package it stands for, which is linked in beside the program's; only its inline bodies are compiled into the importer. It is a module file like any other, so it may import in its turn: those imports are found where the description's package lines say, and what they bind is the include file's own, not the importer's.

More than one module may import the same module. The imported module will only be loaded once. Its information is then reused by all modules that imported it.

A module imports another module once. Writing the same import a second time does nothing. A second import of the same module that says something different — it folds in other names, spells one differently, or is pub where the first is not — is an error, which names both. Say everything in the one import.

A module's own submodule is not imported, and importing one is an error: the subfolder or the file that holds it is what brings it in, so its name is already there to be written. Its names are folded in with a use statement instead, which takes the same clause an import's use does.

Compiled apart, and without a build description#

Separate compilation is built through a build description, which the build tool, Congo, writes for each package: a library compiled from one exports what its importers need, and a program whose import line names that library's include file links with the library's object. A library's compile generates its include file, and Congo compiles every program and package against the include files of the packages it imports. Above is how a program built that way finds its imports.

A program compiled without a build description finds them by the package search path instead. A module found on the search path is compiled into the program that imports it. One found beside the importing file is only declared, so a program importing one cannot be linked.

Importing a sister module#

A submodule imports one of its sisters by writing her name, not a path to her file:

mod engine;
import wheels;

pub fn drive(n i64) i64 {
  wheels.turn(n);
}

A module is the registry its own modules are found in. They are public to each other and to it, and invisible outside it unless it publishes them — so an import written inside one of them looks the name up in the enclosing module and finds a sister there, with no file to search for and nothing loaded. It is the same mechanism that reaches a separately built library; only the registry that answers is different.

What is in reach is the enclosing module's modules and no further up than that. A module two levels down does not see its parent's sisters.

A module inside such a tree is reached by its name, never by a path to its file. Writing import "../wheels/wheels" is an error, and so is a module importing the module that contains it: containment runs one way.

A name of the parent is not imported#

The enclosing module's namespace holds more than its modules: what it declares, and what its own imports bind. But a submodule may not import any of it:

mod scaling;
import Point;               // error: Point is the parent's, and the parent
                            // contains scaling, so the two would loop

A module depends on each of its own submodules, so a submodule that depended on its parent would close a loop, which is an error naming both. The same holds for a module the parent imported, reached through the parent: the submodule imports it by its own name instead. What a parent and its submodules share goes in a sister of theirs, which each imports:

mod scaling;
import shapes use Point;    // shapes is scaling's sister, and holds Point

Re-exporting what you import#

What an import brings in belongs to the module that wrote it. A module importing that module sees only what it passed on, which it does by writing pub:

pub import wheels;          // 'wheels' is a public name here, so 'engine.wheels.turn' resolves
pub import rims use *;      // and so is 'rims', and every name folded in from it
import tyres pub use *;     // every name folded in from tyres is public, and 'tyres' is not
import bolts use *;         // while these are this module's own business

pub before import reaches every name the import binds: the module's own name, and each name it folds in. pub use reaches only the names the use clause folds in, so a module can pass on a library's names without passing on the library. Writing both is allowed and means what pub import means alone. pub says what it says everywhere else — this name is visible outside the module that holds it — and it never makes public something that was not public where it was declared.

A name made public this way travels on: a module that imports engine with use * receives what engine passed on, and nothing that it kept for itself. That holds round a cycle of imports too: where two modules import each other, what each re-exports reaches the other, as it would if neither imported the other back.

Module-qualified names#

import is bringing in a module with its own namespace. By default, the names of an imported module are kept separate from the names in the module doing the importing. The only name that the importing module automically obtains is the name of the module it is importing — unless that module names a default fold on its mod line, which an import writing no use clause of its own folds in as well.

Since the namespaces of the modules stay separated by default, the importing module normally refers to names inside the imported module using a module-qualified name:

import opengl;

fn setColor(col Color) {
  opengl.setColor(col);
}

opengl.setColor references the setColor function to be found in the imported opengl module. The period separates the module name from the name of the function defined in that module. It is the same operator that reaches a member of a value: one period, for a path through a namespace and for a member of a value alike.

Module-qualified names can only access names the module declared pub. Every other name is private to its module and is not accessible to any other module.

Importing under another name#

as after the module binds it under a name of the importer's choosing instead of its own:

import geometry as geo;

pub fn far(a geo.Point, b geo.Point) i64 {
  geo.dist2(a, b);
}

The new name is used exactly as the module's own would have been: for its types, its functions, and the modules inside it (geo.metric.cm). The module's own name, geometry, is not a name here at all. Every form of import takes as — a name or a quoted path, a package or a sister, pub or not — and it comes before any use clause, which folds what it would have folded without the rename:

import geometry as geo use dist2 as d2;

A module's default fold still applies to a renamed import that writes no clause, and pub import units as u; makes u a public name of the importing module, as Re-exporting describes for the module's own name.

The rename is a name of the importing module like any other, so it may not collide with one of its declarations, with the name another import binds, or with a name a use clause folds in. A module is still imported only once, so a second import of it is an error whatever name each binds it under. A use clause may not fold the module's own name back in, which would undo the rename.

The usual reason to rename is a name that is wanted for something else. A type's field or method hides a module of the same name inside the type, its methods' signatures included, so a method named after the module it returns a type of needs the module imported under another name; see Implicit field and method access.

Namespace folding#

By default, an imported module does not pollute the namespace of the module importing it. This is often what you want. However, it can become tedious to read (and type) module-qualified names when they are used a lot by the importing module's code. To address this, an import may end with a use clause, which folds some or all of the module's public names into the importing module. It is the same clause a global variable carries, with the imported module in the place of the variable's type. The names being folded can be for anything: type, function, global variable, macro, etc. However, names folded in may not collide with any name the importing module already uses.

To fold in just a few specific names, list them after use, separated by commas:

import math3d use Point3, Mat3;

fn move(pos &Point3, delta Point3) {
  pos += delta;
}

Use as to give a folded name another spelling here, for instance when its own is already in use:

import math3d use Point3 as Point;

fn move(pos &Point, delta Point) {
  pos += delta;
}

A long list may be written as a block instead, which means the same thing:

import math3d use {
  Point3 as Point,
  Mat3,
  Quaternion,
};

Or one can fold in all public names from the module using *, and leave some out with but:

import math3d use *;
import opengl use * but Point3;   // math3d's Point3 is the one wanted here

An import's own use clause replaces whatever default fold the imported module names: import bigint; folds what mod bigint use BigInt; says, and import bigint use *; folds every public name instead.

The use clause is the only way an import folds names in. import math3d.*; and import math3d.Point3; are errors, each naming the clause to write instead.

A folded name is private to the importing module unless the import says otherwise, as Re-exporting describes. A name the module does not have, or does not make public, cannot be folded in, and it is an error to list one. Naming either after but is an error too: seen from outside the module a private name is as absent as a missing one, and * never folds it, so there is nothing to leave out. So is folding in a name the importing module already has for something else, whether it declared that name or folded it in from somewhere else: rename one with as, or leave it out with but. Writing the same thing in twice is an error too: listing a name twice, or listing it from two modules that each re-export it. A module imports another only once, so a second import of it, even an identical one, is an error. What a wildcard brings is different, since nothing was written twice: if two modules you fold in with * each re-export one function, say, folding in both is fine, and so is listing the function from one while the other's * brings it too. The name is bound once, and if either fold made it public, it is public.

extern#

extern says one thing: this function, method or global variable is defined elsewhere, in another compiled unit, so it is declared here without a body or a value, and the linker finds its definition. It says nothing about how the symbol is spelled. That is the module's to say: in an ordinary module a declaration has a Cone name, and in a module marked @c it has a C name.

It is written in front of a declaration, or of a block of them:

pub extern fn addOne(n i64) i64;
extern mut hits i64;

pub extern {
  fn printStr(str &str);
  fn printInt(nbr i64);
}

A pub before a block reaches every declaration in it. A declaration may be private: an include file declares the private names a public inline body reaches, since that body is compiled into the importer, which then needs them. A function with a body, or a global with a value, is an error in an extern declaration. A thread-local global is declared with its attribute, extern mut @threadlocal depth i64;, and may name a C thread-local in a @c module as well as a Cone one.

Methods and operators#

Inside a type, extern fn declares a method, a static function or an operator that is defined elsewhere:

pub struct Acc {
  pub total i64;

  pub extern fn plus(self, n i64) i64;
  pub extern fn `+`(self, other Acc) Acc;
  pub extern fn zero() Acc;
}

Such a method meets a trait's requirement like any other. What a user needs the body of cannot be extern, and is an error: an inline or generic function, a trait's methods, a generic type's methods. They are written whole, and compiled where they are used. A type's fields are part of its value, and are declared as they are.

Declaring a Cone package#

A package compiled on its own is reached through an include file: the package's root module, with everything its object defines declared extern, and its types, macros, inline bodies and generics written in full. The include file's module is an ordinary, Cone-named module of the package's name, so each extern declaration is spelled exactly as the package defines it, and the two objects link. It is Cone source, named for the package: stdio's is build/debug/stdio.cone in a program Congo builds, generated by compiling stdio, which declares the globals print and eprint and the methods of their type IOStream, all defined in stdio's own object.

Compiling a package as a library writes its include file, name.cone, beside the object. It is the package's own source with what a program does not need taken out: each body the package's object holds is left out and its declaration marked extern, each private declaration nothing a program expands reaches is left out, and everything else, comments included, is kept as written, under a comment saying the file is generated and is not to be edited. A global whose type the source leaves to be inferred has its type written in. Where the include file's lines stop being the source's, a comment such as //#line 24 "loc.cone" says the line after it is line 24 of the package's loc.cone, so an error or a panic in code a program compiles from the include file names the package's source file and line. Congo compiles each package before what imports it, and every importer against the include file that compile wrote, the packages that come with Cone included; an include file may still be written by hand where a build description names one.

What the package's root reaches in one of its submodules — a type a public function names, a function an inline body calls, the names a pub use re-exports — is declared in a nested module block inside the include file, holding only what is reached, edited the same way:

mod coll;

mod vec {
pub struct Stack {
  pub len i64;
  a i64;

  pub extern fn push(self, v i64) Stack;
}

pub extern fn stackOf(x i64, y i64) Stack;
}

pub use vec Stack, stackOf;

pub extern fn countOf(s &vec.Stack) i64;

Each name in a block is spelled after the submodule that declares it, as the package's object spells it. A block is private to the package: a program reaches Stack as coll.Stack, through the root's pub use, and cannot name coll.vec. A submodule nothing reaches has no block, and a generic submodule's block holds its whole text. Only a generated include file — one an import line names that opens with the generator's comment — may hold a module block; in source, and in an include file written by hand, a module is never declared inside a file.

An include file opens with its mod line and its imports follow, as in any module file. A package whose public functions take or return another package's types imports that package in its include file, as it does in its source. A program importing the first package can then use those values, their fields and their methods; it names the second package only if it imports it too, or if the include file's import says pub.

A global the package folds through is declared extern with the source's fold clause, as in pub extern mut config Config pub use *;, so the names the fold puts into the package are names of the include file too.

A generic function or type is written whole because each program makes the instances it uses, from that body: the package cannot know which type arguments its users will give. The package's object holds the instances the package itself uses, and the program's object holds the ones it uses; where both use the same one, say larger[i64], both objects define it, and the linker keeps one copy. Any private function the generic's body calls is declared extern in the include file, as for an inline body.

pub fn larger[T](a T, b T) T {
  if a > b {a;} else {b;}
}

pub struct Pair[T] {
  pub a T;
  pub b T;

  pub fn sum(self) T {a + b;}
}

A package whose root module is a generic module, mod g[T];, is reached the same way: its include file is the generic module's whole source, and each package or program that uses an instance, say g[i64], compiles it. The copies merge at link, globals included, so the instance has one set of globals however many objects use it. A package's include file shows its root module alone, so a generic module inside a package is not yet reachable from outside it.

C names: @c#

Code written in C is reached the same way, with its names spelled as C spells them. @c says so, and is written after the keyword of a mod line or a fn, in one of four forms: @c, @c("text"), @c(system) and @c("text", system).

On a mod line it makes the whole module C-named: its functions and global variables take their names unchanged, after the prefix the string gives. A C library's binding is such a module, and everyone else imports it and sees Cone:

mod @c("SDL_") sdl;

pub extern {
  fn Init(flags u32) i32;                    // the C function SDL_Init
  fn Quit();                                 // SDL_Quit
  fn @c("IMG_Load") LoadImage(file cstr) *u8; // a name outside the prefix
}

On a fn it gives that one function a C name: bare, its own name; with a string, the string is its whole symbol and no prefix is added. That is how a name outside a module's prefix is reached, and how a C function is declared in an ordinary module:

pub extern fn @c abs(n i32) i32;

A bare @c on a function its module already gives a C name says nothing, and is an error; the string form is fine there. system selects the platform's system calling convention (stdcall on 32-bit Windows), for the function or for every function of the module; an extern one is also imported from a DLL:

extern fn @c(system) GetTickCount() u32;

@c goes only where there is one symbol to name. It is an error on a type, on an anonymous, generic or inline function, and on a trait's or a generic type's method. In a C-named module a type's methods, and a generic function, keep Cone names. extern system, the older spelling of the system convention, is an error that names @c(system).

Exporting to C#

A Cone function with a body is published to C under a C name by writing pub and @c: the symbol is then external, so C code links against it. Without pub it is still spelled as C spells it, but stays private to its object. The same holds for the bodies of a C-named module.

pub fn @c("cone_square") square(x i64) i64 { x * x; }

fn @c("main") start() i32 {    // the program's entry, under another Cone name
  0i32;
}

Structs by value: C's calling convention#

A function a module gives a C name is called as C calls it, and calls C as C expects. Where it takes or returns a struct by value, the struct is passed the way the platform's C compiler passes the same C struct, so a Cone struct laid out as C lays out the struct crosses either way unchanged: to an extern function C defines, and into a Cone function C calls, directly or through a pointer to it that Cone handed C.

struct Vec4 { pub x f32; pub y f32; pub z f32; pub w f32; }

extern fn @c v4_sum(v Vec4) f32;       // float v4_sum(Vec4 v);
pub fn @c scaled(v Vec4, k f32) Vec4 { // C calls it: Vec4 scaled(Vec4 v, float k);
  new Vec4(v.x * k, v.y * k, v.z * k, v.w * k);
}

How a struct crosses is the platform's C ABI. On 64-bit Windows a struct of 1, 2, 4 or 8 bytes crosses as one integer of its size, whatever its fields (floats too). Any other size is passed as a pointer to a copy the caller made, and is returned through a pointer to the caller's result slot, which C passes as a hidden first parameter. The Vec4 above is 16 bytes, so it crosses by pointer both ways; a struct of two i32s is one 64-bit integer. The C library's own div and lldiv return a struct each way:

struct DivT { pub quot i32; pub rem i32; }      // C's div_t: 8 bytes, returned as one integer
struct LldivT { pub quot i64; pub rem i64; }    // C's lldiv_t: 16 bytes, returned through a result pointer

extern fn @c div(numer i32, denom i32) DivT;
extern fn @c lldiv(numer i64, denom i64) LldivT;

fn main() {
  imm d = div(17, 5);          // d.quot is 3 and d.rem is 2
  imm l = lldiv(17i64, 5i64);  // so are l.quot and l.rem
}

The same holds for bool, which is C's bool: an argument or a result crosses as one byte holding 0 or 1, widened in its register as the platform's C compiler widens it, and a bool field of a struct is one byte holding 0 or 1, as a C bool field is. An integer narrower than C's int is widened by its sign as C widens it on that platform. A bool, and an 8- or 16-bit integer, cross as C's do on 64-bit Windows, x86-64 Linux and macOS, and WebAssembly.

extern fn @c SetWindowFullscreen(window *Window, fullscreen bool) bool;  // bool f(Window *, bool);

A slice, a tuple or an array is not a C type, and passes as Cone passes it: a slice's pointer and length arrive as two arguments, as a C function's (char *, size_t) takes them.

Note: A struct crosses as C passes it on 64-bit Windows only. On every other target (x86-64 Linux and macOS, WebAssembly) a struct of more than one scalar does not yet cross as C passes it. A call made through a function reference (&fn) uses Cone's own convention, as does a type's method named with @c; so a C-named function taking or returning a struct by value goes wrong when it is called through a reference to it, and C's function pointer taking or returning a struct by value cannot yet be called from Cone.

One C name, one symbol#

C has one namespace for its symbols, so a C name means the same thing wherever it is declared. Several modules of one program may each declare the same C function, and they all call the one function:

mod geometry;

extern {
  fn @c abs(n i32) i32;     // also declared by the module that contains this one
}

Every declaration of one C name must agree: a function in its parameter and return types and its calling convention, a global in its type and its permission. Two that do not are an error, as C's "conflicting types" is, and the error names both. One of them may have a body, which is then the C name's definition, and the others are declarations of it; two bodies under one C name are an error too. A private C-named function is not among them: it stays private to its module, and a C function of the same name declared elsewhere is still reached. The compiler checks the modules it compiles together; between packages compiled separately, the linker does.

Associated types#

Not uncommonly, external functions and variables depend on custom types and constants defined by the external library. If so, these types are declared beside the extern declarations, in the same module: an extern block holds functions and variables only. Constants can be defined using an enum. If the internal field structure of a type is unknown, an opaque struct may be used. Marking an external function trustable, so that a call to it that passes a pointer needs no trust block, is not built yet.

extern in a file of its own#

It can be helpful to put the extern declarations for some C-compatible library (e.g., libcurl or sdl2) into a file of its own. Placed in a module's folder, that file is one of the module's files like any other, so every file of the module can use its declarations. A C-named binding is a module of its own, since @c is on its mod line.