File Organization and Directory Hierarchy
As your project grows, you'll want to split your code into multiple files organized in a directory structure. Oxide provides flexible conventions for organizing files and modules.
Converting Inline Modules to Files
When an inline module becomes large, you can move it to a separate file.
Starting with Inline Modules
// src/lib.ox
module restaurant {
public fn greet() {
println!("Welcome!")
}
public fn serve() {
println!("Your food is ready")
}
}
Extracting to a File
Create a new file src/restaurant.ox:
// src/restaurant.ox
public fn greet() {
println!("Welcome!")
}
public fn serve() {
println!("Your food is ready")
}
Then update src/lib.ox to declare the external module:
// src/lib.ox
external module restaurant
Now your module is in a separate file, but the module structure is identical.
Module Organization Patterns
Pattern 1: Flat Structure (For Small Projects)
Use one file for all modules:
src/
└── lib.ox (contains all modules inline)
When to use: Projects with < 500 lines of code in a single logical unit.
Pattern 2: One Module Per File
Each module gets its own file:
src/
├── lib.ox (declares modules)
├── restaurant.ox (restaurant module)
├── menu.ox (menu module)
└── payment.ox (payment module)
src/lib.ox:
external module restaurant
external module menu
external module payment
When to use: Most projects. Clear one-to-one mapping between modules and files.
Pattern 3: Nested Modules in Directories
Create a directory for complex modules:
src/
├── lib.ox
├── restaurant.ox
└── restaurant/
├── mod.ox (or just named as directory marker)
├── food.ox
├── payment.ox
└── house.ox
src/lib.ox:
external module restaurant
src/restaurant.ox:
external module food
external module payment
external module house
public fn welcome() {
println!("Welcome to our restaurant!")
}
Or alternatively, use a mod.ox file:
src/
├── lib.ox
└── restaurant/
├── mod.ox (equivalent to restaurant.ox)
├── food.ox
├── payment.ox
└── house.ox
src/restaurant/mod.ox:
external module food
external module payment
external module house
public fn welcome() {
println!("Welcome!")
}
When to use: When a module has several sub-modules and related code.
Pattern 4: Deeply Nested Structure
For complex projects with many sub-modules:
src/
├── lib.ox
└── server/
├── mod.ox
├── http/
│ ├── mod.ox
│ ├── handlers.ox
│ ├── middleware.ox
│ └── routing.ox
├── websocket/
│ ├── mod.ox
│ ├── connection.ox
│ └── protocol.ox
└── common.ox
src/lib.ox:
external module server
src/server/mod.ox:
external module http
external module websocket
external module common
src/server/http/mod.ox:
external module handlers
external module middleware
external module routing
public fn startServer() {
// Implementation
}
When to use: Large projects with multiple subsystems. Keep nesting to 2-3 levels max for clarity.
File Naming Conventions
Naming Rules
- File names use snake_case to match module names
- Directory names match module names
- For
mod.oxfiles, the directory name is the module name
Examples
| Module Name | File Location |
|---|---|
restaurant | src/restaurant.ox |
restaurant.food | src/restaurant/food.ox |
restaurant.food.appetizers | src/restaurant/food/appetizers.ox |
my_api.users | src/my_api/users.ox |
Absolute vs. Relative File Declarations
Absolute Declaration
Declare the full path from the crate root:
// src/lib.ox
external module restaurant.food.appetizers
Oxide will look for: src/restaurant/food/appetizers.ox
Hierarchical Declaration
Declare modules at each level:
// src/lib.ox
external module restaurant
// src/restaurant.ox (or src/restaurant/mod.ox)
external module food
// src/restaurant/food.ox
external module appetizers
// src/restaurant/food/appetizers.ox
// Contains the actual items
public fn bruschetta() { }
Both approaches are valid. The hierarchical approach is more common because it keeps each file focused.
Practical Example: A Complete Project Structure
Let's build a restaurant management library:
Directory Structure
my_restaurant/
├── Cargo.toml
└── src/
├── lib.ox
├── restaurant.ox
└── restaurant/
├── menu.ox
├── kitchen.ox
├── payment.ox
└── users.ox
src/lib.ox
/// Restaurant management library
external module restaurant
// Re-export public API
public import restaurant.menu.Menu
public import restaurant.menu.MenuItem
public import restaurant.kitchen.startCooking
public import restaurant.payment.processPayment
src/restaurant.ox
external module menu
external module kitchen
external module payment
external module users
public fn createRestaurant(name: &str): Restaurant {
Restaurant {
name: String.from(name),
menu: menu.createMenu(),
users: [],
}
}
public struct Restaurant {
public name: String,
public menu: menu.Menu,
public users: Vec<users.Employee>,
}
src/restaurant/menu.ox
public struct Menu {
public items: Vec<MenuItem>,
}
public struct MenuItem {
public name: String,
public price: Float,
public description: String,
}
public fn createMenu(): Menu {
Menu {
items: [],
}
}
public fn addItem(menu: &mut Menu, item: MenuItem) {
menu.items.push(item)
}
src/restaurant/kitchen.ox
public fn startCooking(order: Order): Dish {
println!("Chef is cooking: \(order.item.name)")
Dish {
name: order.item.name,
preparedAt: getCurrentTime(),
}
}
public struct Dish {
public name: String,
public preparedAt: UInt64,
}
src/restaurant/payment.ox
public fn processPayment(amount: Float, method: PaymentMethod): Result[String] {
match method {
PaymentMethod.Credit -> {
println!("Processing credit card payment: $\(amount)")
Ok("Payment successful".toString())
},
PaymentMethod.Cash -> {
println!("Received cash payment: $\(amount)")
Ok("Payment successful".toString())
},
}
}
public enum PaymentMethod {
Credit,
Cash,
Check,
}
src/restaurant/users.ox
public struct Employee {
public id: String,
public name: String,
public role: Role,
}
public enum Role {
Waiter,
Chef,
Manager,
}
public fn hireEmployee(id: String, name: String, role: Role): Employee {
Employee { id, name, role }
}
Directory Conventions
Key Principles
- One module per file (usually): Makes it easy to find code
- Match directory structure to module structure:
module a.b.clives ina/b/c.ox - Use
mod.oxfor module hubs: If a directory contains sub-modules - Keep nesting shallow: No more than 3-4 levels deep for readability
- Group related functionality: Put related modules in the same directory
When NOT to Create Separate Files
- For very small modules (< 50 lines)
- For internal helper modules
- In test modules
Circular Dependencies
Oxide prevents circular dependencies between modules:
module a imports module b
module b imports module a // Error!
To fix circular dependencies:
- Extract shared code: Create a third module both depend on
// Before (circular)
module a { ... } // imports b
module b { ... } // imports a
// After (acyclic)
module shared { ... }
module a { ... } // imports shared
module b { ... } // imports shared
- Reorganize module hierarchy: Move items to appropriate levels
module parent {
module child1 { ... } // child1 can reference child2 via parent
module child2 { ... }
}
Public Item Visibility in Files
When items are in separate files, visibility rules still apply:
// src/restaurant.ox
public fn publicFunction() { } // Public
fn privateFunction() { } // Private
Users can only access publicFunction. The file organization doesn't change privacy rules.
Comparison with Rust
In Rust:
- Modules use
modkeyword - File-based modules use
mod name; - The conventional file for a
mod serverisserver.rsorserver/mod.rs - Paths use
::not. - Snake_case is used for module and file names
Rust example:
#![allow(unused)] fn main() { // src/lib.rs mod restaurant; // src/restaurant.rs pub fn greet() { } }
Oxide equivalent:
// src/lib.ox
external module restaurant
// src/restaurant.ox
public fn greet() { }
Best Practices
For Small Projects (< 1000 lines)
src/
└── lib.ox (inline modules)
For Medium Projects (1000-10000 lines)
src/
├── lib.ox
├── users.ox
├── products.ox
├── orders.ox
└── payment.ox
For Large Projects (> 10000 lines)
src/
├── lib.ox
├── auth/
│ ├── mod.ox
│ ├── login.ox
│ ├── permission.ox
│ └── encryption.ox
├── api/
│ ├── mod.ox
│ ├── handlers.ox
│ ├── middleware.ox
│ └── routing.ox
└── data/
├── mod.ox
├── models.ox
├── database.ox
└── cache.ox
Summary
- Inline modules are useful for small, related functionality
- External modules in files scale better for larger projects
- File names match module names in snake_case
- Directories organize nested modules:
module.submodulelives inmodule/submodule.ox - Keep nesting shallow: Maximum 3-4 levels for clarity
- Use
mod.oxas a hub for modules in a directory - Privacy rules apply regardless of file organization
- Extract shared code to prevent circular dependencies
- Cargo handles compilation: No need to manually include files
You now understand the full module system—from crates and packages, through module organization, imports, and file hierarchies. These tools will help you structure even the largest Oxide projects into clean, maintainable code!