Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Compile-time dimensions

A dimension identifies a physical dimension without introducing a runtime value. Declare a nominal base with its coherent-unit symbol, or a transparent derived dimension with an expression:

dimension Length("m")
dimension Time("s")
dimension Velocity = Length / Time
dimension Acceleration = Length / Pow[Time, 2]

@copy
class Sample[D: Dimension]:
    value: f64

def reading[D: Dimension](sample: Sample[D]) -> f64:
    sample.value

def main() -> Result[(), IoError]:
    speed: Sample[Velocity] = Sample[Length / Time](3.0)
    print(reading(speed))?
    Ok(())
3.0

Sample demonstrates a dimension argument. Its field is an ordinary f64; this class does not supply dimension-aware arithmetic or unit conversion. For physical values and arithmetic, use the built-in Quantity[T, D].

Declarations and identity

dimension is a keyword. Declarations belong at module scope and follow ordinary module visibility: pub dimension Length("m") is exported. Bases in different modules differ even when their names and symbols match. Import aliases preserve identity. A public derived dimension or signature cannot expose a private base dimension.

A derived declaration creates no new identity. Forward references work; cycles are rejected. One is the built-in dimensionless dimension. Coherent-unit symbols are retained as metadata; they do not select a conversion or change dimension equality.

Dimensions have no values. A dimension can be an argument to a generic parameter constrained by Dimension, but cannot be a parameter’s value type, a return value, a field, a collection element, or an ordinary type alias target. CopyType describes values and does not admit dimensions.

dimension Length("m")
def bad(length: Length) -> ():
    pass
def main() -> ():
    pass
dimension `Length` has no values

Algebra

ExpressionMeaning
OneEmpty product
Base, derived name, or dimension parameterThe named dimension
A * B, A / BProduct or quotient, left-associative; parentheses group
Pow[A, n]Integer power
Root[A, n]Exact integer root; n must be nonzero

n is an unsuffixed integer literal within i64 range, optionally negative. Decimal, hexadecimal, octal, binary, and ordinary integer separators are supported. Negative roots take the reciprocal of the corresponding positive root. Every base exponent must divide exactly by the root degree.

Pow and Root are fallback type-operator names. A local declaration or import with either name keeps its ordinary meaning; for example, importing std.fs.Root as FsRoot leaves the dimension operator Root available.

The normal form is a sorted list of nominal bases with nonzero i8 exponents. Products commute and associate when their intermediate exponents stay in range; division cancels exactly. Each subexpression must fit -128..127: a later cancellation does not rescue an overflowing intermediate result. Normal forms use base names and Pow terms; aliases do not change type identity.

dimension Length("m")
dimension Invalid = Root[Pow[Length, 3], 2]
def main() -> ():
    pass
not a whole dimension

Diagnostic alias notes

Type mismatches and quantity dimension errors show the normalized dimensions. When a matching derived alias is visible, a note connects that normal form to its source spelling:

dimension Length("m")
dimension Time("s")
dimension Velocity = Length / Time
def main() -> ():
    speed: Quantity[f64, Velocity] = Quantity[f64, Length](1.0)
note: dimension alias `Velocity` = `Length * Pow[Time, -1]`

The first matching alias in declaration/import order is used. A namespace import considers exported aliases in their declaration order. Notes use the written import binding, such as Fast for from units import Velocity as Fast or u.Velocity for import units as u. Private or unimported aliases from other modules are excluded, as are names shadowed by generic parameters. Aliases of One or of a single base work the same way.

The scope belongs to the expression or type that failed. A generic body error can therefore explain an alias in the defining module while the diagnostic also identifies the call that required that instance. Nested types and module constants receive the same notes. Errors encountered while resolving dimension declarations can explain aliases that have already resolved.

These notes do not change equality, specialization, coherent-unit output, or normalized quantity debug formatting.

Generic dimensions

D: Dimension accepts dimensions in functions, classes, and enums. A bare dimension parameter in an argument type can be inferred structurally, as in reading(sample). Explicit arguments use reading[Velocity](sample).

In a function parameter type, a dimension argument must be concrete or a bare parameter. An expression involving a parameter, such as Sample[D / Time], is rejected even for explicit calls. Return types and bodies may use dimension expressions, evaluated after substitution for each instance. An invalid root or overflowing exponent then reports the instantiation that required it.

Dimension arguments distinguish generic instances but add no fields or runtime metadata to their values. Specializations differing only in dimensions are not currently shared. See generic specialization.