Can't seem to solve deep associated type constraints

I'm currently working on a large rewrite on the crate simdeez which allows easy trait-based SIMD selection.

Each SIMD type implements a few traits that represents what operations it can perform. Everything works fine, except when I tried adding "where" constraints on operations associated with the underlying scalar.

Such as the constraint in this (stripped) example:

pub trait SimdBase: Copy
where
    Self::Scalar: Sub<Self, Output = Self>,
{
    type Scalar;
}

Which would allow the user to do 1.0 - simd_value instead of S::Vf32::set1(1.0) - simd_value (where S is the current SIMD instruction set generic).

The above example works fine, however when I add similar constraints onto other subtypes, I get errors that I can't seem to solve. The compiler recommends adding another where clause but it doesn't work at all.

Here's a minimal reproduction of my problem:

I can't add the Div constraint onto the SimdBase trait as only floating point values are able to perform division in the majority of SIMD. I've tried applying the compiler suggestion of course, but it doesn't appear to work.

Am I missing something, or is this a limitation of the compiler?

Huh, it appears that even the constraints that work correctly don't propagate around properly.

E.g. the trait that represents the current SIMD instruction set has associated types for each scalar type representation, however 1.0 - simd_value doesn't work unless I add the same "where" constraints onto those associated types as well, even though the trait itself already specifies them.

Is this ok? playground

pub trait SimdFloat: SimdBase<Scalar = Self::Float>
{
    type Float: Div<Self, Output = Self>;
}

pub trait SimdFloat32: SimdFloat<Float = f32>
{
}

Not really, Float isn't needed because SimdBase::Scalar already specifies the type. And basically everything relies on Scalar, so duplicating multiple fields like that would probably cause more issues.

The main thing I want is to mark that this trait has operator overloading. So I already have things like SimdFloat: Div<Self, Output = Self> + Div<Self::Scalar, Output = Self>, there should also somehow be Self::Scalar: Div<Self, Output = Self>

pub trait SimdInt32: SimdInt<Scalar = i32> 
+   where f32: Div<Self::SimdF32, Output=Self::SimdF32> + Mul<Self::SimdF32, Output=Self::SimdF32> {
    type SimdF32: SimdFloat32;

    fn cast_f32(self) -> Self::SimdF32;
}

playground

The code in your playground doesn't have that.

Oh yeah, your playground link appears to be exactly what I need, I'll work upwards from there, thank you!

Also, weirdly enough, on my PC's installation of rust, the error's suggestion is different to playground and it suggests things like:

` where f32: std::ops::Div<<Self as base::SimdInt32>::SimdF32>`

Which contains base:: that doesn't appear to actually exist, and when I copy-paste it across it errors. What could it mean exactly, and is this something I should open an issue about?

Did you have a mod named base?

Did you specify both the generic type parameter and the associated type, like f32: Div<Self::SimdF32, Output=Self::SimdF32>? The error only says the generic type parameter.

Ah yeah my bad, I do have a module called base, I got confused because I wouldn't be surprized if there was underlying nomenclature such as base to refer to an inherited trait, but yeah it makes sense now.

Anyway, I just noticed a bigger issue. My first playground link actually isn't the full picture, because the associated types actually reference each other. Here is a more complete playground link (implementing your solution as well):

There appears to be an issue with cyclical dependencies and it makes sense why, I'm just not sure if it's physically possible to solve this one.

Now I meet that too with your new playground link.

I don't know whether it's a trait solver limitation in Rust right now. For your first link, I made a deduction:

pub trait SimdInt32: SimdInt<Scalar = i32> 
// Rust seems to fail to know this trait bound by itself
where f32: Div<Self::SimdF32, Output=Self::SimdF32> + Mul<Self::SimdF32, Output=Self::SimdF32>
{
    // <SimdF32 as SimdBase>::Scalar: Mul<SimdF32, Output = SimdF32>
    // <SimdF32 as SimdBase>::Scalar: Div<SimdF32, Output = SimdF32>
    // <SimdF32 as SimdBase>::Scalar == f32
    // f32: Mul<SimdF32, Output = SimdF32>
    // f32: Div<SimdF32, Output = SimdF32>
    type SimdF32: SimdFloat32;

    fn cast_f32(self) -> Self::SimdF32;
}

Rust seems to fail to know the trait bound by itself.

And I made another deduction with my first answer (via adding an associated type):

// <Self as SimdBase>::Scalar: Mul<Self, Output = Self>
// <Self as SimdBase>::Scalar == i32
// i32: Mul<Self, Output = Self>
pub trait SimdInt32: SimdInt<Scalar = i32> {
    // <Simd32 as SimdBase>::Scalar: Mul<Simd32, Output = Simd32>
    // <Simd32 as SimdBase>::Scalar == <Simd32 as SimdFloat>::Float
    // <Simd32 as SimdFloat>::Float: Mul<Simd32, Output = Simd32>
    // <Simd32 as SimdFloat>::Float: Div<Simd32, Output = Simd32>
    // <Simd32 as SimdFloat>::Float == f32
    // f32: Mul<Simd32, Output = Simd32>
    // f32: Div<Simd32, Output = Simd32>
    type SimdF32: SimdFloat32;

    fn cast_f32(self) -> Self::SimdF32;
}

Rust can get it right this time.

So the solution I can figure out is to declare an additional associated type just like I did in the first reply. playground

Al yeah, I'll look into how feasible that would be to work with. Thanks though

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.