I Built a Type-Safe SI Unit Library in Swift — And the…
    Neura MarketNeura Market/Stable Diffusion
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityStable DiffusionStable Diffusion
    DeepSeekDeepSeekCoPilotCoPilotMidjourneyMidjourney
    View All Directories
    OverviewPromptsBlogVideosGuidesCoursesCommunityModelsLoRAsComfyUI WorkflowsTrending
    Stable DiffusionBlogI Built a Type-Safe SI Unit Library in Swift — And the Compiler Catches Your Physics Mistakes
    Back to Blog
    I Built a Type-Safe SI Unit Library in Swift — And the Compiler Catches Your Physics Mistakes
    swift

    I Built a Type-Safe SI Unit Library in Swift — And the Compiler Catches Your Physics Mistakes

    Henrique Sasaki Yuya March 24, 2026
    0 views

    SystemeInternational uses phantom types, affine spaces, and zero-cost abstractions to make unit misuse a compile-time error. 306 tests, 8 bytes per quantity, and unit conversions at 3 ns/op in Release.


    title: "I Built a Type-Safe SI Unit Library in Swift — And the Compiler Catches Your Physics Mistakes" published: true description: "SystemeInternational uses phantom types, affine spaces, and zero-cost abstractions to make unit misuse a compile-time error. 306 tests, 8 bytes per quantity, and unit conversions at 3 ns/op in Release." tags: swift, programming, opensource, types

    You've seen this bug before:

    let speed = distance + time  // Compiles. Runs. Produces nonsense.
    

    Or worse — the Mars Climate Orbiter kind, where pound-seconds and newton-seconds silently mix, and a $327 million spacecraft burns up in the atmosphere.

    I built SystemeInternational to make that class of bug extinct in Swift — at compile time, with zero runtime cost.

    What If the Type System Knew Physics?

    SystemeInternational encodes physical dimensions, units, and even the distinction between absolute positions and intervals into Swift's type parameters. Every quantity is:

    Quantity<Scalar, Unit, Space>
    

    That's it. 8 bytes. The Unit and Space are phantom types — they exist only at compile time and vanish completely in the binary.

    import UnitesSI
    
    let distance = try Quantity<Double, Kilometer, Linear>(36)
    let time     = try Quantity<Double, Second, Linear>(3_600)
    let speed    = try distance / time  // ✅ Compiles — dimension is Length/Time
    
    let nonsense = try distance + time  // ❌ Compile error — no overload
    

    No runtime checks. No if unit == .meter. The compiler simply won't let you add meters to seconds.

    Five Things That Make This Different

    1. Hertz ≠ Becquerel (Even Though They're Both 1/s)

    The SI system has unit pairs with identical dimensions but completely different physical meanings. Most unit libraries treat them as interchangeable. SystemeInternational doesn't:

    import UnitesSI
    
    let reciprocalRate = try Quantity<Double, CanonicalUnit<QuotientDimension<Dimensionless, TimeDimension>>, Linear>(50)
    let frequency = reciprocalRate.interpreted(as: Hertz.self)
    let activity = reciprocalRate.interpreted(as: Becquerel.self)
    
    print(frequency.value)     // 50.0
    print(activity.value)      // 50.0
    

    Hertz and Becquerel are different public types even though they share the same exponent dimension. The same idea applies to angular frequency (rad/s) versus cyclic frequency (Hz), and to Gray/Sievert (absorbed dose vs. equivalent dose). The library respects BIPM's semantic distinctions.

    2. You Can't Add Two Temperatures

    Adding 20°C + 25°C is physically meaningless — you can't add two absolute positions. But subtracting them to get a temperature difference is perfectly valid.

    SystemeInternational models this with affine space algebra:

    import UnitesSI
    
    let room    = try CelsiusTemperatureValue(20)       // Affine (absolute position)
    let boiling = try CelsiusTemperatureValue(100)       // Affine
    let rise    = try boiling - room                     // ✅ Linear (interval): 80°C
    
    let shifted = try room + rise                        // ✅ Affine: 100°C
    let oops    = try room + boiling                     // ❌ Compile error
    
    ExpressionResultMeaning
    Point − PointVectorDistance between positions
    Point + VectorPointShift a position
    Point + Pointcompile errorAdding positions is meaningless

    And yes — it validates against absolute zero at runtime:

    try CelsiusTemperatureValue(-274)  
    // throws QuantityError.belowAbsoluteZero
    

    3. Exact Integer Arithmetic

    Need precise timing in embedded systems? Use integer scalars:

    import UnitesSI
    
    let duration = try Quantity<Int, Millisecond, Linear>(exactly: 2_000)
    let seconds  = try duration.convertedIfExactly(to: Second.self)
    print(seconds.exactValue)  // Optional(2)
    
    let oneMeter = try Quantity<Int, Meter, Linear>(exactly: 1)
    try oneMeter.convertedIfExactly(to: Kilometer.self)  // throws — 0.001 isn't an Int
    

    No silent truncation. No floating-point drift. It throws when the math doesn't work out exactly.

    4. Rational Scale Factors (Not Floating-Point)

    Unit scales are stored as rational numbers with decimal exponents, preserving precision that Double arithmetic would destroy:

    // 1° = π/180 radians, stored as:
    UnitScale(numerator: 3_141_592_653_589_793, denominator: 180, decimalExponent: -15)
    
    // 1 eV = 1.602176634 × 10⁻¹⁹ J (exact by 2019 SI redefinition), stored as:
    UnitScale(numerator: 1_602_176_634, denominator: 1, decimalExponent: -28)
    

    This means conversions like Degree → Radian or ElectronVolt → Joule carry the full precision of the defining constants.

    5. Thin Abstractions the Optimizer Can See Through

    Hot-path accessors and arithmetic are annotated with @inlinable, so the compiler can inline across module boundaries and apply full optimizations in Release builds:

    BenchmarkDebugReleaseSpeedup
    convert_kilometer_to_meter225 ns/op3 ns/op~75×
    semantic_lumen_operator291 ns/op83 ns/op~3.5×
    semantic_lux_operator435 ns/op155 ns/op~2.8×

    Same-unit operations like linear_add_same_unit and multiply_canonical_area measured 0 ns/op in Release — the optimizer eliminated them entirely via dead code elimination, confirming that the phantom-type abstractions add no barriers to standard compiler optimizations. Real-world code that uses the results will still pay the cost of a bare Double operation, but nothing more.

    The key takeaway: the type-safety layer is transparent to the optimizer. You get compile-time unit checking without runtime overhead beyond the underlying arithmetic.

    The Module Architecture

    SystemeInternational is split into focused, composable modules:

    UnitesSI              ← Main facade (import this)
    ├── UnitesDeBaseDuSI  ← Core: Quantity, dimensions, 7 base units
    ├── PrefixesDuSI      ← All 20 SI prefixes (Quetta → Quecto)
    └── UnitesDeriveesDuSI← Named derived units + temperature scales
    
    UtiliseesNonSI        ← 16 BIPM-accepted non-SI units
    UnitesSICompat        ← Foundation.Measurement bridge
    UtiliseesNonSICompat  ← Non-SI Foundation bridge
    

    For most use cases, import UnitesSI gives you everything. The modular design means you only link what you use.

    Quick Tour

    Prefixed Units — All 20 SI Prefixes

    import UnitesSI
    
    let distance = try Quantity<Double, Kilometer, Linear>(5.2)
    let tiny     = try Quantity<Double, Microgram, Linear>(0.3)
    let huge     = try Quantity<Double, Gigahertz, Linear>(2.4)
    
    // Mass follows SI convention: Kilogram is base, but prefixes come from Gram
    let mg = try Quantity<Double, Milligram, Linear>(500)
    print(mg.converted(to: Kilogram.self).value)  // 0.0005
    

    Derived Units with Semantic Operators

    import UnitesSI
    
    let intensity  = try Quantity<Double, Candela, Linear>(1_200)
    let solidAngle = try Quantity<Double, Steradian, Linear>(1.5)
    let luminous   = try intensity * solidAngle  // → Lumen
    
    let area = try Quantity<Double, Meter, Linear>(3) * Quantity<Double, Meter, Linear>(2)  // → m²
    let illuminance = try luminous / area  // → Lux
    

    Foundation.Measurement Interop

    import Foundation
    import UnitesSICompat
    
    // SystemeInternational → Foundation
    let road = try Quantity<Double, Kilometer, Linear>(12.3).foundationMeasurement()
    print(road.unit.symbol)  // "km"
    
    // Foundation → SystemeInternational
    let temp = try Measurement(value: 25, unit: UnitTemperature.celsius)
        .absoluteTemperature(as: DegreeCelsius.self)
    print(temp.converted(to: Kelvin.self).value)  // 298.15
    

    Non-SI Accepted Units

    import UtiliseesNonSI
    
    let water      = try Quantity<Double, Milliliter, Linear>(500)
    let rightAngle = try Quantity<Double, Degree, Linear>(90)
    let gain       = try Quantity<Double, Decibel, Linear>(20)
    
    print(water.converted(to: Liter.self).value)       // 0.5
    print(rightAngle.converted(to: Radian.self).value) // 1.5707963267948966
    

    Why "Systeme International"?

    The name comes from the French Système International d'Unités — the official name of the SI system, maintained by the Bureau International des Poids et Mesures (BIPM). The module names follow the same convention: Unités de base du SI, Préfixes du SI, Utilisées non-SI.

    Requirements & Installation

    • Swift 6.2+
    • No dependencies — pure Swift, no external packages
    // Package.swift
    dependencies: [
        .package(url: "https://github.com/moriturus/SystemeInternational.git", from: "0.1.0"),
    ]
    

    306 tests. 100% coverage target. Apache 2.0 licensed.


    If you work with physical quantities in Swift — whether it's scientific computing, IoT sensor data, robotics, game physics, or just making sure your app doesn't confuse kilometers with miles — give SystemeInternational a look.

    ⭐ Star the repo on GitHub if you think the type system should catch your physics bugs.

    {% github moriturus/SystemeInternational %}


    P.S. To our friends still measuring things in feet, inches, pounds, ounces, fluid ounces (US), fluid ounces (UK), short tons, long tons, nautical miles, statute miles, furlongs, and—my personal favorite—slugs: the year is 2026. The rest of the world moved on. Even the UK went metric (mostly). Even NASA went metric (after that incident). This library does not, and will never, include Foot, Slug, or Hogshead. You know where to find Foundation.Measurement — it's right there, waiting, with all 14 of your competing gallon definitions. 🫡

    Tags

    swiftprogrammingopensourcetypes

    Comments

    More Blog

    View all
    Context bankruptcy: The case for strategic forgetting for AI Agentsai

    Context bankruptcy: The case for strategic forgetting for AI Agents

    Most of us have seen a coding agent fail to complete a task we know it can do. We just don't...

    J
    James O'Reilly
    Parallel Compliance Engine: Drive-to-Sheets Multi-Agent Orchestrationgooglecloud

    Parallel Compliance Engine: Drive-to-Sheets Multi-Agent Orchestration

    When building Generative AI applications, developers often encounter a massive bottleneck: sequential...

    A
    Aryan Irani
    Is It Ethical to Post and Ask About Circuits on Dev.to?discuss

    Is It Ethical to Post and Ask About Circuits on Dev.to?

    I’ve been thinking about sharing some electronic circuit posts on Dev.to — small circuits, DIY...

    C
    codebunny20
    The One-Click Exporter: AI Studio Antigravity, Probed to Its Limitsagents

    The One-Click Exporter: AI Studio Antigravity, Probed to Its Limits

    What nobody tells you about exporting your multi-agent prototype to a local workspace. Every...

    L
    leslysandra
    Guarding the till while autonomous data agents do the diggingagenticarchitect

    Guarding the till while autonomous data agents do the digging

    Autonomous agents are genuinely good at answering messy business questions. Give one an LLM and a set...

    S
    Sireesha Pulipati
    Return on Attention: Why AI Code Reviews Are Wearing Us Outai

    Return on Attention: Why AI Code Reviews Are Wearing Us Out

    PR volume went up, ticket quality didn't, and the gap got filled with LLMs on both sides of the review: bots reviewing, bots replying, bots occasionally arguing with bots about priorities that only existed in a teammate's head. Our CEO named the actual problem, and it's bigger than code review.

    C
    christine

    Stay up to date

    Get the latest Stable Diffusion prompts, rules, and resources delivered to your inbox weekly.

    Neura Market LogoNeura Market

    Discover the best AI prompts, plugins, and resources for Stable Diffusion and more.

    Content Types

    • Rules
    • Prompts
    • MCPs
    • Agents
    • Guides

    Platforms

    • ChatGPT Directory
    • Claude Directory
    • Gemini Directory
    • Cursor Directory
    • Grok Directory
    • Perplexity Directory
    • DeepSeek Directory
    • CoPilot Directory
    • Stable Diffusion Directory
    • Midjourney Directory
    • All Directories

    Resources

    • Blog
    • Documentation
    • Help Center
    • Marketplace

    Legal

    • Privacy Policy
    • Terms of Service

    © 2026 Neura Market. All rights reserved.

    |

    Not affiliated with any AI platform vendors.

    Ready-made automations for this

    Workflows from the Neura Market marketplace related to this Stable Diffusion resource

    • Automate WooCommerce Purchase Data to Facebook Conversions APImake · Free · Uses make
    • Sync Airtable Records with Facebook Offline Conversionsmake · Free · Uses make
    • Automate ManyChat Subscriber Conversions to Facebook APImake · Free · Uses make
    • Sync New Pipedrive Deals to Facebook Offline Conversionsmake · Free · Uses make
    Browse all workflows