I am writing a library project where I need to be generic over SIMD so that in the end all compile-time static branches can be inlined and essentially a couple constants and ZST in the end define an otherwise very errorprone and repetitive SIMD code, I have found myself extremely frustrated with how much Rust can infer type constrains and how much verbosity it requires to the extend that it feels like boilerplate. Essentially RN I am at a state were every generic type and function needs more than 10 where clauses to restrict the generics to make the compiler happy. So I tried to work around the whole ordinal first with sealed traits to imply the constrains and now type aliases that require the constraint to be semantically valid. However neither of the works.
Here is a somewhat minified code:
#![feature(portable_simd)]
use std::ops::{BitAnd, BitOr};
use std::simd::prelude::SimdPartialEq;
use std::simd::SimdElement;
use crate::SimdHelper::{MaskM, SimdM, SimdMathable};
mod SimdHelper {
use std::ops::{BitAnd, BitOr};
use std::simd::cmp::SimdPartialOrd;
use std::simd::{LaneCount, Simd, SimdElement, SupportedLaneCount};
use std::simd::prelude::SimdPartialEq;
trait Sealed{}
pub type MaskM<T, const N: usize>
where Simd<T, N>: SimdPartialEq
= <Simd<T, N> as SimdPartialEq>::Mask;
pub type SimdM<T, const N: usize>
where
SimdM<T, N>: SimdMathable<T, N>,
LaneCount<N>: SupportedLaneCount,
T: SimdElement,
MaskM<T, N>: BitAnd<Output=MaskM<T, N>> + BitOr<Output=MaskM<T, N>>
= Simd<T, N>;
pub trait SimdMathable<T, const N: usize> : SimdPartialOrd + Sealed
where
LaneCount<N>: SupportedLaneCount,
T: SimdElement,
Self::Mask: BitAnd<Output=Self::Mask> + BitOr<Output=Self::Mask>
{}
impl<T, const N: usize> SimdMathable<T, N> for SimdM<T, N>
where
Self: SimdPartialOrd,
LaneCount<N>: SupportedLaneCount,
T: SimdElement,
Self::Mask: BitAnd<Output=Self::Mask> + BitOr<Output=Self::Mask>
{}
impl<T, const N: usize> Sealed for SimdM<T, N> where
Self: SimdPartialOrd,
LaneCount<N>: SupportedLaneCount,
T: SimdElement,
<Self as SimdPartialEq>::Mask: BitAnd<Output=<Self as SimdPartialEq>::Mask> + BitOr<Output=<Self as SimdPartialEq>::Mask> /*+ BitAndAssign<Self::Mask> + BitOrAssign<Self::Mask>*/
{}
}
fn generic<T: SimdElement>(v1: T, v2: T) {
let t = SimdM::<T, 8>::splat(v1);
let t2 = SimdM::<T, 8>::splat(v2);
// does not work even though SimdM creates a constraint
let result = t.simd_eq(t2);
}
// does not work because constraint "cannot" be fulfilled
fn generic_explicit<T: SimdElement>(v1: T, v2: T)
where SimdM::<T, 8> : SimdMathable<T, 8>{
let t = SimdM::<T, 8>::splat(v1);
let t2 = SimdM::<T, 8>::splat(v2);
// does not work even though SimdM creates a constraint
let result = t.simd_eq(t2);
}
// this does work....
fn generic_awfully_verbose<T: SimdElement>(v1: T, v2: T)
where SimdM::<T, 8> : SimdMathable<T, 8>,
MaskM::<T, 8>: BitAnd<Output=MaskM::<T, 8>>,
MaskM::<T, 8>: BitOr<Output=MaskM::<T, 8>>{
let t = SimdM::<T, 8>::splat(v1);
let t2 = SimdM::<T, 8>::splat(v2);
// does not work even though SimdM creates a constraint
let result = t.simd_eq(t2);
}
fn main() {
//statically typed works of course
let t = SimdM::<u8, 8>::splat(0);
let t2 = SimdM::<u8, 8>::splat(1);
let result = t.simd_eq(t2);
println!("Hello, world! {:?}", result);
}
I must be doing something wrong, It cannot be that the type system cannot yet infer such constrains right? Is there a known workaround? It looks to me like I can maybe get it to five where clauses for each fn and type but that still unsatisfactory.