Sometimes I have to define functions and call them generically on just few types, like this gcd that I call with u16, u32 and u64 only:
fn gcd_u16(mut u: u16, mut v: u16) -> u16 {...}
fn gcd_u32(mut u: u32, mut v: u32) -> u32 {...}
fn gcd_u64(mut u: u64, mut v: u64) -> u64 {...}
Sometimes macros are used to generate the various functions in a more DRY way:
macro_rules! gcd_generator {
($name:ident, $t:ty) => {
fn $name(mut u: $t, mut v: $t) -> $t {
if v == 0 { return u; }
loop {
u %= v;
if u == 0 { return v; }
v %= u;
if v == 0 { return u; }
}
}
};
}
gcd_generator!{gcd_u16, u16}
gcd_generator!{gcd_u32, u32}
gcd_generator!{gcd_u64, u64}
Generics allow me to write only a function (and it gets instantiated on just the types that I actually use, unlike the macro):
fn gcd<T>(mut u: T, mut v: T) -> T
where T: PartialEq + RemAssign + Copy + Clone + From<u8> {
if v == 0.into() { return u; }
loop {
u %= v;
if u == 0.into() { return v; }
v %= u;
if v == 0.into() { return u; }
}
}
The num_traits crate allows to write that function in a less hacky and nicer way:
extern crate num_traits;
use num_traits::Unsigned;
use std::ops::RemAssign;
fn gcd<T>(mut u: T, mut v: T) -> T
where T: Unsigned + RemAssign + Copy {
if v == T::zero() { return u; }
loop {
u %= v;
if u == T::zero() { return v; }
v %= u;
if v == T::zero() { return u; }
}
}
But a simpler solution could be to specify only few allowed types:
fn gcd<U>(mut u: U, mut v: U) -> U {
where U: u16 | u32 | u64 {
if v == 0 { return u; }
loop {
u %= v;
if u == 0 { return v; }
v %= u;
if v == 0 { return u; }
}
}
Something similar could work with const generics too:
fn foo<const usize N = 5 | 10>(a: &[u32; N]) -> u32 {
// ...
}
Unlike most Rust features this is quite specific.