Crates & Modules
Use workspace dependencies (workspace-deps)
Always declare shared dependencies
in the workspace [workspace.dependencies] table
and reference them with .workspace = true
in member crates.
# In the workspace root Cargo.toml
[workspace.dependencies]
ostd = { path = "ostd", version = "0.17.0" }
bitflags = "2.6"
# In a member crate's Cargo.toml
[dependencies]
ostd.workspace = true
bitflags.workspace = true
Add module-level documentation for major components (module-docs)
A module file that serves as
an important kernel component
(e.g., subsystem entry point, major data structure, driver)
should begin with a //! comment explaining:
- What the module does
- The key types it exposes
- How it relates to neighboring modules
//! Virtual memory area (VMA) management.
//!
//! This module defines [`VmMapping`] and associated types,
//! which represent contiguous regions of a process's virtual address space.
//! VMAs are managed by the [`Vmar`] tree in the parent module.
Default to the narrowest visibility (narrow-visibility)
Start private,
then widen to pub(super), pub(crate), or pub
only when an actual external consumer requires it.
// Good — restricted to the parent module
pub(super) static I8042_CONTROLLER:
Once<SpinLock<I8042Controller, LocalIrqDisabled>> = Once::new();
pub(super) fn init() -> Result<(), I8042ControllerError> {
// ...
}
// Bad — unnecessarily wide
pub static I8042_CONTROLLER: ...
The asterinas assembler crate does not expose any pub Rust items.
Keep the kernel crate graph layered and acyclic (layered-kernel-crates)
For modularity,
the Asterinas kernel under kernel/ is decomposed into
an acyclic graph of kernel crates arranged in layers.
A kernel crate may depend on crates in the same layer or any lower layer.
The layers are, from highest to lowest:
- The assembler crate (
kernel/src/). - High-level component crates (
kernel/comps/). - The
aster-corecrate (kernel/core/). - Low-level component crates (
kernel/core/comps/). - Kernel libraries (
kernel/libs/).
A new kernel subsystem, driver, or utility type
should be added as a separate crate outside aster-core by default.
Add new code directly to aster-core only when necessary.
All these kernel crates may depend on OSTD.
When lower-layer code needs behavior implemented above it, define the interface and registration mechanism in the lower layer and let the higher component register its implementation.
Restrict subsystem visibility with a short name (short-vis-path)
To conform with the narrow-visibility guideline,
a common pattern is to expose Rust items inside a subsystem only to the subsystem itself.
But when the subsystem sits deep in the module hierarchy,
we have to write long visibility modifiers
like pub(in crate::a::very::deep::subsystem),
which is tedious for writers and unfriendly for readers.
Instead, you can use the short_vis_path macro
to create a short name for the long full path of a subsystem:
// In kernel/src/a/very/deep/subsystem/lib.rs
// Good
#![short_vis_path::add(subsystem)]
pub(in subsystem) fn struct Foo {}
// Bad: violating the narrow-visibility guideline.
pub struct Foo {}
// Bad: the visibility path is too long to read.
pub(in crate::a::very::deep::subsystem) struct Foo {}
To avoid overusing this attribute, only follow this guideline when all of the following three conditions are met:
- the submodule depth exceeds 2 levels
(i.e., the target path contains at least two
::separators)
// In kernel/src/fs/utils/systree_inode.rs
// Good: `fs` is a direct submodule of root, thus it's readable already.
pub(in crate::fs) struct Dentry {}
// Bad: no need to do this.
#![short_vis_path::add(fs)]
pub(in fs) struct Dentry {}
pub(super)andpub(self)are inapplicable
// In ostd/src/mm/page_table/mod.rs
// Good
pub(super) const fn vaddr_range() {}
// Bad
#![short_vis_path::add(mm)]
pub(in mm) const fn vaddr_range() {}
- and the restricted visibility path is used at least 2 times
Refer to #3188 for the short-vis-path design.
Qualify function calls with the parent module (qualified-fn-imports)
When importing a free function or a static/constant
from another module,
import the parent module and access the item
through it (module::function(), module::CONSTANT).
Do not import free functions or statics directly by name.
This convention is recommended by The Rust Programming Language and followed by the Rust compiler codebase. It serves two purposes:
- The call site makes it clear that an imported item is being used, not a local one.
- The module name provides context that complements the item name.
// Good — module-qualified function call
use ostd::irq;
let guard = irq::disable_local();
// Good — module-qualified static access
use ostd::mm::kspace;
let base = kspace::LINEAR_MAPPING_BASE_VADDR;
// Bad — bare function name; unclear origin at call site
use ostd::irq::disable_local;
let guard = disable_local();
// Bad — bare static name; could be mistaken for a local constant
use ostd::mm::kspace::LINEAR_MAPPING_BASE_VADDR;
let base = LINEAR_MAPPING_BASE_VADDR;
This guideline applies to free functions and statics/constants. Types, traits, and enum variants should still be imported directly by name, following the standard Rust convention.