Skip to content

Modules

Each Ard file is a module that can be either a runnable program or used by other modules. Imports are declared at the top of files using the use keyword.

use ard/io
use my_project/utils as helpers
fn main() {
io::print("Hello from main module")
helpers::calculate(42)
}

A module with a main function is a program and can be run with ard run [path].

The basic import syntax uses absolute paths from the project root:

use path/to/module
use path/to/module as alias

By default, the imported module is available with the last segment of the path as the name. Use as to provide a custom name.

The Ard standard library consists of modules under the ard/* path:

use ard/io // Input/output functions
use ard/json // JSON parsing and serialization
use ard/http // HTTP client functionality
use ard/async // Asynchronous programming
use ard/maybe // Maybe type utilities

Project import paths are absolute from the project root, determined by the presence of an ard.toml file. Dependency import paths are absolute from the package that declares the dependency; see the Dependencies guide for lockfile and cache workflow details.

my_calculator/
├── ard.toml # Project configuration
├── main.ard # Entry point
├── utils.ard # Utility functions
└── math/
└── operations.ard # Math operations

The ard.toml file defines the project:

name = "my_calculator"
ard = ">= 0.13.0"

If no ard.toml file is present, the project name defaults to the root directory name.

With the above project structure:

// In main.ard
use my_calculator/utils
use my_calculator/math/operations
fn main() {
let result = operations::add(5, 3)
utils::log("Calculation complete")
}
// In utils.ard
use ard/io
fn log(message: Str) {
io::print("[LOG] {message}")
}
fn format_number(num: Int) Str {
"Number: {num.to_str()}"
}
// In math/operations.ard
fn add(a: Int, b: Int) Int {
a + b
}
fn multiply(a: Int, b: Int) Int {
a * b
}
fn divide(a: Int, b: Int) Int!Str {
match b == 0 {
true => Result::err("Division by zero")
false => Result::ok(a / b)
}
}

By default, functions, structs, enums, and traits are public and accessible from other modules:

// In utils.ard
fn helper_function() Str { // Public
"This can be called from other modules"
}
struct Config { // Public
name: Str
}

Use the private keyword to make these declarations module-local:

// In utils.ard
fn public_function() Str {
private_helper() // OK: same module
}
private fn private_helper() Str { // Private
"This cannot be called from other modules"
}
private struct InternalConfig { // Private
secret: Str
}

Variables have different privacy rules based on mutability:

  • Immutable variables (let) are public by default
  • Mutable variables (mut) are private by default
// In constants.ard
let API_URL = "https://api.example.com" // Public (immutable)
let MAX_RETRIES = 3 // Public (immutable)
mut internal_counter = 0 // Private (mutable)
mut debug_mode = false // Private (mutable)

Usage from another module:

// In main.ard
use my_project/constants
fn main() {
// Access public immutable variables
let url = constants::API_URL // ✅ Works
let max = constants::MAX_RETRIES // ✅ Works
// Cannot access private mutable variables
// let counter = constants::internal_counter // ❌ Error
}

Struct fields are always public if the struct is public.

// In user.ard
struct User {
id: Int // Public
username: Str // Public
email: Str // Public
}
// Methods can be private
impl User {
fn get_display_name() Str { // Public
format_name(self.username) // Calls private method
}
private fn format_name(name: Str) Str { // Private
"User: {name}"
}
}