Introducing Achronyme — a language for zero-knowledge proofs. Read the announcement

Modules

Splitting programs across files with import and export.

Achronyme supports splitting code across multiple .ach files using modules. Export declarations to make them available, and import modules by namespace or by name.

Exporting Declarations

Inline Exports

Use export before fn or let to make a declaration available to other files.

// math.ach
export fn add(a, b) { a + b }
export fn mul(a, b) { a * b }
export let PI = 3

Only fn and let can be exported. Mutable variables (mut), public, and witness declarations cannot be exported.

// These are errors:
export mut x = 5      // ✗ cannot export mut
export public y       // ✗ cannot export public

Export Lists

You can also collect exports at the end of a file using export { ... }:

// math.ach
fn add(a, b) { a + b }
fn mul(a, b) { a * b }
let PI = 3

export { add, mul, PI }

This is equivalent to marking each declaration with export individually. You can mix both styles in the same file. Exporting the same name twice (inline + list, or listed twice) is an error.

Declarations without export are private to the file. They can be used by exported functions internally but are not accessible from outside.

// helpers.ach
fn internal_helper(x) { x * x }    // private

export fn square(x) {
    internal_helper(x)              // works inside this file
}

Importing a Module

Namespace Import

Use import "path" as alias to load a module. The alias is mandatory and becomes the namespace for accessing the module’s exports.

// main.ach
import "./math.ach" as math

print(math.add(1, 2))    // 3
print(math.PI)            // 3

Access exported values using dot notation: alias.name.

Selective Import

Use import { name1, name2 } from "path" to import specific names directly into scope, without a namespace prefix:

import { add, PI } from "./math.ach"

print(add(1, 2))    // 3 — no prefix needed
print(PI)            // 3

Selective imports copy the value at import time (snapshot semantics). Changes to the original module’s globals after import are not reflected.

If a requested name is not exported by the module, the compiler suggests the closest match:

error: module "math.ach" does not export `ad`. Did you mean `add`?

You can combine both styles for the same module:

import { add } from "./math.ach"
import "./math.ach" as math

print(add(1, 2))        // selective — no prefix
print(math.mul(3, 4))   // namespace — with prefix

Module Paths

Paths are relative to the importing file and must include the .ach extension.

import "./utils.ach" as utils          // same directory
import "../lib/hash.ach" as hash       // parent directory
import "./crypto/poseidon.ach" as pos  // subdirectory

Absolute paths are not supported. All imports use relative paths.

Transitive Imports

Modules can import other modules. The chain is resolved automatically.

// a.ach
export fn fa() { 1 }

// b.ach
import "./a.ach" as a
export fn fb() { a.fa() + 1 }

// c.ach
import "./b.ach" as b
print(b.fb())    // 2

Circular Imports

Circular dependencies are detected and produce an error.

// x.ach
import "./y.ach" as y    // error: CircularImport

// y.ach
import "./x.ach" as x

Modules in Circuits

Imports work with ach circuit the same way as ach run. Imported functions are inlined at each call site.

// hash_lib.ach
export fn my_hash(a, b) { poseidon(a, b) }

// circuit.ach
import "./hash_lib.ach" as h

circuit hash_check(out: Public, a: Witness, b: Witness) {
    assert_eq(h.my_hash(a, b), out)
}

Errors

ErrorCause
ModuleNotFoundThe file path does not resolve to an existing file
CircularImportTwo or more modules import each other
ModuleLoadErrorThe file was found but contains parse errors
DuplicateModuleAliasTwo different files imported with the same alias
Name not exportedimport { x } where x is not exported by the module (with “did you mean?” suggestion)
Duplicate exportThe same name is exported more than once (inline + list, or listed twice)
Import conflictA selectively imported name conflicts with an existing global or a prior import from a different module
Export list undefinedexport { x } where x is not defined in the module

Warnings

CodeCause
W005A selectively imported name is never used (prefix with _ to suppress)

Restrictions

  • import and export are only allowed at the top level (not inside functions or blocks)
  • import, export, and as are reserved keywords
  • from is not a keyword — it is recognized contextually only after import { ... }, so it can be used as a variable name elsewhere
  • Selective imports use snapshot semantics (the value is copied at import time)
  • Re-exports (export import) are not supported
  • Importing the same file with two different aliases is allowed (the file is parsed once)

Quick Reference

SyntaxDescription
export fn name() { ... }Export a function (inline)
export let X = valueExport a constant (inline)
export { a, b, c }Export multiple names (list)
import "./file.ach" as aliasNamespace import
import { a, b } from "./file.ach"Selective import
alias.nameAccess a namespace binding
Navigation