Modules

A source file (a file ending with .bnj) is called a module. Modules are used for organizing code units hierarchically. The compiler looks for modules inside the src/ directory of the project and automatically loads them. A module can be imported from another one using use declarations.

Let’s say we have the following directory structure:

src/
├── main.bnj
└── math_utils.bnj

With the following content in math_utils.bnj:

func add(a: i32, b: i32) -> i32 {
    return a + b;
}

func multiply(a: i32, b: i32) -> i32 {
    return a * b;
}

We can now import math_utils into our main module:

use math_utils;

func main() {
    math_utils.add(15, 3);  # 18
    math_utils.multiply(2, 10);  # 20
}

You can also import specific symbols from another module:

use math_utils.add;
use math_utils.multiply;

func main() {
    add(15, 3);  # 18
    multiply(2, 10);  # 20
}

For importing multiple symbols from the same module, there is a more concise syntax:

use math_utils.{add, multiply};

func main() {
    add(15, 3);  # 18
    multiply(2, 10);  # 20
}

Imported symbols can be bound to a different name:

use math_utils as m;
use math_utils.multiply as mul;

func main() {
    m.add(15, 3);  # 18
    mul(2, 10);  # 20 
}

Submodules

Modules can be nested by creating a directory with a file called module.bnj that contains other modules. The hierarchy of modules corresponds to the filesystem hierarchy of the source files.

Here’s an example project structure:

src/
├── game/
│   ├── module.bnj
│   ├── player.bnj
│   ├── monster.bnj
│   └── world.bnj
├── engine/
│   ├── module.bnj
│   ├── graphics/
│   │   ├── module.bnj
│   │   └── renderer.bnj
│   ├── physics/
│   │   ├── module.bnj
│   │   ├── rigid_body.bnj
│   │   └── simulation.bnj
│   └── math/
│       ├── module.bnj
│       ├── vec3.bnj
│       └── mat4.bnj
└── main.bnj

We can now import these modules and their submodules:

# Importing an entire root module
use game;

# Importing a specific submodule from `game`
use game.monster;

# Importing multiple submodules
use game.{player, world};

# Importing a specific symbol
use engine.graphics.renderer.VulkanRenderer;

# Importing multiple nested symbols
use engine.{
    physics.rigid_body.RigidBody,
    math.{vec3.Vec3, mat4.Mat4}
};

The Standard Library

Here are the most important modules of the standard library:

  • std

    • array: Implementation of arrays in the language

    • config: Compile-time constants

    • convert: Conversions

    • file: Reading and writing files

    • io: I/O functionality

    • map: Implementation of maps in the language

    • memory: Dynamically allocating and deallocing memory

    • mutex: OS mutexes and locks

    • optional: Implementation of optionals in the language

    • path: Access to filesystem paths

    • process: Spawning processes

    • result: Implementation of results in the language

    • set: Sets

    • slice: A type combining a pointer to some data and a length

    • socket: Network sockets

    • string: Strings

    • test: Assertions for tests

    • thread: OS threads

    • time: Access to system clocks

The standard library is used like any other module:

# Importing the entire standard library
use std;

# Importing a specific submodule
use std.memory;

# Importing a specific symbol
use std.memory.alloc;

# Importing multiple symbols
use std.thread.{Thread, sleep};
use std.{memory, file.{File, FileMode}};