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.ox files, the directory name is the module name

Examples

Module NameFile Location
restaurantsrc/restaurant.ox
restaurant.foodsrc/restaurant/food.ox
restaurant.food.appetizerssrc/restaurant/food/appetizers.ox
my_api.userssrc/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

  1. One module per file (usually): Makes it easy to find code
  2. Match directory structure to module structure: module a.b.c lives in a/b/c.ox
  3. Use mod.ox for module hubs: If a directory contains sub-modules
  4. Keep nesting shallow: No more than 3-4 levels deep for readability
  5. 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:

  1. 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
  1. 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 mod keyword
  • File-based modules use mod name;
  • The conventional file for a mod server is server.rs or server/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.submodule lives in module/submodule.ox
  • Keep nesting shallow: Maximum 3-4 levels for clarity
  • Use mod.ox as 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!