Types and Traits
Use types to enforce invariants (rust-type-invariants)
Leverage the type system to make illegal states unrepresentable.
Define newtypes to encode domain constraints.
// Good — a `Nice` value is guaranteed to be valid
pub struct Nice(NiceValue);
type NiceValue = RangedI8<-20, 19>;
// Bad — `i8` admits invalid values for nice levels
pub type Nice = i8;
Prefer enums over bare integers and boolean flags.
// Good — access mode is constrained by the enum
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AccessMode {
O_RDONLY = 0,
O_WRONLY = 1,
O_RDWR = 2,
}
// Bad — `u8` admits invalid values
pub type AccessMode = u8;
Encode invariants in generic parameters where needed.
impl IoMem<Sensitive> {
// Good — only unsafe code can write to sensitive MMIO
pub unsafe fn write_u32(&self, offset: usize, new_val: u32) { /* .. */ }
}
impl IoMem<Insensitive> {
// Good — safe code can write to insensitive MMIO
pub fn write_u32(&self, offset: usize, new_val: u32) { /* .. */ }
}
pub enum Sensitive {}
pub enum Insensitive {}
Asterinas uses this pattern widely,
for example with newtypes such as CpuId
and AlignedUsize<const N: u16>.
Prefer enum over trait objects for closed sets (enum-over-dyn)
When the set of variants is known and closed,
an enum is often preferable to Box<dyn Trait>
for both performance and pattern-matching expressiveness.
// Good — closed set modeled as an enum
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TermStatus {
Exited(u8),
Killed(SigNum),
}
Encapsulate fields behind getters (getter-encapsulation)
Do not make fields public when a simple getter method would do. A getter preserves naming flexibility and leaves room for future invariants.
// Good — field is private, accessed via getter
pub struct Vma {
perms: VmPerms,
}
impl Vma {
pub fn perms(&self) -> VmPerms {
self.perms
}
}
// Bad — public field exposes representation
pub struct Vma {
pub perms: VmPerms,
}