Import Statements and Bringing Names Into Scope
Once you've organized your code into modules, you need a way to bring those items into scope so you can use them without always writing the full path. This is where import statements come in.
Basic Import
The simplest form of an import brings an item directly into scope:
import restaurant.food.appetizers
fn main() {
appetizers.bruschetta() // Can use appetizers without the full path
}
Without the import, you'd need to write:
fn main() {
restaurant.food.appetizers.bruschetta() // Full path
}
Importing Specific Items
Import a single function or struct directly:
import restaurant.food.appetizers.bruschetta
fn main() {
bruschetta() // No prefix needed
}
This brings just bruschetta into scope.
Importing Multiple Items
Import multiple items from the same module using braces:
import restaurant.food.appetizers.{bruschetta, calamari, soup}
fn main() {
bruschetta()
calamari()
soup()
}
Importing an Entire Module
Import a module and access its contents with dot notation:
import restaurant.food
fn main() {
food.appetizers.bruschetta()
food.mains.pasta()
food.desserts.tiramisu()
}
Nested Imports
You can nest imports to bring multiple modules from a parent:
import restaurant.{food, payment, house}
fn main() {
food.appetizer() // Via imported module
payment.process() // Via imported module
house.greet() // Via imported module
}
This is equivalent to:
import restaurant.food
import restaurant.payment
import restaurant.house
Wildcard Imports
Import everything from a module using *:
import restaurant.food.*
fn main() {
appetizers.bruschetta()
mains.pasta()
}
Note: While wildcard imports are convenient, they can make code harder to read because it's not clear where appetizers comes from. Use them sparingly and mainly in tests or small scopes.
Renaming Imports
If two modules export items with the same name, or if you just prefer a different name, use as to rename:
import restaurant.food.appetizers as starters
fn main() {
starters.bruschetta()
}
Multiple renames in one import:
import restaurant.{
food.appetizers as starters,
payment.creditCard as cardPayment,
}
fn main() {
starters.bruschetta()
cardPayment.process()
}
Relative Paths
Within the same file or module, you can use relative paths:
module restaurant {
module food {
public fn appetizer() { }
}
module house {
fn greet() {
// Access sibling module using dot notation
let name = "appetizer"
// Can access food.appetizer() here
food.appetizer()
}
}
}
Importing from External Crates
If your project depends on other crates, import from them using the crate name:
# In Cargo.toml
[dependencies]
serde = "1.0"
import serde.json // Import from the serde crate
fn main() {
let data = json.parse("{\"key\": \"value\"}")
}
Import Styles
There are several valid import styles. Choose the one that makes sense for your code:
Style 1: Import Functions Directly
Use when you call the function many times:
import myLib.math.fibonacci
fn main() {
println!("\(fibonacci(10))")
println!("\(fibonacci(20))")
}
Style 2: Import the Module
Use when you need multiple items from the same module:
import myLib.math
fn main() {
println!("\(math.fibonacci(10))")
println!("\(math.add(5, 3))")
}
Style 3: Import with Alias
Use when dealing with naming conflicts:
import myLib.v1.process as processV1
import myLib.v2.process as processV2
fn main() {
processV1.handle()
processV2.handle()
}
Style 4: Full Paths
Use for rare or library items:
fn main() {
let result = myLib.math.fibonacci(10)
}
Scoped Imports
Imports can be declared at any scope level, not just the top of a file:
fn processData() {
import utils.math
let result = math.fibonacci(10)
}
fn formatOutput() {
import utils.formatting
let text = formatting.indent("Text")
}
This can help keep imports close to where they're used, though top-level imports are more common.
Circular Imports
Oxide, like Rust, prevents circular dependencies at the crate level. However, within a crate, you can have modules that reference each other:
// src/lib.ox
module a {
public fn callB() {
b.doSomething()
}
}
module b {
public fn doSomething() {
println!("B does something")
}
}
This works because both modules are in the same compilation unit. The key is that you can't have circular dependencies between crates.
Re-exporting
When you import something in a public scope, it becomes available to your module's users:
// internal/utils.ox
public fn createConfig() { }
// src/lib.ox
import internal.utils.createConfig
// Users of this library can now do:
// import myLib.createConfig
To make this explicit, use public import:
public import internal.utils.createConfig
This makes it clear that createConfig is part of your public API.
Organizing Imports
A common convention is to organize imports into groups:
// Standard library imports (if any)
import io
import collections
// Internal imports
import utils.math
import utils.formatting
// Re-exports
public import math.fibonacci
public import formatting.indent
fn main() {
let fib = fibonacci(10)
}
Comparison with Rust
In Rust:
- Modules are brought into scope with
use, notimport - Paths use
::not. - Wildcard imports use
use module::*; pub usere-exports (same as Oxide)
Rust example:
#![allow(unused)] fn main() { use restaurant::food::appetizers::bruschetta; use restaurant::food::*; use restaurant::food::appetizers as starters; pub use internal::utils::createMessage; }
Oxide equivalent:
import restaurant.food.appetizers.bruschetta
import restaurant.food.*
import restaurant.food.appetizers as starters
public import internal.utils.createMessage
Summary
- Basic imports bring items into scope to avoid writing full paths
- Selective imports bring only what you need using braces
- Module imports let you access items with dot notation
- Wildcard imports bring everything into scope (use sparingly)
- Renaming with
ashelps avoid conflicts and improve clarity - Nested imports import multiple items from the same parent
- Relative paths work within modules without explicit imports
- Scoped imports can be declared at any level
- Re-exports with
public importmake items part of your public API
Now that you understand imports, let's explore paths and how to understand the full module hierarchy.