Move Duration and Size to Standard Library
Status: Approved Approved: 2026-01-31 Author: Claude Created: 2026-01-31 Depends On: operator-traits-proposal.md, associated-functions-language-feature.md Supersedes: Portions of stdlib-philosophy-proposal.md (moves Duration/Size from Core to Stdlib)
Summary
Remove Duration and Size as compiler built-in types and implement them as regular Ori types in the standard library prelude, using pure language features. Literal suffixes (10s, 5mb) remain compiler-recognized but desugar to associated function calls.
Motivation
Currently, Duration and Size are deeply embedded in the compiler:
- Special token types in the lexer (
DurationLiteral,SizeLiteral) - Built-in
Type::DurationandType::Sizevariants - Hardcoded operator implementations in
ori_eval/src/operators.rs - Hardcoded method dispatch in
ori_eval/src/methods.rs - Special type checking in
ori_typeck/src/operators.rs
This creates maintenance burden and prevents the language from being self-hosting. If Duration and Size can be implemented in pure Ori, it validates that the language’s type system and abstraction mechanisms are sufficient for real-world use.
Benefits
- Reduced compiler complexity: Remove ~500 lines of special-case code
- Validation of language features: Proves operator overloading and associated functions work
- User extensibility: Users can create similar unit types (Temperature, Currency, Angle, etc.)
- Consistency: All types follow the same rules — no magic
Design
Literal Suffix Desugaring
Literal suffixes remain compiler-recognized but desugar to associated function calls:
10s // desugars to: Duration.from_seconds(s: 10)
5mb // desugars to: Size.from_megabytes(mb: 5)
100ns // desugars to: Duration.from_nanoseconds(ns: 100)
1kb // desugars to: Size.from_kilobytes(kb: 1)
The compiler:
- Recognizes suffix patterns in lexer
- Generates AST for the associated function call
- Type checks and evaluates normally
This keeps the ergonomic syntax while moving implementation to the library.
Duration Implementation
// library/std/duration.ori
/// Duration represents a span of time in nanoseconds.
#derive(Eq, Comparable, Hashable, Clone, Debug, Default, Sendable)
pub type Duration = { nanoseconds: int }
// Duration + Duration -> Duration
impl Duration: Add {
@add (self, other: Duration) -> Duration =
Duration { nanoseconds: self.nanoseconds + other.nanoseconds }
}
// Duration - Duration -> Duration
impl Duration: Sub {
@subtract (self, other: Duration) -> Duration =
Duration { nanoseconds: self.nanoseconds - other.nanoseconds }
}
// Duration * int -> Duration
impl Duration: Mul<int> {
type Output = Duration
@multiply (self, n: int) -> Duration =
Duration { nanoseconds: self.nanoseconds * n }
}
// int * Duration -> Duration (commutative)
impl int: Mul<Duration> {
type Output = Duration
@multiply (self, d: Duration) -> Duration = d * self
}
// Duration / int -> Duration
impl Duration: Div<int> {
type Output = Duration
@divide (self, n: int) -> Duration =
Duration { nanoseconds: self.nanoseconds / n }
}
// Duration / Duration -> int (ratio)
impl Duration: Div {
type Output = int
@divide (self, other: Duration) -> int =
self.nanoseconds / other.nanoseconds
}
// Duration % Duration -> Duration (remainder)
impl Duration: Rem {
@remainder (self, other: Duration) -> Duration =
Duration { nanoseconds: self.nanoseconds % other.nanoseconds }
}
// -Duration -> Duration
impl Duration: Neg {
@negate (self) -> Duration =
Duration { nanoseconds: -self.nanoseconds }
}
impl Duration {
// Factory methods (associated functions)
pub @from_nanoseconds (ns: int) -> Self = Duration { nanoseconds: ns }
pub @from_microseconds (us: int) -> Self = Duration { nanoseconds: us * 1_000 }
pub @from_milliseconds (ms: int) -> Self = Duration { nanoseconds: ms * 1_000_000 }
pub @from_seconds (s: int) -> Self = Duration { nanoseconds: s * 1_000_000_000 }
pub @from_minutes (m: int) -> Self = Duration { nanoseconds: m * 60_000_000_000 }
pub @from_hours (h: int) -> Self = Duration { nanoseconds: h * 3_600_000_000_000 }
// Extraction methods (truncate toward zero)
pub @nanoseconds (self) -> int = self.nanoseconds
pub @microseconds (self) -> int = self.nanoseconds / 1_000
pub @milliseconds (self) -> int = self.nanoseconds / 1_000_000
pub @seconds (self) -> int = self.nanoseconds / 1_000_000_000
pub @minutes (self) -> int = self.nanoseconds / 60_000_000_000
pub @hours (self) -> int = self.nanoseconds / 3_600_000_000_000
}
impl Duration: Printable {
@to_str (self) -> str = {
let ns = self.nanoseconds
if ns % 3_600_000_000_000 == 0 then `{ns / 3_600_000_000_000}h`
else if ns % 60_000_000_000 == 0 then `{ns / 60_000_000_000}m`
else if ns % 1_000_000_000 == 0 then `{ns / 1_000_000_000}s`
else if ns % 1_000_000 == 0 then `{ns / 1_000_000}ms`
else if ns % 1_000 == 0 then `{ns / 1_000}us`
else `{ns}ns`
}
}
Size Implementation
// library/std/size.ori
/// Size represents a byte count (non-negative).
#derive(Eq, Comparable, Hashable, Clone, Debug, Default, Sendable)
pub type Size = { bytes: int }
// Size + Size -> Size
impl Size: Add {
@add (self, other: Size) -> Size =
Size { bytes: self.bytes + other.bytes }
}
// Size - Size -> Size (panics if negative)
impl Size: Sub {
@subtract (self, other: Size) -> Size = {
let result = self.bytes - other.bytes
if result < 0 then panic(msg: "Size cannot be negative")
Size { bytes: result }
}
}
// Size * int -> Size (panics if negative)
impl Size: Mul<int> {
type Output = Size
@multiply (self, n: int) -> Size = {
let result = self.bytes * n
if result < 0 then panic(msg: "Size cannot be negative")
Size { bytes: result }
}
}
// int * Size -> Size (commutative)
impl int: Mul<Size> {
type Output = Size
@multiply (self, s: Size) -> Size = s * self
}
// Size / int -> Size
impl Size: Div<int> {
type Output = Size
@divide (self, n: int) -> Size = Size { bytes: self.bytes / n }
}
// Size / Size -> int (ratio)
impl Size: Div {
type Output = int
@divide (self, other: Size) -> int = self.bytes / other.bytes
}
// Size % Size -> Size (remainder)
impl Size: Rem {
@remainder (self, other: Size) -> Size =
Size { bytes: self.bytes % other.bytes }
}
// Note: Neg is NOT implemented for Size — unary negation is a compile error
impl Size {
// Factory methods (associated functions) - SI units (1000-based)
pub @from_bytes (b: int) -> Self = {
if b < 0 then panic(msg: "Size cannot be negative")
Size { bytes: b }
}
pub @from_kilobytes (kb: int) -> Self = Self.from_bytes(b: kb * 1000)
pub @from_megabytes (mb: int) -> Self = Self.from_bytes(b: mb * 1_000_000)
pub @from_gigabytes (gb: int) -> Self = Self.from_bytes(b: gb * 1_000_000_000)
pub @from_terabytes (tb: int) -> Self = Self.from_bytes(b: tb * 1_000_000_000_000)
// Extraction methods (truncate toward zero) - SI units (1000-based)
pub @bytes (self) -> int = self.bytes
pub @kilobytes (self) -> int = self.bytes / 1000
pub @megabytes (self) -> int = self.bytes / 1_000_000
pub @gigabytes (self) -> int = self.bytes / 1_000_000_000
pub @terabytes (self) -> int = self.bytes / 1_000_000_000_000
}
impl Size: Printable {
// SI units (1000-based)
@to_str (self) -> str = {
let b = self.bytes
if b % 1_000_000_000_000 == 0 then `{b / 1_000_000_000_000}tb`
else if b % 1_000_000_000 == 0 then `{b / 1_000_000_000}gb`
else if b % 1_000_000 == 0 then `{b / 1_000_000}mb`
else if b % 1000 == 0 then `{b / 1000}kb`
else `{b}b`
}
}
Language Features Required
All required features are now approved:
1. Operator Traits (APPROVED)
See operator-traits-proposal.md. Defines Add, Sub, Mul, Div, Neg, Rem traits that types implement to support operator syntax.
2. Associated Functions (APPROVED)
See associated-functions-language-feature.md. Enables Type.method() syntax for factory methods.
3. Derive for Operator Traits (NICE TO HAVE)
Allow #derive(Add, Sub, Mul, Div) for newtypes wrapping numeric types:
#derive(Add, Sub, Mul, Div)
type Celsius = { value: float }
Defer to future proposal.
Migration Plan
Phase 1: Implement Operator Traits
- Define
Add,Sub,Mul,Div,Neg,Remtraits in prelude - Implement trait dispatch for operators in type checker
- Implement trait dispatch for operators in evaluator
- Add default implementations for built-in numeric types
Phase 2: Literal Suffix Desugaring
- Modify lexer to produce generic “suffixed literal” tokens
- Modify parser to desugar to associated function calls
- Remove special Duration/Size literal handling
Phase 3: Move Duration/Size to Library
- Create
library/std/duration.oriandlibrary/std/size.ori - Implement all methods using operator traits
- Add to prelude exports
- Remove compiler built-in Duration/Size types
- Remove hardcoded operator implementations
- Remove hardcoded method dispatch
Phase 4: Cleanup
- Remove
Type::DurationandType::Sizevariants - Remove Duration/Size from
Valueenum (use regular struct values) - Remove special-case code throughout compiler
Testing
- All existing Duration/Size tests must continue to pass
- New tests for operator trait implementations
- New tests for user-defined types with operator traits
- Performance comparison (should be equivalent)
Risks
-
Performance: Trait dispatch may be slower than hardcoded operators
- Mitigation: Inline trait method calls during compilation
-
Error messages: Generic trait errors less clear than built-in type errors
- Mitigation: Special-case error messages for common operator trait failures
-
Bootstrapping: Need operator traits before Duration/Size can be implemented
- Mitigation: Implement operator traits first, keep built-ins until ready
Alternatives Considered
Keep Duration/Size as Built-ins
Continue with hardcoded implementation.
Rejected: Prevents language self-hosting, maintains technical debt.
Remove Literal Suffixes
Require explicit Duration.from_seconds(s: 10) instead of 10s.
Rejected: Too verbose, significantly reduces language ergonomics.
User-Defined Literal Suffixes
Allow users to define custom suffixes via attribute.
Deferred: More complex, enables future extensibility. Defer to future proposal.
References
- Current Duration implementation:
compiler/ori_eval/src/methods.rs - Current Size implementation:
compiler/ori_eval/src/methods.rs - Current operator handling:
compiler/ori_eval/src/operators.rs - Operator traits proposal:
proposals/approved/operator-traits-proposal.md - Associated functions proposal:
proposals/approved/associated-functions-language-feature.md - Rust’s approach:
std::opsmodule with operator traits