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
When an import list gets long, put it in parentheses, one name per line, with a trailing comma; this works for rust:: imports too.
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/
├── loaf.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:
- Resolves the path (handling
.,..,super,crate). - Looks for the
.incnfile (ormod.incnfor directories). - Parses and type-checks that file.
- 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.
flowchart LR
A["main.incn import"] --> B["resolve module path"]
B --> C["models.incn or models/mod.incn"]
C --> D["parse and type-check module"]
D --> E["checked public symbols"]
E --> F["importing module scope"]
Imports resolve files first, then expose checked symbols. They do not paste source text into the caller.
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:
pub static hits: int = 0
pub def record_hit() -> None:
hits += 1
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).
Import two names that would collide¶
Two imports that bind the same local name are refused, whether they name the same declaration or different ones. When both targets are intentional, give each an explicit alias:
import codecs.prelude as codecs_prelude
import compression.prelude as compression_prelude
Import a standard-library declaration¶
from std import name binds a standard-library module, not a declaration. When from std import Debug is refused, the diagnostic names the module that declares Debug when it is known; import the declaration from that module:
from std.derives.string import Debug
Activate async and await¶
Import std.async, or one of its modules such as std.async.time, before the first async def or await in the file. Without that import, async and await are refused (INCAN-P0001), and the diagnostic names the import to add.