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:

  1. A file named foo.ox in the same directory, or
  2. A directory named foo/ with a mod.ox file 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:

  1. Private by default: Items are private unless marked public
  2. Public items only at boundaries: You can only make items public that are directly in a public module
  3. 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

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, not module
  • External modules are declared with mod foo; (with semicolon)
  • File organization is similar but uses .rs extension
  • Snake_case is used for module names (Oxide follows the same convention)

Oxide syntax:

  • module foo { } for inline modules
  • external module foo for 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 module keyword
  • External modules are file-based and declared with external module
  • Public modules and items use the public keyword
  • 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.