Skip to main content

Modularity

Source File Structure

An Aqua source file has a header and a body. The body contains function definitions, services, types, constants. The header is dedicated to code management: it specifies the name of the module, what is declared by the module, what is exported from the module and what is imported into the module.

Giving a name to an Aqua module with aqua

Every Aqua source file should begin with aqua keyword followed by the the name of the aqua module presented by the file.

aqua
-- `aqua` expression may only be on the very first line of the file
aqua AquaFile
aqua
-- `aqua` expression may only be on the very first line of the file
aqua AquaFile

Module name can contain dots, e.g. aqua Aqua.File.

This name is used when the module is imported with use, see Importing other modules with use.

Specifying what is declared by the module with declares

The aqua AquaFile expression may optionally include a declares section. This section enumerates the elements that the module will make available for other modules that import it. If the declares section is omitted, the module does not declare anything for other modules to use.

aqua
-- This module declares `CONST_NAME`, `ServiceName`, `MyType` and `fn`
aqua AquaFile declares CONST_NAME, ServiceName, MyType, fn
const CONST_NAME = "something"
service ServiceName:
do_something()
data MyType:
result: i32
func fn() -> string:
<- CONST_NAME
aqua
-- This module declares `CONST_NAME`, `ServiceName`, `MyType` and `fn`
aqua AquaFile declares CONST_NAME, ServiceName, MyType, fn
const CONST_NAME = "something"
service ServiceName:
do_something()
data MyType:
result: i32
func fn() -> string:
<- CONST_NAME

To declare everything contained in the file, use declares *:

aqua
-- This module declares `CONST_NAME`, `ServiceName`, `MyType` and `fn`
aqua AquaFile declares *
const CONST_NAME = "something"
service ServiceName:
do_something()
data MyType:
result: i32
func fn() -> string:
<- CONST_NAME
aqua
-- This module declares `CONST_NAME`, `ServiceName`, `MyType` and `fn`
aqua AquaFile declares *
const CONST_NAME = "something"
service ServiceName:
do_something()
data MyType:
result: i32
func fn() -> string:
<- CONST_NAME

Note that symbols declared with declares are not exported to the host language. To export symbols to the host language, use export.

Importing other modules

Aqua modules can import other modules to use their declarations. There are two ways to import a module: with import and use.

With use

The use expression makes it possible to import a module as a named scope. The name of the scope is taken from aqua header of the imported module. Everything declared in the imported module is available in the current namespace as a member of the scope.

aqua
aqua AquaFile declares foo
-- builtin.aqua declares `Op`
use "@fluencelabs/aqua-lib/builtin.aqua"
func foo():
BuiltIn.Op.noop()
aqua
aqua AquaFile declares foo
-- builtin.aqua declares `Op`
use "@fluencelabs/aqua-lib/builtin.aqua"
func foo():
BuiltIn.Op.noop()

It is possible to rename the imported module with use ... as ... expression:

aqua
aqua AquaFile declares foo
-- builtin.aqua declares `Op`
use "@fluencelabs/aqua-lib/builtin" as Renamed
func foo():
Renamed.Op.noop()
aqua
aqua AquaFile declares foo
-- builtin.aqua declares `Op`
use "@fluencelabs/aqua-lib/builtin" as Renamed
func foo():
Renamed.Op.noop()

It is also possible to cherry-pick and rename imports using use ... as ... from ... as ...:

aqua
aqua AquaFile declares foo
-- builtin.aqua declares `Op`
use Op as Noop from "@fluencelabs/aqua-lib/builtin" as Renamed
-- multiple imports are allowed
-- dependency.aqua declares functions `foo`, `baz` and `bar`
import foo as f, baz, bar as b from "dependency.aqua" as Dep
func foo():
Dep.f()
Dep.baz()
Dep.b()
Renamed.Noop.noop()
aqua
aqua AquaFile declares foo
-- builtin.aqua declares `Op`
use Op as Noop from "@fluencelabs/aqua-lib/builtin" as Renamed
-- multiple imports are allowed
-- dependency.aqua declares functions `foo`, `baz` and `bar`
import foo as f, baz, bar as b from "dependency.aqua" as Dep
func foo():
Dep.f()
Dep.baz()
Dep.b()
Renamed.Noop.noop()

Creation of a scope with use makes it easier to avoid name clashes and to understand where the symbol comes from. Thus it is recommended to prefer use instead of import when possible.

With import

Another way to import a module is via import. In this case, everything declared in the imported module comes into the current namespace directly.

aqua
aqua AquaFile declares foo
-- builtin.aqua declares `Op`
import "@fluencelabs/aqua-lib/builtin.aqua"
func foo():
Op.noop()
aqua
aqua AquaFile declares foo
-- builtin.aqua declares `Op`
import "@fluencelabs/aqua-lib/builtin.aqua"
func foo():
Op.noop()

It is possible to cherry-pick and rename imports using import ... as ... from ...:

aqua
aqua AquaFile declares foo
-- builtin.aqua declares `Op`
import Op as Noop from "@fluencelabs/aqua-lib/builtin"
-- multiple imports are allowed
-- dependency.aqua declares functions `foo`, `baz` and `bar`
import foo as f, baz, bar as b from "dependency.aqua"
func foo():
f()
baz()
b()
Noop.noop()
aqua
aqua AquaFile declares foo
-- builtin.aqua declares `Op`
import Op as Noop from "@fluencelabs/aqua-lib/builtin"
-- multiple imports are allowed
-- dependency.aqua declares functions `foo`, `baz` and `bar`
import foo as f, baz, bar as b from "dependency.aqua"
func foo():
f()
baz()
b()
Noop.noop()

Imports resolution

To learn how compiler resolves the import path, see JS Aqua API.

info

.aqua extension in import and use expressions can be omitted. So, import "builtin.aqua" does exactly the same as import "builtin".

Exporting to the host language with export

Inside Aqua language code modularity is achieved with declares, import and use on module level (see also Abilities as more fine grained method of code organization). However, what should be exported to the host language depends on the particular use case of aqua code and has nothing to do with code management inside Aqua. This is why exporting to the host language is a separate concept inside Aqua.

It is possible to specify what should be exported to the host language with export. Exporting symbols that were imported from other modules is allowed. There could be several exports in a file and they are all merged into one.

aqua
aqua Lib
import bar from "lib"
-- Exported functions and services will be compiled
-- into the host language
export foo
export bar, MySrv
service MySrv:
call_something()
func foo() -> bool:
<- true
aqua
aqua Lib
import bar from "lib"
-- Exported functions and services will be compiled
-- into the host language
export foo
export bar, MySrv
service MySrv:
call_something()
func foo() -> bool:
<- true

To export a symbol under a different name, use export ... as ...:

aqua
aqua Lib
export foo as foo_bar
func foo() -> bool:
<- true
aqua
aqua Lib
export foo as foo_bar
func foo() -> bool:
<- true

Note that export does not make the symbol available for other modules that import the current module. To make a symbol available for other modules, use declares.