When modelling mathematical algebraic types (objects we would like to add, multiply, ...), it is quite common to use parameterized types. The classical examples are matrices: Since we don't want to add matrices of different shapes, we can make the shape part of the matrix type, so the compiler can help us avoiding mistakes (like adding a 2x2 and a 3x3 matrix). Since we might also need to create matrices of arbitrary shape at runtime, we also need an extra "dynamic matrix" type. When adding two dynamically shaped matrices, we need to check compatibility at runtime, and have to panic if their shapes mismatch. This is more or less how the Array<T,D>
type from the ndarray
crate works. D
can be a fixed dimension like Ix2
, or dynamic IxDyn
.
What can we do however, when dealing with objects whose parameters cannot be determined by their data? Consider for example the "Integers modulo M" type. Here, the values can be represented by integers between 0 and M-1, and addition is normal integer addition, followed by the "modulo M" operation. Note that M really belongs to the type: "Integers modulo 2" and "Integers modulo 3" are considered completely different types, and I would never want to add values from one with the other. So it is very tempting to implement it this way:
struct IntMod<const M: i32>(i32);
impl<const M: i32> Add for IntMod<M> {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
IntMod((self.0 + rhs.0).rem_euclid(M))
}
}
However, since M cannot be derived from the underlying data, I see no clever way to implement a dynamic version of that struct. Of course we can just move M from the type into the struct itself:
struct IntModDyn {
x: i32,
m: i32
}
impl Add for IntModDyn {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
if self.m != rhs.m {
panic!()
}
IntModDyn {
x: (self.x + rhs.x).rem_euclid(self.m),
m: self.m
}
}
}
This however seems to be a complete waste of memory, as we just doubled the space usage of the struct. Also, I see no reasonable way to generalize IntMod<M>
and IntModDyn
into a single struct like Array<T,Ix2>
and Array<T,IxDyn>
are generalized by Array<T,D>
.
Are there well known workarounds for this kind of situation?