Skip to main content

Packages

A Mochi package is a directory of .mochi files that share a namespace. Files declare the package they belong to with package at the top of the file. Names are private to the package unless marked with export.

The import form also has a multi-language variant for pulling in Go, Python, or TypeScript modules.

A single-file program is a package​

A single-file program needs no package declaration. The file forms an implicit "main" package.

hello.mochi
print("hello")
mochi run hello.mochi

Declaring a package​

Add package <name> to the top of every file that should belong to the same logical unit. The name must match the directory's name.

mathutils/add.mochi
package mathutils

export fun add(a: int, b: int): int {
return a + b
}
mathutils/sub.mochi
package mathutils

export fun sub(a: int, b: int): int {
return a - b
}

Both files share the namespace. add.mochi can call functions from sub.mochi without importing.

Exporting names​

Top-level definitions are package-private by default. Prefix with export to make them visible outside the package:

package mathutils

export type Pair {
left: int
right: int
}

export fun add(p: Pair): int {
return p.left + p.right
}

fun internal_helper(): int {
return 0 // visible only inside mathutils
}

Both types and functions can be exported. Exporting a type also exports its constructor and methods.

Importing a package​

Use import "<name>" to pull a package into the current file. The package becomes available under its name.

import "mathutils"

print(mathutils.add(Pair { left: 2, right: 3 }))

For a shorter alias, add as <alias>:

import "mathutils" as mu

print(mu.add(...))

Without an explicit alias, the package alias is the last segment of the path.

Local imports​

Paths beginning with ./ or ../ are resolved relative to the importing file. They can name a directory of .mochi files or a single .mochi file. The .mochi extension may be omitted.

import "./reading-list" as rl
import "../shared/types"
import "./helpers/strings.mochi"

This is the everyday form for multi-file projects.

Cross-package types and methods​

Imported types are referred to via the package name:

import "./reading-list" as rl

let book = rl.Book {
title: "Mochi for All",
author: "An Onymous",
pages: 200,
read: false
}

Methods on the type carry over. Call them with the dot operator on the value:

print(book.summary())

Re-exports​

A wrapper package can expose names from an inner package by combining a re-import with regular export:

package web

import "./internal/router" as router

export fun handle(req: Request): Response {
return router.dispatch(req)
}

To forward a type or function under the same name, declare a wrapper:

export fun add(a: int, b: int): int {
return mathutils.add(a, b)
}

There is no shorthand for forwarding many names at once. The convention is to keep the surface small enough that a few export fun lines do the job.

FFI imports for Go, Python, and TypeScript​

import has a multi-language form for calling into host runtimes. The syntax is import <lang> "<module>" as <alias>.

Calling Go​

import go "math" as math

extern fun math.Sqrt(x: float): float

print(math.Sqrt(16.0)) // 4

Calling Python​

import python "math" as math

extern fun math.sqrt(x: float): float

print(math.sqrt(49.0)) // 7

Calling TypeScript​

import typescript "./runtime/ffi/deno/math.ts" as math

extern fun math.pow(x: float, y: float): float

print(math.pow(2.0, 8.0)) // 256

For each FFI call, declare the function with extern fun so the compiler knows its types. The host-side runtime under runtime/ffi implements the bridge.

When as <alias> is omitted, the alias defaults to the last path component. See FFI in the language guide for the full rules.

Visibility rules​

FormVisible inside the package?Visible outside?
fun foo()YesNo
export fun foo()YesYes
let x = …YesNo
export let x = …YesYes
var x = …YesNo
export var x = …YesYes (mutable cross-package state, use sparingly)
type T { … }YesNo
export type T { … }YesYes

File layout conventions​

A typical project tree:

my-app/
main.mochi
reading-list/
book.mochi
storage.mochi
storage_test.mochi
shared/
types.mochi

Conventions:

  • Files in a package are split by topic, not size. Keep related types and functions together.
  • Test files end in _test.mochi and are picked up automatically by mochi test.
  • Lowercase, hyphenated directory names; matching package names use underscores: reading-list/ directory holds package reading_list.

Common errors​

MessageCauseFix
package name "X" does not match directory "Y"Mismatched declarationRename the directory or the package line.
cannot import package "X": not foundPath typoCheck the path; for local imports, use ./ or ../.
name "X" is not exported by package "Y"Calling an unexported nameAdd export to the declaration in the source package.
cyclic importTwo packages import each otherExtract shared types to a third package.

Next​

  • Variables: visibility starts at the binding
  • Types: exported types and methods
  • Tutorial: a worked example with a multi-file package