Skip to content

Modules

Spice supports the linkage of several source files to one executable. To do so, you can use the import statement for tying in another source file to the current one.

Importing own source files

Here is an example for importing an own source file into the main source file. Imagine you have the utils.spice file, containing some util functions, which you want to use in your main source file main.spice.

Util functions file utils.spice:

Spice
1
2
3
4
5
// Simple recursive fibonacci algorithm
public f<int> fib(int n) {
    if n <= 2 { return 1; }
    return fib(n - 1) + fib(n - 2);
}

Main source file main.spice:

Spice
1
2
3
4
5
6
import "utils" as fibonacci;

f<int> main() {
    dyn fibResult = fibonacci::fib(30);
    printf("Fibonacci result: %d", fibResult);
}

In the example above, the imported file lives in the same directory as the main source file. If you have a nested folder structure you can state the import like this: import "sub-folder/sub-sub-folder/utils" as fibonacci.

You can also write the source file like this, using anonymous imports:

Spice
1
2
3
4
5
6
import "utils";

f<int> main() {
    dyn fibResult = fib(30);
    printf("Fibonacci result: %d", fibResult);
}

In the case that two imported source files expose symbols with the same name, Spice does not offer the anonymous usage of both symbols to prevent the usage of the wrong symbol. Then, you need to specify explicitly, which of the two symbols is meant. Using the unqualified name results in an Ambiguous symbol error, which names the colliding imports:

Spice
1
2
3
4
5
6
7
import "lib-a" as libA;
import "lib-b" as libB;

f<int> main() {
    Item item1 = Item{}; // Error: 'Item' is exposed by both 'lib-a' and 'lib-b'
    libA::Item item2 = libA::Item{}; // Ok
}

A symbol declared in a source file itself hides imported symbols with the same name. The imported ones stay reachable via the import name, e.g. libA::getValue().

Non-public functions, procedures and global variables are only visible within their own source file, so they may share names and signatures with symbols of other source files. Public ones, however, are visible to the linker under a name that only consists of their name and signature (plus the struct name for methods), but not of the source file. Therefore, two public functions or procedures with the same name and signature, or two public global variables with the same name, must not exist in the same program. The compiler reports each of these collisions with a Multiple exported symbols with the same linker name error:

Spice
1
2
3
4
5
6
7
8
9
// lib.spice
public f<int> getValue() { return 1; }
f<int> getHelper() { return 2; }

// main.spice
import "lib" as lib;

public f<int> getValue() { return 3; } // Error: same linker name as 'getValue' in lib.spice
f<int> getHelper() { return 4; }       // Ok, both are non-public

Linking against source files with another main function

Spice supports linking against source files which include a main function. Although the main function in the imported source file will be ignored and only the main function in the main source file will be taken into account.

Importing functionality from the standard library

Every import starting with std/, will be automatically assigned to the standard library.

Spice
1
2
3
4
5
import "std/text/print";

f<int> main() {
    println("This is a single line");
}

Circular imports

Source files may import each other, directly or transitively. Spice resolves such circular imports automatically — you simply write the imports you need, no special handling required.

A common case is two structs that reference each other: struct A (in a.spice) holds a pointer to B, and struct B (in b.spice) holds a pointer back to A.

a.spice:

Spice
1
2
3
4
5
import "b" as b;

public type A struct {
    B* bPtr
}

b.spice:

Spice
1
2
3
4
5
import "a" as a;

public type B struct {
    A* aPtr
}

main.spice:

Spice
1
2
3
4
5
6
7
8
9
import "a" as a;
import "b" as b;

f<int> main() {
    A aInst;
    B bInst;
    aInst.bPtr = &bInst;
    bInst.aPtr = &aInst;
}

Here a.spice and b.spice import each other, forming a cycle in the dependency graph. The compiler handles the cycle internally, so the B* field in A is type-identical to a B resolved through b.spice, and vice versa. Mutually recursive functions spread across files work the same way.

Platform dependent imports

If you have written code, that only works on one particular platform (e.g. two function with the same functionality, one for Linux and one for Windows), you can name source files with the suffix of the OS e.g. example_linux.spice / example_windows.spice. Then you can import the files without the suffix: import "example" as example;. Depending on your target triple, the matching source file will be chosen at compile time automatically.