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