Preventing Dimensional Errors in F#: Units of Measure vs. Wrapper Types
Learn how to choose between F# Units of Measure and wrapper types to prevent dimensional errors in engineering and financial applications.
14 Nov 2025, 21:56 UTC

The Cost of Dimensional Mismatches
In engineering and financial applications, a common source of critical bugs is the "primitive obsession" where different physical quantities—such as meters and feet, or kilograms and grams—are all represented as float. When a developer accidentally adds a value in meters to a value in feet, the compiler accepts the operation because both are floating-point numbers, leading to silent calculation errors.
The goal is to ensure that mass + time or meters + seconds results in a compile-time error rather than a runtime failure. In F#, you have two primary paths: using the built-in [<Measure>] attribute or creating custom wrapper types (Single-Case Discriminated Unions).
Comparing Safety and Performance
The following table compares the two most common strategies for enforcing dimensional safety in F# 5.0+ on .NET 6/7/8.
| Feature | Units of Measure ([<Measure>]) | Wrapper Types (SCDU) |
|---|---|---|
| Runtime Overhead | Zero (Erased at compile time) | Low (Boxing/Unwrapping cost) |
| Compile-time Safety | Strong (Prevents mismatched math) | Strong (Prevents mismatched types) |
| C# Interop | Seen as raw float/decimal |
Seen as a distinct class/struct |
| Reflection | Unit labels are invisible | Type names are preserved |
| Arithmetic | Native operators (+, -, *, /) | Requires custom operator overloading |
Trade-offs and Decision Logic
When to use Units of Measure
Use [<Measure>] when your primary concern is mathematical correctness and performance. Because the F# compiler erases these labels during compilation, there is no memory or CPU penalty. This is the ideal choice for high-frequency calculations or physics engines where you need the speed of a float but the safety of a type system.
When to use Wrapper Types
Use a Single-Case Discriminated Union (SCDU), such as type Kilograms = Kilograms of float, when the unit must survive the boundary of the F# project. If a C# project consumes your library, [<Measure>] provides no protection; the C# developer will see only a double. A wrapper type forces the C# consumer to explicitly handle the unit, preventing errors across language boundaries.
Implementation: Units of Measure
To implement units of measure, define the dimensions first. These act as labels for the compiler. You can then tag literals or variables using the <unit> syntax.
// Define the measures
type [<Measure>] m
type [<Measure>] s
type [<Measure>] kg
// Assign units to values
let distance = 10.0<m>
let time = 2.0<s>
let mass = 5.0<kg>
// Valid: Result is automatically calculated as m/s
let velocity = distance / time
// INVALID: This will trigger compiler error FS0001
// let error = distance + mass
Validation and Verification
To verify that your dimensional constraints are working, you can use the F# Interactive (dotnet fsi) tool or a standard build process:
- Compile-time check: Attempt to add two different measures (e.g.,
1.0<m> + 1.0<s>). The build must fail with a type mismatch error. - Runtime check: Use a debugger or
printfn "%f" velocityto confirm the value is treated as a standard float. - Interop check: If using a C# project, reference the F# assembly and inspect the
velocityproperty. It will appear as adouble, confirming that the measure was erased.
Limitations and Risks
- API Boundaries: Units of measure are not enforced when reading from a JSON API or a database. You must manually validate or cast the raw input into the measured type at the edge of your system.
- Case Sensitivity: Measure names are case-sensitive.
type [<Measure>] Kgandtype [<Measure>] kgare treated as distinct, incompatible dimensions. - Complexity in Generics: Writing generic functions that operate across different units can lead to verbose type signatures and complex compiler errors if the units are not explicitly handled.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.