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.oxroot - Publishes code with
publicvisibility 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.oxroot with amain()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.oxand can be executed - Library crates have
lib.oxand provide reusable code - Multiple binaries go in
src/bin/ - Separating core logic in
lib.oxand interface inmain.oxis a best practice
Now that you understand packages and crates, let's explore how to organize code within a crate using modules.