Home > Software engineering >  Compile-time type inference from a limited number of permutations of types
Compile-time type inference from a limited number of permutations of types

Time:07-20

I am building a Rust library with the goal of inferring at compile-time if the operation between two different types of matrices is going to be either a new constant matrix or a variable matrix.

The combination rules are as follows:

Matrix<Variable> with Matrix<Variable> = Matrix<Variable>
Matrix<Variable> with Matrix<Constant> = Matrix<Variable>
Matrix<Constant> with Matrix<Variable> = Matrix<Variable>
Matrix<Constant> with Matrix<Constant> = Matrix<Constant>

As you can see the resulting matrix is only Constant if both matrices in the operation are Constant, Variable otherwise.

It seems logical to me to think that the Rust compiler should be smart enough to be able to figure out at compile time the types of the resulting matrices, but I haven't find a way to express these combination rules in any successful way.

I guess I should have a type that looks like this:

struct Matrix<T>(T);

And maybe implement this?

impl<T> Matrix<T> {
    fn do_operation<O>(&self, other: &Matrix<O>) -> Matrix<???> {
        // TODO: do something with T and O to get the correct type.
        // How to let the compiler know that both T and O can only be either
        // `Variable` or `Constant` and how to return one of both types
        // according to the above rules?
    }
}

Maybe I am thinking about this problem the wrong way, or it could be it is just impossible to do. The only thing I know is that the result should look similar to this:

let x = Matrix(Constant);
let y = Matrix(Variable);
let z = x.do_operation(&y); // Matrix<Variable>
let w = z.do_operation(&y); // Matrix<Variable>
let c = x.do_operation(&x); // Matrix<Constant>

Is there any way to express this behavior so that the compiler understands the rules? Of course I could use an enum, but then I loose the compile-time checking and the possibility to implement some methods only for Matrix<Variable> that are not available for Matrix<Constant>.

For example this allows me to make sure the user of my library doesn't for example try to calculate the gradients on a constant matrix and panic at run time. It would be a very nice feature!

Update

Following Chayim's suggestion I've created a playground and added a trait to implement it for the possible combinations. It works, but then the trait bound clauses quickly grow out of control, if for example I try to write a function that performs several operations, to compile it needs to be written like this, and I am only performing two!

fn use_two_matrices<X: Copy   Default, Y: Copy   Default>(x: Matrix<X>, y: Matrix<Y>) -> Matrix<<(<(X, Y) as MatrixPermutation>::Result, Constant) as MatrixPermutation>::Result>
where
    (X, Y): MatrixPermutation,
    (<(X, Y) as MatrixPermutation>::Result, Constant): MatrixPermutation
{
    let z = x.do_operation(&y);
    let w = Matrix(Constant::default());
    z.do_operation(&w)
}

That will look very ugly for people that want to implement complex operations using the types my library provides. Is there any way to make it more friendly while keeping the compile-time checks?

CodePudding user response:

You need some trait to express this relationship. There are multiple ways to implement it, but I would implement it on two element tuples like so:

pub trait MatrixPermutation {
    type Result;
}

impl MatrixPermutation for (Variable, Variable) {
    type Result = Variable;
}
impl MatrixPermutation for (Variable, Constant) {
    type Result = Variable;
}
impl MatrixPermutation for (Constant, Variable) {
    type Result = Variable;
}
impl MatrixPermutation for (Constant, Constant) {
    type Result = Constant;
}

This way, you can define do_operation() like:

fn do_operation<O>(&self, other: &Matrix<O>) -> Matrix<<(T, O) as MatrixPermutation>::Result>
where
    (T, O): MatrixPermutation,
{
    // ...
}

If you need access to specialized methods in the body of do_operation, you can create them on MatrixPermutation too.

  • Related