Overloading of addition for arrays in Rust

668 Views Asked by At

Trying to sum two arrays ([1.0, 2.0] + [3.0, 4.0]) fails in Rust as addition isn't defined for arrays. I tried to overload the operation:

use std::ops::Add;
type float = f64;
impl<const N: usize> Add for [float;N] {
    type Output = Self;
    fn add(x : [float; N], y : [float; N]) {
        let z : [float; N];
        for i in 1..=N { z[i] = x[i] + y[i]; }
        z
    }
}

However, now rustc says that this is disallowed: “only traits defined in the current crate can be implemented for arbitrary types”. Rust also doesn't allow defining custom operators, such as .+ or so for elementwise addition.

Why would I want such a thing? I'm looking at using Rust for numerical computing, having gotten fed up with the dynamic typing of Julia, and resulting wasted hours trying to get hidden memory allocations down when it fails to specialise functions. However, requiring custom types and boilerplate code to sum two arrays doesn't seem very practical. For big arrays one of course wants to use something else; there's ndarray, but based on just having tried to wrap it into a general LinearOperator trait, it seems to have its own problems due to lack of an AbstractArray trait or enum to cover both concrete arrays and views, and somewhat weird rhs-only lifetimes (although the latter might also be just me just trying to learn lifetimes). But such a thing is neither feasible nor efficient for simple small and fast “inner loop” computations that occur here and there. On the other hand, having to use ascii-literal functions for standard mathematical operations also does not help readability.

Any ideas? Any future hope of getting custom (unicode) operators or at least being able to locally overload the standard ones? (If somebody wants to complain about users potentially defining meaningless unreadable operators, I can say the same about bitwise operators. Seriously, in 2021, you put those in the language itself?!?)

1

There are 1 best solutions below

5
On

You can't implement someone else's trait for someone else's type. This is to prevent conflicting definitions. What if you and I BOTH write a crate that defines addition for vec, and someone else uses both the crates? Whose implementation should be used?

What you can do is wrap vec in your own type and define Add for that type.

struct MyVec<T> { 
  fn new(from: Vec<T>) { ...
}
impl Add for MyVec { ... }

If you want to make access to the underlying Vec transparent (like it is for Box Arc Mutex etc.) you can impl deref for your type.

Something like this will allow you to easily call all the vec methods on your new type.

use std::ops::Deref;

struct MyVec<T> {
    value: T
}
impl<T> Deref for MyVec<T> {
    type Target = Vec<T>;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}