N-dimensional array in Rust
Nice example of const generics! In general your code is well organized and idiomatic. Some comments:

In your new() function, you are using an argument as a default value. It's more idiomatic to use the Default trait for this:
pub fn new(dim: [usize; N]) -&gt; Self
where
    T: Clone + Default,
{
    let cap = Self::calculate_capacity(&amp;dim);
    NdArray {
        dim,
        data: vec![Default::default(); cap],
    }
}

In some of your unit tests this means you have to use the turbofish to initialize: e.g. NdArray::new becomes NdArray::&lt;usize, 2&gt;::new. However, this only occurs when the user does not access / modify anything in the NdArray, and if they do that then type inference kicks in. So I recommend providing both options to the user, say NdArray::new(dim: ...) for T: Default and NdArray::new_fillwith(dim: ..., default: T) if the user wants to specify their own default value.

Your structure could use some more functionality. For any collection of T, .len() and .iter() would be the minimum:
pub fn len(&amp;self) -&gt; usize {
    self.data.len()
}

pub fn iter(&amp;self) -&gt; impl Iterator&lt;Item = &amp;T&gt; {
    self.data.iter()
}

What about adding two nd-arrays? Multiplying by a constant? Concatenating them along a certain dimension? Generalized matrix multiplication? Some of these will probably require certain utility functions.

For example the following would probably be quite useful and a good exercise to write: iteration along a sub-dimensional slice of the array, like pub fn iter_subarray(&amp;self, coords: &amp;[Option&lt;usize&gt;; N]) -&gt; impl Iterator&lt;Item = &amp;T&gt; where if the coord is None then you iterate along all coordinates in that dimension, but if it's Some(x) then that coordinate is fixed.


You probably want to #[derive(PartialEq, Eq)] in addition to Clone and Debug.

Both the implementation and unit tests could use some handling of edge cases, in particular when the dimension is empty and when any of the dimension lengths is zero. Whether you allow these cases is up to you; I would personally allow the former but not the latter. Here's a unit test:
#[test]
fn nd_array_edgecases() {
    let array_dim0 = NdArray::&lt;usize, 0&gt;::new([]);
    assert_eq!(array_dim0.len(), 1);
    assert_eq!(array_dim0[&amp;[]], 0);
    let array_dim1 = NdArray::&lt;usize, 1&gt;::new([0]);
    assert_eq!(array_dim1.len(), 0);
    assert!(catch_unwind(|| array_dim1[&amp;[0]]).is_err());
}


Great job with the unit tests for vector overflow, that is super useful.

In get_flat_idx, Clippy recommends: for (d, &amp;dlen) in self.dim.iter().enumerate() (then use dlen instead of self.dim[d]). I agree, though in this case we still have to use d to index into idx.

In your implementation, the field .data is not intended to dynamically change size. For this reason, you could initialize with Vec::with_capacity(n) instead of Vec::new() in your new_with() function. You might also consider using a fixed-size or fixed-capacity array implementation, for example the ArrayVec crate.