Using F# Units of Measure to Prevent Unit‑Mismatch Bugs
Learn how F#'s Units of Measure let the compiler catch accidental mixes of meters, feet, and other units before your code runs.
03 Aug 2026, 13:58 UTC

The problem: silent unit mix‑ups
When you write calculations that involve physical quantities, it is easy to accidentally add a length in meters to a length in feet. The types involved are both float, so the compiler sees no issue. The mistake only surfaces at runtime, often producing hard‑to‑trace errors in scientific simulations, games, or engineering tools.
Thesis: Units of Measure give the compiler unit awareness
F# provides a lightweight type‑level feature called Units of Measure. By annotating a numeric type with a unit symbol ([] type m for meters, for example), the compiler treats float and float as distinct types. Any attempt to add, subtract, or compare values with incompatible units results in a compile‑time error, catching the bug before the program runs.
Worked example: converting meters to feet
First, define the units and a conversion function:
// Define unit symbols
[] type m // meters
[] type ft // feet
// Convert meters to feet
let toFeet (x:float) = x * 3.28084
// Example usage
let heightInMeters = 5.0
let heightInFeet = toFeet heightInMeters
printfn "Height: %f ft" (float heightInFeet)
If you try to add a raw meter value to a foot value without conversion, the compiler rejects it:
let badSum = heightInMeters + 2.0 // <-- error: unit mismatch
The error message (from dotnet fsi or Visual Studio) will look similar to:
error FS0001: The unit of measure 'ft' does not match the unit of measure 'm'.
Changing the erroneous line to use the conversion function makes the code build:
let goodSum = heightInMeters + (2.0 |> (fun f -> f / 3.28084)) // convert ft to m
printfn "Sum in meters: %f m" (float goodSum)
Trade‑offs and limitations
- Verbosity: Every numeric value that carries a unit needs the unit annotation, which can feel noisy in heavy‑math code.
- Learning curve: Teams unfamiliar with the syntax must invest time to read and write measure‑aware signatures.
- Generic code: When writing functions that should work with any unit, you need to expose the unit as a generic parameter (
float<'u>) or useLanguagePrimitives.GenericZeropatterns. - Runtime erasure: Units are removed during compilation; they have no runtime cost, but you cannot discover them via reflection.
These drawbacks are usually outweighed by the safety gain, especially in domains where unit errors are costly.
Getting started and verification
- Install the .NET SDK 6.0 or later (any recent version works).
- Create a small console project:
dotnet new console -lang F# -n UnitDemo - Navigate into the folder:
cd UnitDemo - Replace the generated
Program.fswith the example code above. - Build the project:
dotnet build. You should see no errors. - Introduce the mistaken line (
let badSum = heightInMeters + 2.0) and rebuild; the build will fail with a unit‑mismatch error. - Fix the line as shown in the “good sum” example and verify the build succeeds again.
- Run the program:
dotnet runand confirm it prints the expected height in feet.
If you remove the [] definitions entirely, the same code compiles but will produce incorrect results at runtime, demonstrating the value of the annotations.
Actionable closing
Start by adding Units of Measure to the most error‑prone calculations in your codebase—typically those that involve physical constants, sensor readings, or geometry. Even a few annotated types can prevent costly bugs, and the compiler will immediately tell you when you forget a conversion. Over time, you’ll develop a habit of thinking in units, and your F# code will become both safer and more self‑documenting.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.