Ion Fusion Documentation
Release 0.38a1-SNAPSHOT (2026-04-16T19:45:37.790Z)

Module /fusion/module

The basic organizational component of Fusion code.

The Fusion language is structured around modules. The module is the unit of dependency management, code loading, and access control. It is similar to a .so library in Unix, or to a class in Java. Loading a Fusion module triggers loading of its required dependencies.

Most Fusion source files contain a single module holding a bunch of related code. In general, the purpose of this code is to populate a namespace. Each module has its own namespace holding any number of variables or bindings. These bindings are kept private to the module by default, or they may be provided (made public) for use by other modules.

A script is a Fusion source file that's not a module. As such, it's invoked differently, does not have a dedicated namespace, and cannot protect or export bindings. Instead, a script is run inside a namespace provided by the invoker of the script. This works very similarly to "sourcing" shell scripts in Unix: the script is run inside the environment of the invoker, and can change or extend that environment at will.

Both modules and scripts are expressed in terms of a language that provides meaning to the syntax, operators, procedures, etc. that are used therein. Fusion doesn't hard-code any of this: beyond the syntax of Ion, there are no fixed keywords, and no fixed semantics to any piece of syntax. The meaning of the syntax can be entirely customized to create an independent language. All of those languages, including "standard" Fusion itself, are simply modules exporting bindings that define the syntax and semantics of the language.

Module Identifiers

Fusion modules are organized into a hierarchy and are identified with a notation similar to a URI. The module /fusion is both the default language and the parent of many submodules that make up the built-in libraries. Fusion applications and libraries are expected to utilize their own independent module hierarchies to avoid name conflicts.

Fusion restricts module names to start with an ASCII letter, followed by more ASCII letters, digits, or underscores. By convention, submodules named private indicate hierarchies that should not be used except by their vendor.

Like the path portion of a URI, module identifiers are strings starting with "/" followed by one or more module names, each separated by "/". The first segment is the name of a root module, with further segments denoting successive submodules. For example, the module identifier /fusion/list denotes the submodule named list of the root module named fusion.

Repositories

A repository is a simple component that provides access to modules and other resources. The Fusion runtime is configured with one or more repositories, arranged into a prioritized sequence; the highest priority is assigned to the bootstrap repository that contains the standard libraries. In general, repositories are searched from highest to lowest priority to find a required module, but it will be possible for a module to allow itself to be shadowed or extended by lower-priority repositories.

Repositories are usually implemented as a file-system directory, and module source code is held in a subdirectory called src which in turn contains one or more root modules and their submodules. Each module is contained in its own file whose name matches the module name and whose extension is .fusion. As with Java, module source files are arranged in file-system hierarchies that mirror the module hierarchy, and the names of the modules are (usually) derived from the file names.

To illustrate, the bootstrap repository is structured like this:

BOOTSTRAP_REPO/
               src/
                   fusion.fusion
                   fusion/
                          base.fusion
                          collection.fusion
                          experimental/
                                       syntax.fusion
                          sexp.fusion
                          ...

The .fusion files above contain the source for the modules /fusion, /fusion/base, /fusion/collection, /fusion/experimental/syntax, and /fusion/sexp.

Module Syntax

You declare a module using the module form:

(module <NAME> <LANGUAGE>
  <CODE> ...+)

The name, written as an Ion identifier, is only meaningful when this form is encountered within a script, in which case it denotes a local name by which the module can be used. When the module is loaded from a file (by far the most common case), this name is ignored because the module's identity is inferred from the source file's name and path within the repository.

The language declaration is far more critical, since it determines the semantics associated with all of the code within the module. It's written as an Ion string, holding the path to the module. The usual value is "/fusion", the standard Fusion language.

Code often requires features beyond those provided by the declared language. The require form imports bindings from another module:

(require <MODULE_REFERENCE> ...+)

In most cases the module reference is a string containing a path to a module. This imports all of the public bindings provided by that module into the enclosing namespace.

The provide form exports bindings from a module's namespace to the users of the module:

(provide <ID> ...)

The given identifiers must all be defined within the enclosing namespace or required from another module, and they are all made public and available for import elsewhere.

Here's a tiny sample module to tie it all together. It simply defines and exports two small procedures:

(module tiny "/fusion"
  (require "/fusion/sequence")

  (provide second next_to_last)

  (define (second seq)
    (element seq 1))

  (define (next_to_last seq)
    (element seq (- (size seq) 2))))

Module References

When requireing a module, you use a module reference to identify the target module. These references are syntactic forms that the compiler resolves to a module identifier for the target.

Most module references are module paths that describe how to get to the target module from the referring namespace. These paths are denoted (equivalently) as strings or symbols, the difference being purely syntactic. Stylistically, we recommend using strings for module paths, except when referring to a sibling module, where an Ion identifier suffices and is easier to read.

Paths that start with "/" must be proper module identifiers and they denote absolute paths. All other paths are parsed as relative.

A relative path is resolved based on the (absolute) identifier of the module in which it appears; see below for information on references from top-level code. Relative references are structured as "/"-separated segments composed in one of these ways:

Any "." and ".." segments can only occur at the front of the path.

WARNING: "." and ".." segments are not yet implemented! See issue #28.

WARNING: Relative paths are only supported at top-level! See issue #29.

For example, consider these imports within a module /root/parent/self:

(require sibling)             // Resolves to /root/parent/sibling
(require "sibling")           // Equivalent
(require "sibling/niece")     // Resolves to /root/parent/sibling/niece

(require "./child")           // Resolves to /root/parent/self/child
(require "./child/grand")     // Resolves to /root/parent/self/child/grand

(require ..)                  // Resolves to /root/parent
(require "..")                // Equivalent
(require "../sibling")        // Resolves to /root/parent/sibling
(require "../sibling/niece")  // Resolves to /root/parent/sibling/niece
(require "../..")             // Resolves to /root
(require "../../..")          // ERROR: /root has no parent

Module References from Top-Level

At top-level (e.g., at the REPL), relative paths are resolved based on a unique, transient pseudo-module identifier whose only purpose is to scope modules uniquely within that top level. Relative references can therefore only address modules (and submodules) declared within that top-level, and those modules cannot be referenced from outside the top-level namespace. Furthermore, "." and ".." segments are not allowed in paths from a top-level require, since the top-level is not a module and does not have a parent module.

Naming Modules

Like Java, Fusion arranges code in a hierarchy. Unlike Java, that hierarchy is intended to be active in providing metadata around access protection, security, and so on. Furthermore, we anticipate dynamic code distribution mechanisms that well assume and enforce a global hierarchy. Therefore it is more important in Fusion to carefully select a namespace within the hierarchy, since conflicts will cause more problems.

DO NOT CREATE SUBMODULES OF /fusion. That hierarchy is reserved for exclusive use by the Fusion platform.

We recommend that all user code be placed in modules that follow a naming convention similar to Java packages. For example, code that in Java would be under the package org.example.company.project would be in the module /org/example/company/project in a Fusion package.

all_defined_out syntax
(all_defined_out)

A provide clause that exports all bindings defined by the enclosing module. Imported bindings are not exported.

This form can only appear within provide.

module syntax
(module name language body ...+)

Declares a module containing the given body. The name must be a symbol; it is ignored when loading a module from a file.

The language must be an absolute module path. The denoted module is instantiated and all of its provided bindings are immediately imported. This "bootstraps" the module with a set of bindings that form the base semantics for the body. Unlike bindings that are required, these bindings can be shadowed by module-level definitions and by require statements.

When compiling a module, the body forms are partially macro-expanded in order to discover certain core forms like require and provide. The former are handled immediately, before other forms. The latter are handled after all other forms. At module level, the elements within begin forms are spliced into the enclosing module body, replacing the single begin form with its elements. This effectively enables module-level macro uses to expand into multiple forms.

only_in syntax
(only_in module_path id ...)

A require clause that imports only the given ids from a module. If an id is not provided by the module, a syntax error is reported.

Bindings introduced by this form use the lexical context of the module path, not that of the given identifiers.

This form can only appear within require.

prefix_in syntax
(prefix_in prefix_id module_path)

A require clause that adjusts each identifier to be bound by prefixing it with prefix_id.

Bindings introduced by this form use the lexical context of the module path, not that of the prefix identifier.

This form can only appear within require.

provide syntax
(provide provide_clause ...)

Declares bindings to be exported from the enclosing module. This form may only appear at module level.

Each provide_clause denotes some names to be exported. The following clause forms are allowed:

  • An identifier defined at module-level or imported from another module.
  • all_defined_out exports all module-level definitions.
  • rename_out exports selected bindings, giving them new names on the way out.

Within a module, a single provide form with multiple clauses behaves identically to multiple provide forms with single clauses.

rename_in syntax
(rename_in module_path (exported_id local_id) ...)

A require clause that imports each exported_id using the name local_id. If an exported_id is not provided by the module, a syntax error is reported.

In contrast to other require-clauses, bindings introduced by this form use the lexical context of the local_ids, not that of the module path.

This form can only appear within require.

Warning: This behaves differently than Racket's rename-in, which imports everything from the given module (or nested require-clause) while renaming selected items from that set. In contrast, this form ignores exported bindings that are not renamed.

rename_out syntax
(rename_out (local_id exported_id) ...)

A provide clause that exports each local_id using the name exported_id. This effectively renames the binding on export.

This form can only appear within provide.

require syntax
(require require_clause ...+)

Declares bindings to be imported into the enclosing namespace. This form may only appear at module level or top level.

Each require_clause denotes some bindings to be imported. The following clause forms are allowed:

  • A string or symbol containing a module path; all names provided by the referenced module are imported. The bindings introduced by this form use the lexical context of the module path.
  • only_in enumerates a set of names to import.
  • prefix_in provides a prefix to imported bindings.
  • rename_in renames specified bindings.

Within a module, require declarations are processed before other forms, regardless of their order within the module source, and imported bindings are scoped across the entire module. No identifier may be imported multiple times, unless all such bindings refer to the same originating definition. Furthermore, no identifier may have both an import and a module-level definition. In other words: module-level bindings introduced by require or define must not conflict, although either may shadow same-named bindings introduced by the module's language declaration.

At top level, require will replace an existing import, and may shadow an existing top-level definition.