Skip to content

Imports and modules (how-to)

This page shows how to structure multi-file projects and use imports in practice.

Prerequisite: follow Install, build, and run so you can run incan.

Contributor source-build fallback

If you are working from a compiler checkout instead of a toolchain install, you can run the repository-built binary directly:

  • from the repository root:
./target/release/incan ...
  • or via an absolute path (from anywhere):
/absolute/path/to/incan run path/to/file.incn

Simple multi-file project

Recommended structure:

myproject/
├── main.incn
├── models.incn
└── utils.incn

Example imports:

from models import User
import utils::format_currency

Nested projects

Recommended structure:

myproject/
└── src/
    ├── main.incn
    ├── db/
    │   └── models.incn
    └── shared/
        └── utils.incn

Example imports:

from db.models import User
import shared::utils::format_date

Publish a structured library

Create the library with its public declarations in ordinary source modules:

hyperquant-lib/
├── incan.toml
└── src/
    ├── lib.incn
    └── hyperquant/
        ├── index.incn
        └── search.incn
# src/hyperquant/index.incn
pub model HyperquantIndex:
  pub size: int

pub def build_index(size: int) -> HyperquantIndex:
  return HyperquantIndex(size=size)

def pack_bits(size: int) -> int:
  return size
# src/hyperquant/search.incn
from hyperquant.index import HyperquantIndex

pub def search(index: HyperquantIndex) -> int:
  return index.size

Build the checked library artifact:

cd hyperquant-lib
incan build --lib

Add the library to the consumer:

[dependencies]
hyperquant_lib = { path = "../hyperquant-lib" }

Import the source-derived namespace:

from pub::hyperquant_lib import hyperquant

def main() -> None:
  index = hyperquant.build_index(1024)
  println(hyperquant.search(index))

No entry in src/lib.incn is needed merely to make hyperquant exist. Keep explicit pub from entries there when you also want a curated flat facade:

# src/lib.incn
pub from hyperquant.index import HyperquantIndex, build_index

When sibling modules publish the same declaration name, import through the exact child module instead of the parent:

from pub::codecs.encoding.base64 import encode
from pub::codecs.encoding.hex import encode as encode_hex

The compiler reports the conflicting source modules if you try the ambiguous parent import.

How module discovery works (practical view)

When you import a local module, the compiler:

  1. Resolves the path (handling ., .., super, crate).
  2. Looks for the .incn file (or mod.incn for directories).
  3. Parses and type-checks that file.
  4. Makes its types and functions available in your importing file.

Published-library consumers follow a different trust boundary: they resolve pub:: imports from the checked .incnlib artifact and generated library crate rather than re-reading the dependency's source.

Examples from the repo

  • Multi-file example: examples/advanced/multifile/

Run:

incan run examples/advanced/multifile/main.incn
  • Nested project example: examples/advanced/nested_project/

Run:

incan run examples/advanced/nested_project/src/main.incn

If you prefer browsing first, see the examples directory on GitHub: https://github.com/encero-systems/incan/tree/main/examples.

Share module-owned state across files

If a module needs to export live runtime state, use pub static:

This example shares live runtime state across module boundaries with pub static:

counters.incn
pub static hits: int = 0

pub def record_hit() -> None:
    hits += 1
main.incn
from counters import hits, record_hit

def main() -> None:
    record_hit()
    record_hit()
    println(hits)

This prints 2.

For the full walkthrough, see: Module state (how-to).