Packages and Crates

Packages and crates are closely related concepts, but they serve different purposes. Understanding the distinction is crucial for organizing larger Oxide projects.

What is a Crate?

A crate is the smallest amount of code that the Oxide compiler considers at a time. When you run oxc (the Oxide compiler), it treats the input as a single crate. Similarly, Cargo treats your project as a crate.

A crate can be in one of two forms:

  • Binary crate: A standalone executable program
  • Library crate: A collection of code meant to be used by other programs

Each crate has a root module that defines the structure of the entire crate:

  • For binary crates: The root module is typically src/main.ox
  • For library crates: The root module is typically src/lib.ox

When you compile a crate, the compiler starts at the root module and looks for code that needs to be compiled, including any code referenced through module declarations or imports.

What is a Package?

A package is one or more crates that work together. It contains a Cargo.toml file that describes how to build those crates.

A package can contain:

  • At most one library crate: If present, it's named after the package
  • Any number of binary crates: These are placed in src/bin/

Package Structure

Here's a typical package structure:

my_oxide_project/
├── Cargo.toml
├── src/
│   ├── main.ox       (binary crate root)
│   └── lib.ox        (library crate root)
└── src/bin/
    ├── tool1.ox      (another binary crate)
    └── tool2.ox      (another binary crate)

The Cargo.toml file at the package root is the manifest that describes the entire package.

Creating a Package

When you create a new package with Cargo, it automatically sets up the structure:

$ cargo new my_oxide_project
     Created binary (application) package

This creates:

my_oxide_project/
├── Cargo.toml
└── src/
    └── main.ox

The package name in Cargo.toml defaults to the directory name. This package contains only a binary crate (because of main.ox).

To create a package with a library crate, use the --lib flag:

$ cargo new --lib my_oxide_lib
     Created library package

This creates:

my_oxide_lib/
├── Cargo.toml
└── src/
    └── lib.ox

Binary and Library Crates in One Package

You can have both binary and library crates in a single package. For example:

$ cargo new my_oxide_project
$ cargo new --lib my_oxide_project  # This would overwrite, so instead:

Simply add a lib.ox file alongside your main.ox:

my_oxide_project/
├── Cargo.toml
├── src/
│   ├── lib.ox        (library crate)
│   └── main.ox       (binary crate)

Now you have both. The binary can import and use code from the library crate because they're in the same package.

Multiple Binary Crates

If you need multiple binary crates beyond the default main.ox, place them in src/bin/:

my_oxide_project/
├── Cargo.toml
├── src/
│   ├── lib.ox
│   └── main.ox       (default binary)
└── src/bin/
    ├── tool1.ox
    └── tool2.ox

Build a specific binary:

$ cargo build --bin tool1

Run a specific binary:

$ cargo run --bin tool2

Library Crates vs. Binary Crates

Library Crates

Use a library crate when you're building code meant to be used by other programs:

  • Contains reusable functionality
  • Has a lib.ox root
  • Publishes code with public visibility for others to use
  • Cannot be run directly with cargo run

Example: A math library that others can import and use in their projects.

Binary Crates

Use a binary crate for executable programs:

  • Has a main.ox root with a main() function
  • Typically uses code from library crates
  • Can be run with cargo run
  • Can be installed with cargo install

Example: A command-line tool that users can run.

Separating Concerns: bin and lib

A common pattern is to have both:

  • lib.ox: Contains the core logic and reusable functionality
  • main.ox: Contains the command-line interface or user-facing code

This separation makes testing easier and allows the library to be reused by other programs.

For example, a search tool might have:

// lib.ox - Core search logic (reusable)
public fn searchFiles(directory: &str, pattern: &str): Vec<String> {
    // Implementation
    []
}

// main.ox - CLI interface
import lib

fn main() {
    let args = getCommandLineArgs()
    let results = lib.searchFiles(args[0], args[1])
    for result in results {
        println!("\(result)")
    }
}

Other programs can import and use searchFiles from the library, while the binary provides a command-line interface.

Rust Comparison

In Rust:

  • The default root module for a binary is src/main.rs
  • The default root module for a library is src/lib.rs
  • Binary files in src/bin/ follow the same pattern
  • Package structure is identical to Oxide

The main difference is file extensions (.rs vs .ox) and Oxide's syntax for imports and visibility.

Summary

  • Crates are the unit of compilation, either binary or library
  • Packages contain crates and are managed with Cargo.toml
  • Binary crates have main.ox and can be executed
  • Library crates have lib.ox and provide reusable code
  • Multiple binaries go in src/bin/
  • Separating core logic in lib.ox and interface in main.ox is a best practice

Now that you understand packages and crates, let's explore how to organize code within a crate using modules.