Const generics Multiple Definitions of trait function

I'm trying to do compile time shape checking for linear algebra and I'm specifying two definitions for matmul():

/// Vector vector multiplication
impl <const DIM1: DimType, const DIM2: DimType, const DIM3: DimType, const DIM4: DimType, const DIM5: DimType>Tensor<DIM1, DIM2, DIM3, DIM4, DIM5>
where Assert::<{dim_eq_1(DIM2)}>: IsTrue, Assert::<{dim_eq_1(DIM3)}>: IsTrue, Assert::<{dim_eq_1(DIM4)}>: IsTrue, Assert::<{dim_eq_1(DIM5)}>: IsTrue {
    pub fn matmul<const DIM6: DimType>(&self, rhs: &Tensor<DIM1, DIM2, DIM3, DIM4, DIM5>) -> Tensor<DIM1, DIM2, DIM3, DIM4, DIM5>
    where Assert::<{dim_eq_1(DIM6)}>: IsTrue {
        Tensor::from_tch(self.data.matmul(&rhs.data))
    }
}

/// Matrix vector multiplication
impl <const DIM1: DimType, const DIM2: DimType, const DIM3: DimType, const DIM4: DimType, const DIM5: DimType>Tensor<DIM1, DIM2, DIM3, DIM4, DIM5>
where Assert::<{dim_eq_1(DIM3)}>: IsTrue, Assert::<{dim_eq_1(DIM4)}>: IsTrue, Assert::<{dim_eq_1(DIM5)}>: IsTrue {
    pub fn matmul<const DIM6: DimType>(&self, rhs: &Tensor<DIM2, DIM3, DIM4, DIM5, DIM6>) -> Tensor<DIM1, DIM3, DIM4, DIM5, DIM6>
    where  Assert::<{dim_eq_1(DIM6)}>: IsTrue {
        Tensor::from_tch(self.data.matmul(&rhs.data))
    }
}

As you can see, the assertions on const generic parameters are different, but I'm getting an error:
duplicate definitions with name matmul

Is there a way to seperate these two implementations?

That is not currently possible. When looking for duplicate definitions, bounds on type parameters are not considered. What you're looking for is specialization, which is far from being stable.

Sorry for the late response but I've just gotten time to come back to this project. I realize specialization isn't stable, but is it possible? I've resigned to using a bunch of nightly features already, so one more won't hurt.

I'm currently doing something like this:

trait MatMul<T> {
    type Output;

    fn matmul(&self, other: &T) -> Self::Output;
}

// 1 x 2
impl <const D1: u16, const D2: u16>MatMul<Tensor<D1, D2>> for Tensor<D1, 1, 1, 1, 1> {
    default type Output = Tensor<D1, D2>;

    default fn matmul(&self, other: &Tensor<D1, D2>) -> Self::Output {
        todo!()
    }
}

// 1 x 1
impl <const D1: u16>MatMul<Tensor<D1>> for Tensor<D1> {
    type Output = Tensor<D1>;

    fn matmul(&self, other: &Tensor<D1>) -> Self::Output {
        todo!()
    }
}

// 2 x 1
impl <const D1: u16, const D2: u16>MatMul<Tensor<D1, 1, 1, 1, 1>> for Tensor<D1, D2> {
    type Output = Tensor<D1, D2>;

    fn matmul(&self, other: &Tensor<D1, D2>) -> Self::Output {
        todo!()
    }
}

and the 1x2 and 1x1 work fine, but when I add the 2x1, it conflicts with the 1x1, I guess because the argument to MatMul is the same.

How is your Tensor type defined?

#[derive(Debug)]
pub struct Tensor<const DIM1: u16, const DIM2: u16 = 1, const DIM3: u16 = 1, const DIM4: u16 = 1, const DIM5: u16 = 1> {
...
}

Sorry, I'm having trouble understanding what the matmul operation is supposed to represent w.r.t. the dimensions. Why do you need to specialize on dimension 1 at all? And what does your attempted 2×1 impl look like?

I want to specialize the matmul operation so that given two different shaped tensors, I can do different operations on them depending on their size. For certian shapes, broadcasting is needed, for others matrix-vector products are needed, and for some, simple matrix multiplication is needed.

Check out how pytorch does this here: torch.matmul — PyTorch 1.12 documentation
Of course they do it at runtime depending on the shapes, but I wanted to encode this in the different specialized impls and output a tensor at compile time with the proper shape.

Hmm... I haven't been able to get this to work, and I suspect that specialization simply isn't powerful enough to express this. If I were writing this, I'd get rid of the pseudo-variadic-generics and simply use distinct types:

#[derive(Debug)]
struct Tensor1<const DIM1: u16>;

#[derive(Debug)]
struct Tensor2<const DIM1: u16, const DIM2: u16>;

trait MatMul<T> {
    type Output;

    fn matmul(&self, other: &T) -> Self::Output;
}

impl<const D1: u16> MatMul<Tensor1<D1>> for Tensor1<D1> {
    type Output = Tensor1<D1>;

    fn matmul(&self, _other: &Tensor1<D1>) -> Self::Output {
        todo!()
    }
}

impl<const D1: u16, const D2: u16> MatMul<Tensor2<D1, D2>> for Tensor1<D1> {
    type Output = Tensor1<D2>;

    fn matmul(&self, _other: &Tensor2<D1, D2>) -> Self::Output {
        todo!()
    }
}

impl<const D1: u16, const D2: u16> MatMul<Tensor1<D2>> for Tensor2<D1, D2> {
    type Output = Tensor1<D1>;

    fn matmul(&self, _other: &Tensor1<D2>) -> Self::Output {
        todo!()
    }
}

impl<const D1: u16, const D2: u16, const D3: u16> MatMul<Tensor2<D2, D3>> for Tensor2<D1, D2> {
    type Output = Tensor2<D1, D3>;

    fn matmul(&self, _other: &Tensor2<D2, D3>) -> Self::Output {
        todo!()
    }
}

(Keep in mind that a n×1 matrix as distinguished from a vector can be a useful concept.)

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.