3

I'm trying to wrap the nalgebra and/or ndarray Rust crates into an abstract LinearOperator trait and corresponding AdjointableOperator etc. traits. The problem is that I get significant “trait bound bloat”, having to specify even internal implementation details (Storage, DefaultAllocator) for the compiler not to choke up. In case of nalgebra:

impl<SM,SV,N,M,K,E> LinearOperator<Matrix<E,M,K,SV>> for Matrix<E,N,M,SM>
where SM: Storage<E,N,M>, SV: Storage<E,M,K>,
      N : Dim, M : Dim, K : Dim, E : Scalar + ClosedMul + ClosedAdd + Zero + One,
      DefaultAllocator : Allocator<E,N,K>,
      DefaultAllocator : Allocator<E,M,K>,
      DefaultAllocator : Allocator<E,N,M>,
      DefaultAllocator : Allocator<E,M,N> {
    type Codomain = OMatrix<E,N,K>;
    fn apply(&self, x : &Matrix<E,M,K,SV>) -> Self::Codomain {
        self.mul(x)
    }
}

(In case of vector to vector operations, the dimension K would be 1. Ideally apply would be just a function call, but the compiler doesn't currently allow defining it.) Some of this may not be necessary for LinearOperator alone (e.g. DefaultAllocator : Allocator<E,M,N> for the adjoint dimensions), but the compiler starts requiring them even for LinearOperator once AdjointableOperator is also defined. In case of ndarray the amount of lines for the bounds is less, but still require specifying hardly documented semi-internal traits.

type GM<S> = ArrayBase<S,Ix2>;
type GV<S> = ArrayBase<S,Ix1>;

impl<SM,SV,E> LinearOperator<GV<SV>> for GM<SM>
where SM: ArrayData<Elem=E>, SV: ArrayData<Elem=E>, E : LinalgScalar {
    type Codomain = Array1<E>;
    fn apply(&self, x : &GV<SV>) -> Array1<E> {
        self.dot(x)
    }
}

All of these trait bounds need to be repeated for the AdjointableOperator and other traits, so I tried to at least compress the requirements into a custom trait:

trait ValidMatrix<E,N,M,SM>
where N : Dim, M : Dim, E : Scalar + ClosedMul + ClosedAdd + Zero + One,
      DefaultAllocator : Allocator<E,N,M>, SM: Storage<E,N,M> {
}

impl<E,N,M,SM> ValidMatrix<E,N,M,SM> for Matrix<E,N,M,SM>
where N : Dim, M : Dim, E : Scalar + ClosedMul + ClosedAdd + Zero + One,
      DefaultAllocator : Allocator<E,N,M>, SM: Storage<E,N,M> {
}

and then only specifying Matrix<E,N,M,SM> : ValidMatrix<E,N,M,SM> in the definition of LinearOperator. The compiler immediately starts complaining about DefaultAllocator (potentially) not being implemented.

So is there any way how to avoid such “trait bound bloat”, users of crates having to basically access increasing amounts of internal implementation details of crates to implement their own traits for the exposed types? I can think of specifying mul etc. in the ValidMatrix trait, and the implementing those, but surely it would be more efficient to design the upstream crate in such a way in the first place.

  • Does this answer your question? [Is there any way to create a type alias for multiple traits?](https://stackoverflow.com/questions/26070559/is-there-any-way-to-create-a-type-alias-for-multiple-traits) – Chayim Friedman Dec 26 '21 at 09:29
  • The top answer is what I tried to do, but it doesn't appear to work. The trait aliases might work when available, although based on https://doc.rust-lang.org/unstable-book/language-features/trait-alias.html no `where` covering parameters appears to be allowed so they might not cover this case. Perhaps that's the problem with my attempt and the top answer in the link as well: the approach doesn't cause the “alias” to inherit the bounds of the parameters. – 497e0bdf29873 Dec 26 '21 at 10:30
  • Trait aliases do work with any number of traits you want, including `where` clauses. The Unstable Book is probably outdated. If trying to create a custom trait failed for you, please create a [MRE](https://stackoverflow.com/help/minimal-reproducible-example), prefereably with a playground link, and attach the full error message from the compiler. – Chayim Friedman Dec 26 '21 at 12:26

0 Answers0