Yet another SWE Blog.

# Avoiding generic types for safer code Nigel Schuster

Generic types like `int` or `string` are quick and easy to use. However, sometimes they represent a specific concept. Declaring a new type is an easy way to avoid confusion. Let's look at the following code:

``````pub fn rectangle_circumference(width: u32, height: u32) -> u32 {
2 * width + 2 * height
}
``````

This code is meant to calculate the circumference of some arbitrary rectangle. It is simple and universally usable. However, it is very easy to use incorrectly. We have no guarantee that the units are all the same. `width` could be in centimeters, while `height` is in meters. To alleviate this problem, we can declare a specific type for a unit:

``````pub struct Meter(pub u32);
pub fn rectangle_circumference(width: Meter, height: Meter) -> Meter {
Meter(2 * width.0 + 2 * height.0)
}
``````

With this, we now put the burden on the caller to ensure that both inputs are of type `Meter`, and we guarantee that the output is of type `Meter` as well. At first sight, you may think that this incurs some overhead, however, the Godbolt output is the same for both versions when using `-C opt-level=3`:

``````example::rectangle_circumference:
lea     eax, [rdi + rdi]
ret
``````

Of course, we should consider making this more generic while maintaining precise types:

``````pub trait LengthUnit {}

pub struct Rectangle<T: LengthUnit> {
width: T,
height: T,
}

impl<T: LengthUnit> Rectangle<T>
where
T: std::ops::Add<Output = T> + Copy,
{
pub fn circumference(self) -> T {
let Rectangle { width, height } = self;
width + width + height + height
}
}

#[derive(Clone, Copy)]
struct Meter(u32);
impl LengthUnit for Meter {}
type Output = Meter;
fn add(self, rhs: Self) -> Meter {
Meter(self.0 + rhs.0)
}
}
pub fn rectangle_circumference<T>(width: T, height: T) -> T
where
T: LengthUnit + Copy + std::ops::Add<Output = T>,
{
(Rectangle { width, height }).circumference()
}
``````

And even this code compiles down to the same assembly in Gobolt when calling it with `Meter(u32)` input.

### Bonus

Rust makes it particularly easy for us to deal with different units. You can design a `create` function for the rectangle to account for different types:

``````impl<T: LengthUnit> Rectangle<T> {
pub fn create<W, H>(width: W, height: H) -> Self
where
W: std::convert::Into<T>,
H: std::convert::Into<T>,
{
Self {
width: width.into(),
height: height.into(),
}
}
}
``````

This allows us to write the following program:

``````#[derive(Clone, Copy, Eq, PartialEq, Debug)]
struct Meter(u32);
...
#[derive(Clone, Copy)]
struct Centimeter(u32);
impl LengthUnit for Centimeter {}
impl std::convert::Into<Meter> for Centimeter {
fn into(self) -> Meter {
Meter(self.0 / 100)
}
}
pub fn main() {
let width = Meter(3);
let height = Centimeter(300);
let circumference: Meter = (Rectangle::create(
width,
height,
))
.circumference();
assert_eq!(circumference, Meter(12));
}
``````

Published on 2021-08-30.