I have a function, index_to_indices , that translates flattened indices to array indices based on shape. Since I need to account for row/column sums during indexing, using the flattened index is not enough.

fn index_to_indices(index: usize, shape: &[usize]) -> &[usize]
{
let mut indices = vec![0; shape.len()];
let mut index = index;
for (i, &s) in shape.iter().enumerate().rev() {
indices[i] = index % s;
index /= s;
}
&indices
}
fn other_func<R, D>(arr: &ArrayBase<R, D>)
where R: RawData, D: Dimension
{
// this function needs an immutable borrow of arr
todo!()
}
fn calling_fn<R,D>(array: &mut ArrayBase<R, D>)
where R: RawData, D: Dimension
{
for index in 0..array.len() {
let coordinates = index_to_indices(index, &array.shape());
// borrows arr immutably:
let value = other_func(arr);
// borrows arr mutably:
array.get_mut(coordinates) += value; //doesn't work because coordinates is not of the proper type
}
}

Question: How can I leverage index_to_indices 's output (array of indices) to effectively access elements in multi-dimensional arrays and modify them, while the effective dimension of the array is generic?

The issues arise from the fact that iterating over the array with indexed_iter_mut doesn't work because the function I need to pass needs to borrow the array itself, so I'm trying to implement something similar but with a for loop, where I can control where the borrows happen.

I think this can only work if you use a "dynamic dimension" array. Dynamic dimension arrays can be indexed using a &[usize]. Regular arrays cannot. According to this page:

the NdIndex trait is only implemented for fixed-size arrays and tuples with the correct number of elements, with the one exception of NdIndex being implemented for &[usize] in the case of dynamic-dimension arrays.

I got it to build the way (I think) you want it to work, using dynamic-dimension arrays:

We just need to use an object implementing the trait Dimension, as it will satisfy the NdIndex bound.

Since Dimension is implemented for tuples or arrays of usize, we can create an empty element and access/modify it with the same syntax I used for &[usize]. Thus, we can use generic programming around the dimension.

For easy reference, here is the main part of the code:

fn index_to_indices<D>(index: usize, shape: &[usize]) -> D
where
D: Dimension,
{
let ndim = shape.len();
// NDIM is None for dynamic dimension, so any shape is ok in that case
if D::NDIM.unwrap_or(ndim) != ndim {
panic!("Dimension mismatch");
}
let mut indices = D::zeros(ndim);
let mut index = index;
for (i, &s) in shape.iter().enumerate().rev() {
indices[i] = index % s;
index /= s;
}
indices
}
fn calling_fn<R, D>(array: &mut ArrayBase<R, D>)
where
R: DataMut + Data,
R::Elem: AddAssign<R::Elem> + Zero,
D: Dimension + RemoveAxis,
{
for index in 0..array.len() {
let coordinates = index_to_indices::<D>(index, &array.shape());
// borrows arr immutably:
let value = other_func(coordinates, array);
// borrows arr mutably:
*array.get_mut(coordinates).unwrap() += value;
}
}