Rust move problem with struct Option & ndarray

I am having trouble using Option within a struct and being able to access members of the struct in init() function. Rust newbie - I would appreciate some advice on how to get around this.

extern crate ndarray;

use ndarray::prelude::*;

struct Block {
    voxels: Option<Array3::<u8>>,
}

impl Block {

    fn init(&mut self, size: usize) {
    
        self.voxels = Some(Array3::<u8>::from_elem((size, size, size), 0));
        
        // Set a value in the voxel array
        match &self.voxels {
            Some(mut voxels) => {
                voxels[[1, 1, 1]] = 1;
            
            },
            None => (),
        }
    }
}



fn main() {

    let mut block = Block {
        voxels: None,
    };
    block.init(10);
    
    match block.voxels {
        Some(voxels) => {
            println!("r = {:?}", voxels[[1,1,1]]);
        },
        None => {
            println!("nada");
        }
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0507]: cannot move out of a shared reference
  --> src/main.rs:16:15
   |
16 |         match &self.voxels {
   |               ^^^^^^^^^^^^
17 |             Some(mut voxels) => {
   |                  ----------
   |                  |
   |                  data moved here
   |                  move occurs because `voxels` has type `ArrayBase<OwnedRepr<u8>, Dim<[usize; 3]>>`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0507`.
error: could not compile `playground` due to previous error

There are two ways to fix your code:

match &mut self.voxels {
    Some(voxels) => {
        voxels[[1, 1, 1]] = 1;
    },
    None => (),
}
match self.voxels {
    Some(ref mut voxels) => {
        voxels[[1, 1, 1]] = 1;
    },
    None => (),
}

In general, I suggest using the first version, and avoiding the ref/mut keywords in match statements. The ref/mut keywords were only really necessary before the ability to match on references was added to the language.

The reason your code didn't work is that you are matching on an immutable reference. You are never going to be able to modify anything through an immutable reference.

Note that matching on a reference has the following meaning: It behaves like if you are matching on the inner type, but with all of its fields replaced with the same kind of reference. So in my first snippet, you are matching on an &mut Option<Array3<u8>>, and the only field in Some has type Array3<u8>. Since you are matching on a reference, the field gets the type &mut Array3<u8> in the match.


There's also a third way:

match self.voxels.as_mut() {
    Some(voxels) => {
        voxels[[1, 1, 1]] = 1;
    },
    None => (),
}

This uses the Option::as_mut method, which converts from &mut Option<T> to Option<&mut T>. Now, since you are matching on an Option<&mut Array3<u8>>, it should be clear why the voxels field has type &mut Array3<u8>.


There's also a fourth way:

fn init(&mut self, size: usize) {
    let mut voxels = Array3::<u8>::from_elem((size, size, size), 0);
    voxels[[1, 1, 1]] = 1;

    self.voxels = Some(voxels);
}

Perfect - thank you for the clear explanation and the multiple options.

And I am wondering if your fourth way is actually the most elegant/idiomatic.

Yeah, the fourth way is probably the best option here.