Implement array type with element-wise operations

I'm currently trying to implement an Array type that supports element-wise operations, similar to ndarray. This is just a toy project to help me improve my Rust programming skills.

I would like to be able to use this type like so:

let a = arr![1, 2, 3, 4];
let b = arr![8, 7, 6, 5];

assert_eq([9, 9, 9, 9], *(a + b));  // case 1
assert_eq([2, 3, 4, 5], *(a + 1));  // case 2

Currently, I'm able to implement either case 1 or case 2, above, but not both. Here is the relevant code that I've written so far.

use core::{array, ops};

#[macro_export]
macro_rules! arr {
    ( $($x:expr),* ) => ($crate::Array::from([$($x),*]))
}

#[derive(Debug, Copy, Clone, Hash)]
pub struct Array<T, const N: usize>([T; N]);

// ... some trait impls ...

// Case 1: Implements add (+) operation on `Array<T, N>` with `Array<U, N>` where `T: ops::Add<U>`.
impl<Rhs, T: ops::Add<Rhs>, const N: usize> ops::Add<Array<Rhs, N>> for Array<T, N> {
    type Output = Array<<T as ops::Add<Rhs>>::Output, N>;
    fn add(self, rhs: Array<Rhs, N>) -> Self::Output {
        let mut rhs = rhs.0.into_iter();
        // This unwrap will never panic because the arrays are the same length.
        Array(self.0.map(|x| x + rhs.next().unwrap()))
    }
}

// Case 2: Implements add (+) operation on `Array<T, N>` with `U` where `T: ops::Add<U>`.
impl<Rhs: Copy, T: ops::Add<Rhs>, const N: usize> ops::Add<Rhs> for Array<T, N> {
    type Output = Array<<T as ops::Add<Rhs>>::Output, N>;
    fn add(self, rhs: Rhs) -> Self::Output {
        Array(self.0.map(|x| x + rhs))
    }
}

// ... more trait impls ...

But this results in the following compiler error.

error[E0119]: conflicting implementations of trait `Add<Array<_, _>>` for type `Array<_, _>`
  --> src/lib.rs:51:1
   |
42 | impl<Rhs, T: ops::Add<Rhs>, const N: usize> ops::Add<Array<Rhs, N>> for Array<T, N> {
   | ----------------------------------------------------------------------------------- first implementation here
...
51 | impl<Rhs: Copy, T: ops::Add<Rhs>, const N: usize> ops::Add<Rhs> for Array<T, N> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Array<_, _>`

For more information about this error, try `rustc --explain E0119`.
error: could not compile `array` (lib) due to 1 previous error

I understand the reason for the compiler error: The Rhs generic type parameter could be Array<U, N> for some U, so the compiler is unable to tell which implementation to use.

How, though, could I implement this trait to allow operations between Array<T, N> and any of U, Array<U, N>, Array<Array<U, N>>, etc. for any U where T: core::ops::Add<U>?

My previous answer did not work for multidimensional arrays and also it copied unnecessarily.
Rust does not have negative trait bounds so there is no way to tell it first implementation of Add should only be applied between an array and non array. We basically use trait which would be in every type we care about but not in Array to do that.
So as a workaround I am using marker trait. Whatever we want added to Array we implement marker trait for that. For most things partial order is fine so we are implementing marker trait for everything which implements partial order.
In this code Array must be on RHS and any other object on LHS. This can maybe be worked around somehow but It would be very ugly.

use core::ops;
#[derive(Debug, Copy, Clone, Hash, PartialEq)]
pub struct Array<T, const N: usize>([T; N]);
pub trait Marker{}
impl<T> Marker for T where T:PartialOrd{}
impl<const N: usize,T,U,S> ops::Add<S> for Array<T, N> where T:ops::Add<S,Output=U>,S:Marker+Copy
{
    type Output = Array<U, N>;
    fn add(self, rhs: S) -> Self::Output {
        // This unwrap will never panic because the arrays are the same length.
        Array(self.0.map(|x| x+rhs))
    
}}

impl<RHS, const N: usize, T, U> ops::Add<Array<RHS, N>> for Array<T, N>
where
    RHS: ops::Add<T, Output = U>+Copy,
{
    type Output = Array<U, N>;
    fn add(self, array: Array<RHS, N>) -> Self::Output {
    let mut array=array.0.into_iter();
        // This unwrap will never panic because the arrays are the same length.
        Array(self.0.map(|x| array.next().unwrap()+x))
    }
}

fn main() {
    let a = Array([1, 2, 3, 4]);
    let b = Array([8, 7, 6, 5]);
    let c = Array([Array([8, 8, 8, 8]); 4]);
    assert_eq!(Array([9, 9, 9, 9]), a + b); // case 1
    assert_eq!(Array([Array([9, 9, 9, 9]);4]), (c+1));  // case 2
    let c=Array([Array([8, 8, 8, 8]);4]);   
    assert_eq!(Array([Array([9, 9, 9, 9]);4]), (c + 1)); 
}

Playground

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.