Defining and Organizing Modules
Modules allow you to organize code within a crate into logical, hierarchical groups. They provide a namespace for your code and allow you to control what's public and what's private.
Module Basics
A module is declared with the module keyword (not mod as in Rust). It creates a namespace that can contain functions, structs, traits, and other items:
module restaurant {
fn prepareFood() {
println!("Preparing food")
}
}
fn main() {
restaurant.prepareFood()
}
In this example, prepareFood is defined inside the restaurant module and accessed using dot notation: restaurant.prepareFood().
Nested Modules
Modules can be nested inside other modules, creating a hierarchy:
module restaurant {
module food {
module appetizers {
public fn bruschetta() {
println!("Making bruschetta")
}
}
module mains {
public fn pasta() {
println!("Making pasta")
}
}
}
module house {
fn greet() {
println!("Welcome!")
}
}
}
fn main() {
restaurant.food.appetizers.bruschetta()
restaurant.food.mains.pasta()
}
Paths use dot notation, just like accessing nested objects or properties in other languages.
Module Organization Conventions
While Oxide allows you to nest modules deeply, it's often clearer to organize them in files:
Single-File Organization
For small projects, keep everything in src/main.ox or src/lib.ox:
// src/main.ox
module restaurant {
module food {
public fn appetizer() { }
public fn mainCourse() { }
}
module house {
public fn greet() { }
}
}
fn main() {
restaurant.food.appetizer()
}
Multi-File Organization
For larger projects, split modules into separate files. The conventional approach is:
src/
├── lib.ox (declares modules, defines some items)
├── restaurant.ox (or restaurant/mod.ox)
└── restaurant/
├── food.ox
├── house.ox
└── payment.ox
In src/lib.ox:
external module restaurant
The external module keyword tells Oxide that the module is defined in an external file, not inline.
In src/restaurant.ox (or src/restaurant/mod.ox):
public module food {
public fn appetizer() {
println!("Appetizer")
}
}
public module house {
public fn greet() {
println!("Welcome")
}
}
In src/restaurant/food.ox:
public fn appetizer() {
println!("Appetizer served")
}
public fn dessert() {
println!("Dessert served")
}
Then import and use:
import restaurant.food
fn main() {
food.appetizer()
}
File Naming and Location
When you declare external module foo, Oxide looks for:
- A file named
foo.oxin the same directory, or - A directory named
foo/with amod.oxfile inside
For nested modules, you can either:
Option 1: Inline with dots
external module restaurant.food.appetizers
Option 2: Nested directories
src/
├── restaurant.ox
└── restaurant/
├── food.ox
└── food/
├── appetizers.ox
Both approaches work. Choose the one that feels most natural for your project structure.
Public and Private Items
By default, all items in a module are private:
module restaurant {
fn secret() {
println!("Secret recipe")
}
}
fn main() {
restaurant.secret() // Error: secret is private
}
Use the public keyword to make items available outside the module:
public module restaurant {
public fn greeting() {
println!("Welcome!")
}
fn secret() {
println!("Secret recipe") // Private, not accessible from outside
}
}
fn main() {
restaurant.greeting() // OK
restaurant.secret() // Error: private
}
Note: A module can be public at the declaration, but items inside it are still private by default:
public module restaurant {
// This module itself is public
fn secret() { } // But this item is private
public fn welcome() { } // This item is public
}
Re-exporting with public import
Sometimes you want to reorganize your internal module structure without breaking the public API. Use public import to re-export items:
// src/lib.ox
external module internals
public import internals.helpers.createMessage
// Now users can do:
// import myLib.createMessage
// Instead of:
// import myLib.internals.helpers.createMessage
This is useful for:
- Simplifying the public API
- Reorganizing internal code without breaking user code
- Grouping related functionality under a simple name
Privacy Rules
Oxide's privacy model is hierarchical:
- Private by default: Items are private unless marked
public - Public items only at boundaries: You can only make items public that are directly in a public module
- Public all the way down: To access a deeply nested public item, all parent modules must also be public
Example:
module restaurant { // Private module (default)
public module food { // Public submodule
public fn appetizer() { } // Public function
}
}
// In another file:
import restaurant.food.appetizer // Error! restaurant is private
// The rule: parent must be public too
To fix:
public module restaurant { // Make parent public
public module food {
public fn appetizer() { }
}
}
// Now it works!
import restaurant.food.appetizer
Best Practices
1. Use Modules to Group Related Functionality
public module httpServer {
public module handlers {
public fn handleRequest() { }
public fn handleError() { }
}
public module middleware {
public fn logRequest() { }
public fn validateAuth() { }
}
}
2. Keep Public APIs Simple
Use public import to flatten your public interface:
// Organize internally
external module internals.utils
external module internals.validators
// Expose cleanly
public import internals.utils.createConfig
public import internals.validators.validateInput
3. Use Consistent Naming
Module names should be descriptive but concise:
// Good
public module user_management { }
public module payment { }
public module notifications { }
// Avoid overly nested or redundant names
public module users.user_management.user_utils { }
Comparison with Rust
In Rust:
- Modules are declared with
mod, notmodule - External modules are declared with
mod foo;(with semicolon) - File organization is similar but uses
.rsextension - Snake_case is used for module names (Oxide follows the same convention)
Oxide syntax:
module foo { }for inline modulesexternal module foofor file-based modules- Dot notation for paths instead of
::or:: - snake_case for module names by convention
Summary
- Modules organize code into hierarchical namespaces
- Inline modules are defined with
modulekeyword - External modules are file-based and declared with
external module - Public modules and items use the
publickeyword - Privacy is hierarchical: parent modules must be public to access nested public items
- Dot notation accesses paths:
restaurant.food.appetizers - public import re-exports items to simplify the public API
Now that you understand how to organize code with modules, let's explore how to bring those items into scope with import statements.