Units

Sec can make the unit of a numeric quantity part of its static type. This lets the compiler reject code that is numerically legal but semantically wrong.

unit m physical
unit s physical

let distance: <m> := 120
let duration: <s> := 10
let speed := distance / duration

The result carries structural unit semantics equivalent to <m/s>. No runtime unit object is required for ordinary unit checking.

Most programs do not need to declare common physical units themselves. Sec's standard library contains a substantial ready-made unit catalog that can be imported as the units module.

Practical rule: Use the standard units catalog when it already contains the quantity you need. Declare a new unit when your application or domain needs a semantic quantity that is not already represented.

Why Units Are Types

Without units, these values may all have the same numeric representation:

let distance: decimal := 10
let duration: decimal := 10
let price: decimal := 10

The compiler cannot know whether adding them is meaningful.

With units:

let distance: <m> := 10
let duration: <s> := 10
let price: <EUR> := 10

the quantities have distinct semantic meaning.

Unit checking can therefore detect mistakes such as:

  • adding metres to seconds;
  • mixing unrelated semantic quantities with identical machine representation;
  • confusing frequency with rotational frequency;
  • using one currency where another is required;
  • silently applying an unknown conversion relationship;
  • silently truncating a unit conversion to make it fit a numeric carrier.

The Standard Unit Library

Sec ships a standard unit catalog in:

sec/stdlib/units/

The catalog is ordinary Sec source code. It is not a hard-coded compiler table.

Every source file in that directory declares the same module:

module units

Because one source directory forms one Sec module, all of those files contribute declarations to one shared units module.

Design idea: The compiler knows how units work. The standard library supplies a rich catalog of which units exist. This keeps the language general without forcing every application to redefine metre, second, Hertz, Newton, Volt, foot, mile and similar units.

Importing the Unit Catalog

The standard catalog is imported through the normal standard-library import path:

import "units"

That imports the complete units module, not one physical file such as base.sec.

Internally the catalog is split across several files for maintainability, but the file boundaries do not create separate source modules.

The imported unit declarations follow the normal Sec module and name-resolution rules. For example, a standard-library unit can be referred to through the imported module:

import "units"

let distance: <units.m> := 125
let duration: <units.s> := 10
let speed := distance / duration

A unit expression may therefore combine catalog units just like locally declared units:

let throughput: decimal<units.KiB/units.s> := 512
let acceleration: decimal<units.m/units.s^2> := 9.81

Importing units does not create a runtime unit registry. Unit identity and metadata are primarily compile-time semantic information.

What the Standard Catalog Contains

The standard module is intentionally broader than the seven SI base units.

Its source is currently organized into these catalog files:

File Examples of what it covers
base.sec SI base units and common scaled base units
core_derived.sec Core derived SI quantities such as Hertz, Newton and Pascal
mechanics.sec Velocity, acceleration, pressure, density, viscosity and related mechanics units
power_energy.sec Joule, Watt, Watt-hour and related energy/power units
electricity.sec Electrical quantities such as Coulomb, Volt and other electrical SI-derived units
scaled_derived.sec Scaled forms such as kW, kWh, mV, kV, kΩ, µF and similar units
time_frequency.sec Minute, hour, day, week, scaled frequency and rotational/time-related quantities
light.sec Photometric quantities such as luminance and illuminance units
science.sec Scientific derived quantities such as sievert, katal, concentrations and thermal quantities
imperial_us.sec Imperial, US customary and nautical units such as inch, foot and yard

The catalog includes both base units and convenient named derived units. For example, base.sec contains the seven SI base units:

m
kg
s
A
K
mol
cd

and common scaled forms such as:

nm
um
mm
cm
dm
km

g
mg
t

ms
us
ns
ps
fs

mA
uA
nA
pA

Other files add named derived quantities such as:

Hz
N
Pa
J
W
Wh
kWh
V
kV
kph
mps2
bar
atm
inch
ft
yd

The purpose is not to require programs to use named catalog units for every derived expression. Structural units remain available whenever they are clearer.

Why Units Live in the Standard Library

Sec separates the language mechanism from the catalog.

The compiler owns concepts such as:

  • unit identity;
  • unit-bearing numeric types;
  • structural unit algebra;
  • dimensions;
  • Kind compatibility;
  • scale and transform checking;
  • point/difference semantics;
  • conversion validation;
  • unit diagnostics.

The standard library owns declarations such as:

unit m physical
unit kg physical
unit s physical
unit N physical
unit V physical
unit ft physical

This separation has several advantages:

  • the compiler does not need a hard-coded list of every real-world unit;
  • the standard catalog can grow without extending language syntax;
  • domain libraries can define their own equally strong unit catalogs;
  • projects can use structural units even when no named catalog unit exists;
  • unit metadata stays inspectable as ordinary Sec declarations;
  • unused unit declarations do not require runtime unit objects.

In practice, import "units" gives a project a broad vocabulary immediately, while the language remains open to application-specific quantities.

Declaring a Unit

The declaration model is:

unit Name [NumericType] Category

The normal form omits the numeric type:

unit m physical
unit EUR currency
unit percent ratio
unit Packet other

Unless there is a specific reason to choose another default carrier, this is the form users should prefer.

Why the short form is normal

A unit is semantic identity, not a storage type. Writing a numeric type in every declaration would make the declaration look more representation-bound than it is.

When the numeric type is omitted, the unit's default numeric representation is decimal.

That default controls shorthand only. The same unit may still be used with another compatible numeric carrier explicitly.

Optional Numeric Type

Specify a numeric type only when the unit should have a different preferred/default carrier.

unit Item uint other
unit PacketCount uint64 other
unit KiB uint information

Then:

let items: <Item> := 5

uses the declared uint default.

The same unit may still be used with another explicit carrier:

let exactItems: decimal<Item> := 5
Rule: The optional numeric type chooses the default carrier. It does not become part of the unit's semantic identity and does not prevent explicit use of another compatible carrier.

Using Units in Types

The full form is:

NumericType<Unit>

Examples:

let distance: decimal<m> := 10
let measuredDistance: float64<m> := 10
let ticks: uint64<ns> := 25

These quantities use the same unit mechanism with different numeric carriers.

A single named unit also has a shorthand:

<m>

For:

unit m physical

the shorthand is equivalent in carrier selection to:

decimal<m>

For a unit with an explicit default numeric type:

unit Item uint other

the shorthand:

<Item>

selects:

uint<Item>

Structural Unit Expressions

A quantity does not need a named unit declaration for every meaningful derived form.

Structural unit expressions can be written directly:

<m/s>
<m/s^2>
<kg*m/s^2>
<KiB/s>
<EUR/s>
<EUR/SEK>
<m^2>
<1>

Structural expressions support:

  • named unit factors;
  • multiplication with *;
  • division with /;
  • signed integer exponents with ^;
  • parentheses for grouping;
  • the dimensionless identity 1.

For example:

let distance: <m> := 100
let duration: <s> := 5
let speed := distance / duration

speed has structural unit semantics equivalent to <m/s>.

Compound structural shorthand uses decimal as its default carrier unless an explicit carrier is written:

<m/s>          // decimal carrier
float64<m/s>   // explicit float64 carrier

Named Units and Structural Units Are Different

Suppose a catalog declares:

unit mps physical

Then these two forms are not identical source-level identities:

<mps>
<m/s>

The first is a named unit. The second is a structural expression.

They may describe compatible mathematical quantities, but the compiler does not arbitrarily replace one with the other.

This matters because several named units can share the same dimension and even the same scale while representing different semantic Kinds.

Categories

Sec defines five unit categories:

physical
currency
information
ratio
other

Category establishes high-level semantic and diagnostic policy. It is not the same thing as dimension, Kind or System.

physical

unit m physical
unit s physical
unit Hz physical

Physical units normally participate in dimensional analysis.

currency

unit EUR currency
unit SEK currency

Distinct currencies remain semantically distinct. The compiler never invents an exchange rate.

information

unit KiB uint information

Information quantities use the compiler-known information dimension axis.

ratio

unit percent ratio

Named ratios are mathematically dimensionless but retain semantic identity.

other

unit Packet other
unit Request other

Application-defined units remain statically checked even when they need no specialized compiler category policy.

The Complete Semantic Model

A unit has more semantic structure than a name plus a conversion factor.

Sec keeps these concepts separate:

unit identity
numeric carrier
category
dimension
Kind
System
transform
scale and transform metadata

This is what lets the compiler distinguish quantities that simple dimension-vector systems often collapse together.

Concept Meaning
Unit identityThe named semantic unit
Numeric carrierThe representation used for the number
CategoryHigh-level semantic policy
DimensionMathematical dimensional structure
KindThe semantic quantity represented
SystemThe named measurement/catalog system
TransformHow numeric coordinates map to canonical quantity coordinates

Dimensions

A dimension is a normalized mapping from semantic axes to integer exponents.

impl m {
    Dimension: [length^1]
}

impl Hz {
    Dimension: [time^-1]
}

The seven SI base axes are:

length
mass
time
electric_current
thermodynamic_temperature
amount_of_substance
luminous_intensity

Additional semantic axes may be used by other categories and domain units.

Dimension algebra follows ordinary exponent rules:

[length^1] / [time^1]
    -> [length^1, time^-1]

Multiplication adds exponents; division subtracts them.

Dimensionless identity

The canonical structural dimensionless unit is:

<1>

For example:

<m/m> -> <1>

A named ratio such as percent does not lose its named identity merely because its normalized dimension is dimensionless.

Kind

Dimension answers:

What mathematical dimension does this quantity have?

Kind answers:

What semantic quantity does this value represent?

Two quantities may have the same dimension but different Kinds.

For example:

energy
    [mass^1, length^2, time^-2]

torque
    [mass^1, length^2, time^-2]

Equal dimension does not make energy and torque interchangeable.

Similarly:

Hz
    Dimension: [time^-1]
    Kind: frequency

rpm
    Dimension: [time^-1]
    Kind: rotational_frequency

A fixed mathematical relationship can exist while implicit semantic compatibility is still intentionally rejected.

Kind may be unknown. Unknown Kind is not evidence that two named units are compatible.

The compiler does not invent Kind solely from dimension.

<N*m>

does not automatically become energy or torque.

System

System records the named unit system or catalog context.

Examples include:

SI
Imperial
USCustomary
IEC
Currency
Domain
Other

System is descriptive and conversion metadata. It does not by itself determine whether two quantities are compatible.

Unit Metadata

Metadata is declared in the unit's ordinary impl block.

The standard catalog demonstrates the pattern directly:

unit m physical

impl m {
    LongName: "Meter"
    Symbol: "m"
    BaseUnit: true
    Status: active
    Dimension: [length^1]
    Scale: 1
    System: SI
}

Compiler-known metadata fields include:

LongName
Symbol
BaseUnit
Status
Dimension
Kind
Scale
System
Transform
Offset
Origin
LogBase
LogFactor
Reference

Symbol is for human-facing display and documentation. It does not define source identity.

This allows source identifiers to remain valid Sec names even when the conventional printed symbol contains characters that would not be appropriate as an identifier.

Scale

Linear units declare their relationship to the canonical quantity using Scale.

unit mm physical

impl mm {
    LongName: "Millimeter"
    Symbol: "mm"
    Dimension: [length^1]
    Scale: 1 / 1000
    System: SI
}

Conceptually:

canonical = value * Scale

Exact rational scales are particularly useful because they let the compiler reason about conversions without introducing unnecessary binary floating-point approximation.

The stdlib makes extensive use of exact rational scales, including Imperial conversions such as inch and foot.

Transforms

Scale-only units are not sufficient to model every real measurement system.

Sec defines these transform modes:

linear
affine
logarithmic

Linear

canonical = value * Scale

Linear is the default transform.

Affine

canonical = value * Scale + Offset

Affine transforms model coordinate systems where zero is offset from the canonical origin.

Logarithmic

Logarithmic units use metadata such as:

Transform: logarithmic
LogBase
LogFactor
Reference

Ordinary linear arithmetic is not silently applied to logarithmic quantities.

Points and Differences

Some quantities represent positions relative to an origin rather than free vectors.

Examples include:

  • absolute temperature;
  • timestamps;
  • altitude relative to a datum.

Origin describes the semantic point space.

For compatible point P and difference D quantities:

P - P -> D
P + D -> P
P - D -> P
D + D -> D
D - D -> D

This is invalid:

P + P

Affine offsets cancel when two compatible points are subtracted.

Ratios

Ratio units are dimensionless but remain semantically named.

unit percent ratio

impl percent {
    LongName: "Percent"
    Symbol: "%"
    Dimension: []
    Kind: ratio
    Scale: 1 / 100
    System: Other
}

A ratio may behave dimensionally like a scalar in an appropriate operation without losing its identity prematurely.

Currency

Currencies are nominal units:

unit EUR currency
unit SEK currency

A SEK quantity is not implicitly converted to EUR. The compiler does not query or invent a market exchange rate.

Derived currency expressions remain valid:

<EUR/s>
<SEK/EUR>
<EUR/SEK>

Tooling may warn about anonymous currency multiplication or division because the expression can easily be mistaken for a compiler-known exchange relationship. The expression is still valid.

Information Units

Information quantities use the information category.

unit KiB uint information

The default dimension is based on the compiler-known information axis.

Structural information rates work naturally:

<KiB/s>

The names bit and byte cannot be unit identifiers because they already have compiler-known language meanings.

Arithmetic

Unit algebra applies after the underlying numeric operator has been validated.

Rule: Unit semantics do not make an invalid numeric operation valid.

Addition and Subtraction

Addition and subtraction require semantically compatible quantities.

The compiler may consider:

  • dimension;
  • named identity;
  • known Kind;
  • transform;
  • point/difference role;
  • origin;
  • numeric representability.

Compatible fixed-scale quantities may use a safe exact conversion when permitted.

Different known Kinds do not become implicitly compatible merely because their dimensions and scales match.

Multiplication and Division

Linear quantities normally combine into structural unit expressions.

<m> / <s>      -> <m/s>
<KiB> / <s>    -> <KiB/s>
<Packet> / <s> -> <Packet/s>

The result is not silently renamed just because a catalog contains a named unit with compatible dimensions.

This preserves semantic honesty when several named Kinds can share one dimension.

Comparison

Equality and ordering require compatible quantity semantics and a valid conversion path.

if distanceA < distanceB {
    ...
}

The compiler does not compare different currencies by inventing an exchange rate.

Point comparisons additionally require compatible origins and point semantics.

Conversions

Unit conversion and numeric conversion are separate operations.

The compiler may use a fixed unit conversion when the complete relationship is known and the destination numeric carrier can represent the result without hidden loss.

For example, the stdlib metadata establishes exact relationships between units such as:

m
mm
cm
km

s
ms
us
ns

m
ft
inch

Equal dimension is not enough when semantic Kinds differ.

Hz
rpm

may have a fixed conversion relationship while still requiring explicit conversion because frequency and rotational frequency are different Kinds.

Explicit Conversion Functions

A unit can define conversion functions inside its ordinary impl.

unit Hz physical
unit rpm physical

impl rpm {
    fn rpm(hz: <Hz>) <rpm> {
        return decimal<rpm>(hz * 60)
    }
}

A conversion function named like the target unit participates in constructor-style conversion resolution.

Explicit conversion is especially useful across semantic Kind boundaries or when application-specific policy is involved.

Ordinary assignment does not silently call user-defined conversion functions.

Runtime Conversion Factors

Some relationships cannot be known statically.

Currency conversion is the clearest example:

let eur := EUR(sek, factor)

For conversion from SEK to EUR, the factor must carry compatible unit algebra equivalent to:

<EUR/SEK>

The compiler validates the dimensional relationship:

SEK * EUR/SEK -> EUR

It does not validate or invent the actual market value of the factor.

Precision and Fallibility

Sec never silently rounds, truncates or overflows merely to make a unit conversion fit the destination numeric carrier.

A mathematically valid conversion can still be invalid as an implicit conversion when the chosen numeric representation cannot express the result exactly.

In that case, choose an explicit policy:

  • use another numeric carrier;
  • perform an explicit numeric conversion;
  • apply an explicit rounding policy;
  • use a fallible conversion function.

Merely changing a unit is not automatically fallible. A conversion uses Result and try only when the actual conversion operation can fail.

Units and Named Types

Unit identity and named-type identity are separate layers.

type Distance decimal<m>
type Altitude decimal<m>

Distance and Altitude remain different nominal types even though both use metre as their unit.

Unit compatibility never erases ordinary named-type identity.

Units and Contracts

A named unit-bearing type may use ordinary contracts where applicable.

type PositiveDistance decimal<m> range 0..

The contract belongs to PositiveDistance, not to unit m.

Converting a quantity to the target named type cannot bypass its contracts.

Units with Registers and FFI

Unit meaning is separate from hardware layout and foreign ABI representation.

A unit annotation does not by itself change:

  • register field width;
  • hardware access semantics;
  • foreign calling convention;
  • wire encoding;
  • ABI storage width.

A wrapper may attach Sec unit semantics to an otherwise raw hardware or foreign numeric quantity while keeping the physical interface unchanged.

Application and Domain Unit Catalogs

The standard units module is an example of a broader design pattern.

Applications and libraries can define their own unit modules:

module logistics

unit Pallet uint other
unit Container uint other
unit Shipment uint other

Another domain might define:

module astronomy

unit parsec physical
unit lightyear physical

These units use the same compiler semantics as standard-library units. There is no privileged "built-in unit declaration" mechanism that application code cannot use.

This makes units useful beyond physics:

  • packets per second;
  • requests per minute;
  • items per pallet;
  • frames per sample window;
  • domain-specific counters;
  • financial quantities;
  • scientific quantities;
  • hardware calibration values.

Diagnostics and Tooling

Unit diagnostics should explain the semantic problem rather than only the numeric types.

Examples include:

  • unknown unit;
  • invalid unit declaration;
  • invalid numeric default carrier;
  • incompatible dimension;
  • incompatible known Kind;
  • invalid point/difference operation;
  • incompatible origin;
  • invalid affine or logarithmic operation;
  • lossy implicit conversion;
  • missing explicit runtime conversion;
  • deprecated or obsolete catalog unit;
  • suspicious anonymous derived currency expression.

A useful diagnostic is semantic:

cannot implicitly convert frequency Hz to rotational_frequency rpm

help: use an explicit unit conversion

LSP and hover

Tooling may expose:

  • unit identity;
  • symbol and long name;
  • default numeric carrier;
  • category;
  • dimension;
  • Kind;
  • System;
  • Scale;
  • Transform;
  • Offset and Origin;
  • logarithmic metadata;
  • canonical structural form;
  • conversion exactness.

Completion for an imported units module can expose the standard catalog without requiring users to memorize every available unit name.

Formatting

Structural unit expressions remain compact:

<kg*m/s^2>

The formatter does not:

  • replace a structural expression with a named catalog unit;
  • replace a named unit with its structural expansion;
  • reorder factors merely because internal canonicalization does;
  • invent unit conversions.

Summary

  • Units make semantic quantity identity part of Sec's static type system.
  • Most programs should reuse the standard units catalog rather than redeclare common units.
  • The standard catalog lives in sec/stdlib/units/ and is ordinary Sec source code.
  • All files in that directory declare one shared module named units.
  • import "units" imports the complete standard unit module, not one physical source file.
  • The catalog includes SI base units, scaled units, derived SI units, mechanics, electricity, power, time/frequency, light, science and Imperial/US units.
  • The compiler owns unit semantics; the standard library owns the reusable catalog.
  • Unit metadata does not require a runtime unit registry.
  • The declaration model is unit Name [NumericType] Category.
  • The normal declaration form omits the numeric type, for example unit m physical.
  • When omitted, the default numeric carrier is decimal.
  • Specify a numeric type only when a different preferred/default carrier is useful.
  • The default carrier does not become part of the unit's semantic identity.
  • A unit may be used with another compatible explicit numeric carrier.
  • Unit-only shorthand uses <Unit>.
  • Structural unit expressions support multiplication, division, signed integer exponents and <1>.
  • Compound structural unit shorthand defaults to decimal.
  • Named and structural units remain distinct source-level identities.
  • Sec unit categories are physical, currency, information, ratio and other.
  • Unit identity, carrier, category, Dimension, Kind, System and Transform are separate concepts.
  • Dimension models mathematical dimensional structure.
  • Kind preserves semantic distinctions such as energy versus torque.
  • The compiler does not infer Kind solely from dimension.
  • System identifies the measurement/catalog system but does not determine compatibility by itself.
  • Unit metadata is declared in the unit's ordinary impl.
  • Linear units use Scale; affine units additionally use Offset; logarithmic units use explicit log metadata.
  • Point quantities and differences have distinct algebra.
  • Named ratios remain semantically meaningful even though dimensionless.
  • Currencies remain nominal and exchange rates are never invented by the compiler.
  • Information quantities use their own compiler-known dimension axis.
  • Addition and subtraction require compatible quantity semantics.
  • Multiplication and division normally produce structural unit results.
  • Equal dimensions do not imply compatibility when known Kinds differ.
  • Fixed unit conversions are allowed only when the relationship and numeric result are safe.
  • Sec never silently rounds, truncates or overflows to make a unit conversion fit.
  • Units may define explicit conversion functions in their impl blocks.
  • Runtime relationships such as currency exchange use explicit factors or explicit conversion code.
  • Changing units is not automatically fallible; fallible conversion implementations use ordinary Result and try.
  • Units and named types provide independent layers of semantic identity.
  • Contracts on unit-bearing named types belong to the named type.
  • Unit semantics do not by themselves change hardware layout or foreign ABI representation.
  • Projects can define their own unit modules using exactly the same language feature as the standard catalog.