Discover how to manage units with the Foundation framework's Measurement struct, enabling type-safe management of units.
Written by Jacob Gelman
Handling units of measurement accurately is crucial in software development,
where errors in unit handling can lead to serious logical errors. Swift’s
Foundation framework provides a
powerful solution
with its generic Measurement
struct—allowing for the management of numeric
quantities with associated units in a type-safe way. This article delves into
how using the Measurement struct can streamline working with different units of
measure, leading to code that is more concise and free of logical errors.
Let's begin with a simple example of calculating perimeter from width and height measurements. At first width and height are both specified in meters, and then height is changed to be expressed in feet instead of meters:
Regardless of whether height is expressed in feet or meters, we effectively get the same result. Since both measurements represent lengths, arithmetic operations can be correctly performed between them regardless of the exact unit of length used to express each one; arithmetic operations are performed on a measurement's underlying value only after being automatically converted to the base unit (meters in the case of length).
Measurement
is a generic struct whose definition looks like this:
The generic, UnitType
, keeps track of what type of quantity (e.g. length,
mass, etc.) a measurement represents and bakes that information directly into
its type. Consequently, two measurements representing different types of
quantities are not the same type, and thus trying to perform arithmetic
operations between incompatible measurements will produce a compile time error:
This error is raised because the + operator is not overloaded for operands of
type Measurement<UnitLength>
and Measurement<UnitInformationStorage>
. Since
combining these units logically does not make sense, this error effectively
highlights the presence of a logical error in the code. However, there are
situations where it makes sense to perform arithmetic between two measurements
with different unit types—such as in the case of derived units. Consider
calculating speed, a unit rate, as the quotient of distance and time:
Though this isn't supported out of the box, it is trivial to define a custom
extension leveraging the type system and operator overloading to make this work.
Specifically, we can overload the binary /
operator on
Measurement<UnitSpeed>
to accept a length measurement as its left operand and
a duration measurement as its right operand. In the implementation, we convert
the two measurements to meters and seconds respectively, take their quotient,
and use it to construct a speed measurement expressed in meters per second:
With the extension in place, dividing distance by time just works:
Though this is a simple example, imagine an application performing long,
multi-step calculations involving various measurements and units. This type of
application would particularly benefit from using the Foundation framework's
measurement APIs to prevent logical errors that arise from performing arithmetic
operations between incompatible units. By using the Measurement
struct and
defining custom extensions as needed to enable inter-unit operations, developers
can create code that is not only more concise, since unit conversions are
performed automatically, but also statically guaranteed to be correct in terms
of unit agreement.