# Libraries and Imports

This page is the "Libraries and Imports" section of the Voxlang language reference. It is generated from LANGUAGE.md in the Voxlang compiler's own repository, so it says what the specification says and nothing more.

A URL is the heading, lowercased, with punctuation dropped and spaces turned into hyphens. Each "##" section of the spec is a page under /docs/, and every heading inside it is a fragment on that page, so "File I/O" is /docs/file-io/ and "Reading a whole file" inside it is /docs/file-io/#reading-a-whole-file.

As data: https://vox-lang.dev/docs/index.json lists every section and every heading with its URL, https://vox-lang.dev/docs/search.json carries one entry per heading with its first sentence and keywords, and https://vox-lang.dev/docs/anchors.json maps every slug to the page it lives on. Search is also a JSON endpoint: https://vox-lang.dev/docs/search?q=<words> returns ranked results as JSON, no page load.

Source: LANGUAGE.md at bf2cba0, 2026-08-22. https://github.com/Vox-lang/vox/blob/bf2cba037976586c30d22a48e7b7246d347f54a1/LANGUAGE.md

[Reference](https://vox-lang.dev/docs/) > Libraries and Imports

## The `see` Keyword

`see` pulls in code from another file. It has two distinct jobs:

- **`see "<path>.vox".`** — include another Vox source file. This works today: the file is parsed as part of your program, so its functions become callable with no linking step. It is how you split a program across files.
- **`see '<lib>' version "<ver>" from "<path>.lib".`** — consume a shared library through its `.lib` interface. This is the library path; see [Shared libraries](https://vox-lang.dev/docs/libraries-and-imports/#shared-libraries) below.

```vox fragment
see "./utils.vox".
see mathkit version "1.0" from "./libmathkit.lib".
```

There is exactly **one** library form. Three other shapes — `see "./path.so".`, `see "lib" version "1.0" from "./path.so".`, and `see "./path.so" for "lib" version "1.0".` — point `see` at a `.so` directly. A `.so` is binary ELF: it carries mangled symbol *names* but no Vox type information, so the compiler cannot check a call against it. All three are refused: `see` of a `.so` errors and directs you to the `.lib`, and the `see ... for ...` form has its own diagnostic — both name the canonical form `see '<lib>' version "<x.y>" from "<path>.lib".`.

**Search paths.** `see` resolves the path by its shape:

- `./…` or `../…` — resolved against the directory of the file that contains the `see` statement, and only there.
- `/…` — used as-is (absolute).
- a bare name — `/usr/share/vox/lib/<name>` is checked **first**, and only if that does not exist does it fall back to the containing file's directory. Watch this: a bare `see "utils.vox".` can silently pick up a system file in preference to the one sitting next to your source. Write `./utils.vox` when you mean the local one.

Those three shapes describe a `.vox` source include. A `.lib` path resolves differently: relative or bare, it is tried against the containing file's directory first and then each `--lib-path` directory, and its `Location` `.so` against the `.lib`'s own directory first and then `--lib-path`; absolute paths are used as-is. `--lib-path` is not consulted for a `.vox` include at all; for `--link` it only passes search paths to the linker (`-L`). See [Consuming a library](https://vox-lang.dev/docs/libraries-and-imports/#consuming-a-library).

**Circular includes.** The compiler tracks files already seen and skips a `see` that would re-enter one.

## Shared libraries

A shared library is a `.so` you build from Vox and call from Vox — or from C, Rust, or any other host. The chain is:

**`.vox` → `see` a `.lib` → `Location` → `.so`**

The `.lib` is the typed interface (the `.h` equivalent); the `.so` it points at is linked, never read for types. This section covers writing one, the `.lib` file, consuming one, putting several libraries in one `.so`, and the symbol names a non-Vox caller needs.

> **What runs today.** The whole path runs: building a library with `--shared` produces a self-contained `.so` plus its `.lib` interface, `see` of a `.lib` consumes it from Vox, export names are mangled, and multi-input `--shared` links several libraries (and several versions of one library) into one `.so`. A foreign host can also call the `.so` directly — see [Calling a library from a non-Vox host](https://vox-lang.dev/docs/libraries-and-imports/#calling-a-library-from-a-non-vox-host).

### Writing a library

Add a `Library` declaration at the top of a `.vox` file, then build with `--shared`:

```
Library mathkit version "1.0".

To 'add two numbers' with a number called x and a number called y. Return a number, x add y.

To greet.
  Print "hello from mathkit".
```

```bash
vox mathkit_lib.vox --shared -o libmathkit.so
```

That writes two files: `libmathkit.so`, and `libmathkit.lib` beside it — the typed interface a Vox consumer `see`s, described in [The `.lib` file](https://vox-lang.dev/docs/libraries-and-imports/#the-lib-file) below. The `.lib` name comes from `-o`, so the pair always travels together.

This compiles to a self-contained shared object. It carries its own copy of the Vox runtime, so it is loadable from C, Rust, or any other host — not only from Vox. The runtime is position-independent, so a library may use the full core language — arithmetic, printing, buffers, files, floats, lists, maps — not a runtime-free subset. Only the library's own function definitions are exported; every runtime symbol is kept out of the dynamic symbol table.

Verify what you built:

```bash
$ nm -D --defined-only libmathkit.so
000000000000072c T mathkit_1_0_add_two_numbers
000000000000076c T mathkit_1_0_greet
$ readelf -r libmathkit.so
There are no relocations in this file.
```

Two exports and nothing else leaked; zero absolute relocations, so the whole object is position-independent. The labels are the mangled `<library>_<version>_<func>` form — `mathkit_1_0_add_two_numbers` and `mathkit_1_0_greet` — so two versions of one library can live in one `.so` without colliding; see [Mangling](https://vox-lang.dev/docs/libraries-and-imports/#mangling) below.

**A library needs an identity.** The `Library` declaration gives the library the name and version that drive mangling and the `.lib`. A `--shared` build with no `Library` line has no identity and is rejected — add the declaration.

**Top-level statements are rejected.** A shared library has no entry point, so a top-level executable statement (`Print`, assignment, `If`, a bare function call, …) would be silently dropped. The compiler rejects it instead:

```text
error: Top-level print statement is not allowed in a shared library: only
function definitions, 'Library', and 'see' may appear at the top level.
```

Only function definitions, `Library`, and `see` may appear at the top level of a `--shared` compile. Put any work you need inside a function.

**An empty library is rejected.** A `--shared` compile with no function definitions exports nothing, which would yield a malformed version script and an opaque linker error. The compiler rejects it instead: a shared library must export at least one function.

### The `.lib` file

The `.lib` is the public interface to a library: its name and version, where its `.so` is, and a table of contents of every exported function's signature. It is what a consumer `see`s, and the only place Vox types live — ELF carries mangled names but no types, so the `.lib` is the type source. A `--shared` build writes `<output-stem>.lib` beside the `.so`, one `Library` block per input. The `.lib` is a declared output like the `.so`, derived from the same `-o`: a rebuild overwrites it in place, so an edit-build loop needs no manual cleanup and anything hand-edited into a `.lib` is lost. The pair is written together, so a fresh `.so` never lands beside a stale `.lib`. The format:

```
Library mathkit version "1.0".
Location "./libmathkit.so".

Table of Contents:
    To 'add two numbers' with a number called x and a number called y, returning a number.
    To greet.
```

- **`Library '<name>' version "<ver>".`** — the block's identity. Several `Library` blocks may appear in one `.lib`, each with its own `Location`; parsing runs to EOF, and a `Library` line starts a new block.
- **`Location "<path>".`** — where the `.so` is. It resolves relative to the `.lib` first, then `--lib-path`, then error. Absolute paths are honoured but never generated, so a `.lib` is portable.
- **`Table of Contents:`** — one line per exported function, in the same 11-type vocabulary as Vox source, in EITHER position: `number`, `float`, `text`, `boolean`, `list`, `map`, `buffer`, `file`, `time`, `timer`, `value`; anything else is an error naming the unsupported type. `void` isn't a spelling — a function returning nothing omits the `, returning` clause entirely — and neither is `unknown`, the compiler's own internal placeholder for an untyped parameter. A user-defined thing has no noun here either, so a `--shared` build refuses an export that takes or returns one — see [`.lib` export of a thing is not yet supported](https://vox-lang.dev/docs/things/#lib-export-of-a-thing-is-not-yet-supported).
- A `list` may optionally carry its element type: `a list of text called out`, `returning a list of text`. This is compiler-inferred, not author- declared — Vox source has no generic/typed-collection syntax, so a library author still just writes `a list called out`; a `--shared` build scans the exported function's own body and, when every appended/returned element provably agrees on one type, writes `list of <type>` for you. Disagreement or no evidence emits plain untyped `list`, same as always. `map`'s value type isn't carried this way; `map` stays element-untyped in both positions.
- **`, returning a <type>`** exists only in `.lib` files. Vox source declares return types in the body (`Return a number, x.`), which a bodiless `.lib` declaration has no room for. No `returning` clause means the function returns nothing — which is also why a list's element type only shows up in the `.lib` when the exporting function has a *declared* return type (`Return a list, out.`) in the first place; a bare `Return out.` records no return type at all, list-element-typing included.

A `.lib` is lexed with the Vox lexer but parsed by a dedicated parser, so it cannot carry executable statements — only the interface above.

### Consuming a library

```
see mathkit version "1.0" from "./libmathkit.lib".

a number called sum is 'add two numbers' of 3 and 4.
Print the sum.
```

```bash
$ vox mathkit_consumer.vox -o consumer
$ ./consumer
7
```

That is the whole consumer build — no `--link`, no `-l`, no `-L`. The `see` does the linking, because the `.lib` says where the `.so` is.

`see` of a `.lib` is the consumption path. The compiler:

1. Resolves the `.lib` (relative to the source, then `--lib-path`).
2. Parses it and selects the block matching name **and** version.
3. Resolves `Location` relative to the `.lib`, then `--lib-path`.
4. **Verifies against the `.so`'s dynamic symbol table** — every mangled name the `.lib` promises must exist in the `.so`. This is the staleness check: a `.lib` that lies about a function is a compile error, not a runtime crash.
5. Registers the signatures, so calls type-check like any other function.
6. Emits `extern <mangled>` for each used function and adds the `.so` and an `-rpath` to the link line.

The `-rpath` in step 6 is the directory the `.so` was found in, recorded as an absolute `RUNPATH`. So the program finds its library where it stood at build time; move the `.so` afterwards and the loader will not find it, unless `LD_LIBRARY_PATH` points at the new directory.

Each failure is its own diagnostic naming the file and what was expected: missing `.lib`; no such library in it; **version mismatch, listing the versions the `.lib` does offer**; missing `.so` at `Location`; symbol absent from the `.so` (the stale-`.lib` case — it names the symbol); arity or type mismatch at the call site; and reading the *result* of an entry that has no `, returning` clause, which returns nothing, so there is no value to read — call it as a statement instead.

The stale-`.lib` case is the one you meet by hand-editing a `.lib` or by rebuilding a library with different exports:

```text
Error: the .lib entry 'To 'ghostgreet' ...' promises the symbol
'mathkit_1_0_ghostgreet', but 'libmathkit.so' does not export it (not in
.dynsym).
The .lib is stale: it does not match the library binary. Rebuild the library
with `vox --shared` to regenerate the pair.
```

The worked example set in [`examples/`](https://github.com/Vox-lang/vox/tree/bf2cba037976586c30d22a48e7b7246d347f54a1/examples) shows the workflow: `mathkit_lib.vox` is the library and `mathkit_consumer.vox` is the Vox consumer above. A foreign caller — C, Rust, or assembly linking the `.so` directly — is shown in [Calling a library from a non-Vox host](https://vox-lang.dev/docs/libraries-and-imports/#calling-a-library-from-a-non-vox-host) below.

### Several libraries in one `.so`

`vox a.vox b.vox --shared -o lib.so` links several libraries into one `.so` in a single step — you cannot append to a linked `.so`, so one link step is the only way to combine them. The sources are parsed independently and then compiled into one unit, so the runtime is included once and shared by every library in the `.so`.

The reason this exists is **backwards compatibility**: two *versions* of the same library can live in one `.so`, kept apart by mangling. A consumer who upgrades the library keeps calling `mathkit_1_0_add_two_numbers` after `mathkit_2_0_add_two_numbers` ships beside it, with no recompile — both symbols are present and independently callable. That version isolation is why the whole design looks the way it does, and it is the case to keep in mind when the rest of it seems elaborate.

Duplicate `<library, version>` pairs across inputs are rejected with both filenames. Multi-input is `--shared` only; it is rejected for executable builds, where the semantics would be ambiguous.

### Mangling

Every exported function is mangled to a single flat label:

```text
<library>_<version>_<func>
```

`mathkit` + `1.0` + `add two numbers` → `mathkit_1_0_add_two_numbers`. Each component is sanitized by mapping every character outside `[A-Za-z0-9_]` to `_`. The leading-digit prefix (a digit is not a legal C identifier start) applies only to the **library** component, which begins the symbol; the version and function components are interior and take the sanitizer alone, so the version `1.0` appears as `1_0` (the `.` becomes a single `_`, no prefix — applying the prefix per component would yield `mathkit__1_0_add_two_numbers`, a double underscore). A non-Vox caller — C, Rust, anything that links the `.so` — needs this mangled name to call the function at all, which is why the scheme is documented here and not only in [docs/SYMBOL_MANGLING.md](https://github.com/Vox-lang/vox/blob/bf2cba037976586c30d22a48e7b7246d347f54a1/docs/SYMBOL_MANGLING.md) (the full rules, including what is and is not mangled). There is no unmangled alias: an alias would defeat the version isolation above.

**Runtime state is not mangled** — a deliberate non-goal. The runtime is emitted once per `.so` and shared by every library in it (one resource table, one `.fini_array`, one idempotent cleanup). Cross-`.so` isolation holds because each `.so` carries its own runtime and the version script hides it. Only function labels are mangled. See [docs/SYMBOL_MANGLING.md](https://github.com/Vox-lang/vox/blob/bf2cba037976586c30d22a48e7b7246d347f54a1/docs/SYMBOL_MANGLING.md).

### Calling a library from a non-Vox host

A shared library is a plain `.so`, so any caller that can link one can use it — C, Rust, or hand-written assembly. This is also the case the mangling scheme above exists for: the foreign caller must name the export by its mangled label. Build the example library, then call it from a small assembly driver (nasm + ld only — the tools Vox already requires). Run these from the `examples/` directory:

```bash
$ vox mathkit_lib.vox --shared -o libmathkit.so
$ nm -D --defined-only libmathkit.so
000000000000072c T mathkit_1_0_add_two_numbers
000000000000076c T mathkit_1_0_greet
```

```nasm
; mathkit_driver.asm — link against libmathkit.so and call its exports.
global _start
extern mathkit_1_0_add_two_numbers
extern mathkit_1_0_greet

section .text
_start:
    and rsp, -16            ; 16-byte stack alignment for the Vox prologue
    mov rdi, 3
    mov rsi, 4
    call mathkit_1_0_add_two_numbers  ; mathkit_1_0_add_two_numbers(3, 4) -> 7, in rax
    cmp rax, 7
    jne .fail
    call mathkit_1_0_greet            ; prints "hello from mathkit"
    mov rax, 60             ; SYS_exit
    xor rdi, rdi
    syscall
.fail:
    mov rax, 60
    mov rdi, 2
    syscall
```

```bash
$ nasm -f elf64 -o mathkit_driver.o mathkit_driver.asm
$ ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 -rpath '$ORIGIN' \
      -o mathkit_driver mathkit_driver.o -L. -lmathkit
$ ./mathkit_driver
hello from mathkit
```

The driver declares the exports `extern` and calls them with the Vox calling convention: integer arguments in `rdi`, `rsi`, … and the result in `rax`. `-rpath '$ORIGIN'` makes it find `libmathkit.so` in its own directory, so the pair is relocatable. (The `.asm` extension is gitignored under `examples/` because the compiler emits `.asm` there as output, so this driver is shown here rather than tracked as a file — copy it out to run it.) The `extern` names are the mangled labels `mathkit_1_0_add_two_numbers` and `mathkit_1_0_greet`, matching what `nm -D` showed above.

### Linking an executable against a `.so` directly

If the library has a `.lib`, you do not need this: `see` it, which registers its signatures *and* links its `.so`. `--link` is for a `.so` with no `.lib` — foreign, or hand-built — where the compiler knows no Vox signatures and only the linker is involved.

`--link` puts a built `.so` on the link line of an executable. It takes the library's soname *stem* — the part between `lib` and `.so` — so a file named `libmath.so` is linked as `--link math`:

```bash
$ vox hello.vox --link math --lib-path ./libs -o hello
$ readelf -d hello | grep -E 'NEEDED|RUNPATH'
 0x0000000000000001 (NEEDED)             Shared library: [libmath.so]
 0x000000000000001d (RUNPATH)            Library runpath: [./libs]
```

Because such an executable needs the dynamic loader at runtime, `--link` automatically adds the loader (`/lib64/ld-linux-x86-64.so.2`) and an `-rpath` for each `--lib-path` — but only when libraries are actually linked, so a plain `vox hello.vox` build stays a flat static binary with no loader dependency.

`--link` alone does not teach the compiler a library's function signatures, so it does not let Vox source call the library's functions — that is what `see` of a `.lib` does (it registers the signatures *and* adds the `.so` to the link line). `--link` is for the case where the program already references the symbols another way, or for a non-Vox driver assembled by hand — the [Calling a library from a non-Vox host](https://vox-lang.dev/docs/libraries-and-imports/#calling-a-library-from-a-non-vox-host) driver above is exactly that, linked with `ld` rather than `--link`.
