Hello all,
Figured this would be the best place to ask for help with some slightly esoteric Rust programming.
A little while ago, I attempted to create a generic small-matrix struct with the typical linear algebra operations, multiplications, etc. I also didn't want it to contain just primitive types, so I followed these instructions and used higher ranked trait bounds to make it work with references to both primitive types and any other type implementing Add
. To this end, I created the following structs and functions:
#[derive(Debug,Clone,PartialEq,Eq)]
pub struct Mat2x2<T>
{
m: [T; 4],
}
impl<'a, 'b, T> ops::Mul<&'b Mat2x2<T>> for &'a Mat2x2<T>
where T: Clone + ops::Add<Output = T>,
for<'c> &'c T: Clone + ops::Mul<Output = T>
{
type Output = Mat2x2<T>;
fn mul(self, rhs: &'b Mat2x2<T>) -> Self::Output
{
let m = &self.m;
let n = &rhs.m;
Mat2x2 { m: [
&m[0]*&n[0] + &m[1]*&n[2], &m[0]*&n[1] + &m[1]*&n[3],
&m[2]*&n[0] + &m[3]*&n[2], &m[2]*&n[1] + &m[3]*&n[3]
]}
}
}
fn main()
{
let a = Mat2x2 { m: [1, 0, 0, 1] };
let b = Mat2x2 { m: [1, 0, 0, 1] };
let c = &a * &b;
assert_eq!(a, c);
}
And this works fine on its own, but when I introduce some other function that also uses higher ranked trait bounds and type-inference, things get a bit weird:
#![recursion_limit="10"]
use std::ops;
#[derive(Debug,Clone,PartialEq,Eq)]
pub struct Mat2x2<T>
{
m: [T; 4],
}
fn testing<T>() -> T
where T: Clone + From<u64>,
for<'a> &'a T: ops::Mul<Output = T>
{
let a: T = From::from(0);
let b: T = From::from(1);
let d: T = &a * &b;
d
}
impl<'a, 'b, T> ops::Mul<&'b Mat2x2<T>> for &'a Mat2x2<T>
where T: Clone + ops::Add<Output = T>,
for<'c> &'c T: Clone + ops::Mul<Output = T>
{
type Output = Mat2x2<T>;
fn mul(self, rhs: &'b Mat2x2<T>) -> Self::Output
{
let m = &self.m;
let n = &rhs.m;
Mat2x2 { m: [
&m[0]*&n[0] + &m[1]*&n[2], &m[0]*&n[1] + &m[1]*&n[3],
&m[2]*&n[0] + &m[3]*&n[2], &m[2]*&n[1] + &m[3]*&n[3]
]}
}
}
fn main()
{
let a = Mat2x2 { m: [1, 0, 0, 1] };
let b = Mat2x2 { m: [1, 0, 0, 1] };
let c = &a * &b;
assert_eq!(a, c);
let res: u64 = testing::<_>();
let ans: u64 = 55;
assert_eq!(res, ans);
}
This will refuse to compile with the error:
error[E0275]: overflow evaluating the requirement `<_ as std::ops::Add>::Output`
--> src/lib.rs:44:20
|
44 | let res: u64 = testing::<_>();
| ^^^^^^^^^^^^
|
= help: consider adding a `#![recursion_limit="10"]` attribute to your crate
= note: required because of the requirements on the impl of `std::ops::Mul` for `&'c Mat2x2<_>`
= note: required because of the requirements on the impl of `std::ops::Mul` for `&'c Mat2x2<Mat2x2<_>>`
= note: required because of the requirements on the impl of `std::ops::Mul` for `&'c Mat2x2<Mat2x2<Mat2x2<_>>>`
= note: required because of the requirements on the impl of `std::ops::Mul` for `&'c Mat2x2<Mat2x2<Mat2x2<Mat2x2<_>>>>`
= note: required because of the requirements on the impl of `for<'a> std::ops::Mul` for `&'a Mat2x2<Mat2x2<Mat2x2<Mat2x2<Mat2x2<_>>>>>`
= note: required by `testing`
(I explicitly set the recursion depth rather low to reduce the amount of output).
If I understand this correctly, Rust is unable to deduce the type T
for testing
, and is for some reason trying all possible nestings of Mat2x2
to figure it out and the process of doing that runs afoul of the generic recursion depth limit.
As far as I can tell, this happens because Mat2x2
implements Mul
and requires T
to also implement it, enabling this kind of recursion. So what I'm wondering is this: Is this working as intended? And if so, is there any way to work around it?
It is possible to explicitly set the type for testing
, but that essentially means that type inference gets disabled when using this kind of structures, which is a bit of a shame.