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.
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.
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
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 identity | The named semantic unit |
| Numeric carrier | The representation used for the number |
| Category | High-level semantic policy |
| Dimension | Mathematical dimensional structure |
| Kind | The semantic quantity represented |
| System | The named measurement/catalog system |
| Transform | How 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.
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
unitscatalog 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,ratioandother. - 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 useOffset; 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
implblocks. - Runtime relationships such as currency exchange use explicit factors or explicit conversion code.
- Changing units is not automatically fallible; fallible conversion implementations use ordinary
Resultandtry. - 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.