Modules
StarLang has two types of modules: source modules (pure .star files) and native modules (C-backed with .star stubs).
import
Load modules with import. Each import creates a namespace — access symbols with namespace.symbol().
package main
import "math"
import mqtt "net/mqtt"
fn main():void {
var x: number = math.sqrt(16.0)
mqtt.open("host", 1883, "id")
}
Import Syntax
Two forms are supported:
Both create a namespace. Symbols are accessed with dot notation:
Path Resolution
Imports are resolved in two steps:
- Local —
<base_dir>/<module>.star(relative to the importing file) - star.mod — if not found locally, search
requirepaths fromstar.mod
import "utils" # → ./utils.star (local)
import "math" # → star.mod require path → dev-libs/math/math.star (native)
star.mod
Dependencies are declared with require <path> <version> in the project's star.mod manifest:
import "math" then resolves to that path, and math.sqrt(x) compiles to an OP_NATIVE_CALL. Only modules listed in star.mod are available for native import. See the star.mod reference for the full manifest format (module, star, require, config).
Export
The export keyword controls function visibility in modules. Only exported functions are accessible from importers.
In this example, add is accessible via mylib.add(), but helper is not visible to importers.
If no function in a module uses export, all functions are visible (backward compatible).
Native Modules
Native modules are implemented in C and exposed to StarLang through stub declarations. A native function stub has the export fn signature but no body:
package math
export fn sin(x: number): number
export fn cos(x: number): number
export fn sqrt(x: number): number
export fn pow(base: number, exp: number): number
The compiler recognizes stubs (no { after return type) and emits OP_NATIVE_CALL instead of a regular function call. Each native is identified by a contract hash computed from its module, symbol, parameter types and return type; the image records the natives it needs in its NREQ section, and the VM resolves each one to a registered C function at load — no string lookup at runtime, and no dependence on the order natives were registered.
Because resolution is by hash, a runtime only needs to register the natives it actually provides. A device firmware (e.g. ESP32) typically registers just the compute subset — math, time, json, queue, crypto — and omits host I/O modules like http, net, ws, mqtt and config. An image that imports a native the runtime didn't register fails to load with a missing native error listing the unresolved contract hashes, rather than calling the wrong function.
Available Native Modules
| Module | Functions |
|---|---|
math |
sin, cos, tan, sqrt, abs, floor, ceil, round, pow, log, log10, atan2, min, max |
Using Native Modules
-
Add
requiretostar.mod: -
Import and use:
Source Modules
Regular .star files with function implementations:
These compile inline into the main bytecode — no separate linking step.
Module Layout
project/
star.mod
main.star
utils/
helpers.star
dev-libs/
math/
math.star # native stubs
math.c # C implementation
math.h
gpio/
gpio.star
gpio.c
gpio.h
No Name Collisions
Because all symbols are namespaced, two modules can define the same function name without conflict:
import mqtt "net/mqtt"
import http "net/http"
fn main():void {
mqtt.open("host", 1883, "id") # net/mqtt.star → connect()
http.get("url") # net/http.star → get()
}
Alias Collisions
Duplicate aliases are a compile error:
import "net/mqtt" # alias: "mqtt"
import "drivers/mqtt" # alias: "mqtt" → ERROR: duplicate import alias 'mqtt'
Fix by using explicit aliases:
Circular Imports
Circular imports are a compile error:
Restructure your code to break the cycle — extract shared symbols into a third module.
Unused Imports
Importing a module without using any of its symbols is a compile error:
Remove the import or use a symbol from the module to fix.
Rules
| Rule | Description |
|---|---|
| Single load | Each module is loaded at most once per compilation |
| Ordering | Imports must come after package, before other code |
| Namespaced | All imported symbols require alias.symbol access |
| Inline compilation | Source modules are compiled inline into the main bytecode |
| Native dispatch | Native modules use OP_NATIVE_CALL — direct index, no string lookup |
| Export visibility | If any function has export, only exported functions are importable |
| star.mod required | Native modules must be listed in star.mod with require |
| No duplicate aliases | Two imports cannot share the same alias |
| No circular imports | Circular dependencies are a compile error |
| No unused imports | All imports must be referenced (compile error otherwise) |
| Debug info | Each module retains its own source filename and line numbers |
| Max imports | Up to 32 imports per file (compile error if exceeded) |