Ndarray crate: add_assign() to slice

Hello everyone

I was hoping someone can explain to me what is happening in this example. I am creating a larger and a smaller 2D array and I am trying to add_assign() the smaller array to the center of the larger array.

Why does Variant 1 not work but all the others do?

use ndarray::prelude::*;
use std::ops::AddAssign;

fn main() {
    let mut a: Array2<i32> = Array2::zeros([4, 4]);
    let b = Array2::ones([2, 2]);

    let mask = s![1..3, 1..3];

    // Variant 1
    // a.slice_mut(mask) += &b;
    // println!("Variant 1\n{}\n", a);

    // Variant 2
    let mut slice = a.slice_mut(mask);
    slice += &b;
    println!("Variant 2\n{}\n", a);

    // Variant 3
    a.slice_mut(mask).add_assign(&b);
    println!("Variant 3\n{}\n", a);

    // Variant 4
    *&mut a.slice_mut(mask) += &b;
    println!("Variant 4\n{}\n", a);
}

(Playground)

Output:

Variant 2
[[0, 0, 0, 0],
 [0, 1, 1, 0],
 [0, 1, 1, 0],
 [0, 0, 0, 0]]

Variant 3
[[0, 0, 0, 0],
 [0, 2, 2, 0],
 [0, 2, 2, 0],
 [0, 0, 0, 0]]

Variant 4
[[0, 0, 0, 0],
 [0, 3, 3, 0],
 [0, 3, 3, 0],
 [0, 0, 0, 0]]


Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.04s
     Running `target/debug/playground`

Errors when uncommenting 1:

error[E0067]: invalid left-hand side of assignment
  --> src/main.rs:11:23
   |
11 |     a.slice_mut(mask) += &b;
   |     ----------------- ^^
   |     |
   |     cannot assign to this expression
1 Like

Is it something like

struct.foo_mut()

is not a place expression and therefore we cannot add_assign to it?

Yes, that's exactly the reason.

2 Likes

Edit: I need to learn to read better, you asked for place expressions already, too, but somehow I overlooked the word “place”.[1] I’ve thus adjusted the answer from “no” to “yes” :sweat_smile: . Take from this answer anything you didn’t know, and ignore stuff that’s clear for you; I hope that at least others who never heard of “place expression”s might learn something :slight_smile:


Indeed, a.slice_mut(mask) is not a place expression. See also the explanation of the error code: E0067 - Error codes index.

Here’s the reference talking about place expressions and value expressions: Expressions - The Rust Reference

The reason such a restriction exists for += is that the language design wants to avoid allowing somewhat useless things like 42 += 1 that modify a newly created temporary value, a useless operation since the thing being mutated doesn’t live longer than the += expression.

For many use-cases this poses no problem, but in this case, it turns out it unfortunately makes the code you want to write harder to write. For slicing of e.g. Vec or arrays, the slice type being a special kind of type that creates fat-pointer references means that things like v[i] += 1; work very well, unfortunately, custom types like ndarray’s types cannot (yet?[2]) offer the same kind of nice slicing API, and need to give special slice-reference types that include the necessary (dimension and stride) info.


Going through all the examples now, we see that

  • a.slice_mut(mask) += &b has a value-expression on the LHS of the += and is thus rejected (to clarify once more, this restriction is mainly to disallow bad code in many other cases, it’s not a restriction that’s necessary for safety in any way, which is why)
  • a.slice_mut(mask).add_assign(&b) works completely fine, since it’s an ordinary method call, not the += operator. (Only that operator itself has the restriction in place, checked before desugaring; and the restriction for place expressions is not necessary for safety in any way, so it’s unsurprising the that [more-or-less] desugaring works fine.])
  • slice += &b; works fine because variables are place expressions, and
  • *&mut a.slice_mut(mask) += &b; works fine because dereference expressions are place expressions. (You’ll find both these facts listed in the entry to the reference about place and value expressions that I’ve linked above.)

  1. You even came up with the *&mut a.slice_mut(mask) workaround, another thing I had overlooked and typed out, but I caught that point before posting! ↩︎

  2. though for the general case of a dynamic number of dimensions it will presumably always be this way, since references should be Copy-able ↩︎

1 Like

Thanks for this in depth explanation. I hope this will help others having this problem find a solution. Ndarray is a great crate but unfortunately a bit underdocumented so far and not a lot of stack overflow threads concerning it since it is too young.

1 Like

Just read your edit. That happens to me all the time. I already figured this is was what happend^^.

1 Like