How to store a reference in a Vec and use this later on in Rust?

Viewed 2002

I need to create a Vec to track the objects I'm creating and change it's states to use later. But if I use a clone when get object stored on vec, its state is not updated how do I do this?

#[derive(Debug, Clone)]
struct Movement {
  x: i32,
  y: i32,
}

fn main() {
   let mut current = Some(Movement { x: 1, y: 2 });

   let mut stack = Vec::new();

   stack.push(&current);

   current.as_mut().unwrap().x = 2;

   println!("m: {:?}", current);
   println!("stack.m: {:?}", stack.pop());
   current = None;
}
2 Answers

You're trying to mutate something while holding a shared reference to it somewhere else. This is a problem because Rust assumes things behind shared (&) references will not be mutated, in order to prove your code is correct in multi-threaded contexts and in the presence of certain optimizations.

To fix it, you could tell the compiler to forget about multi-threading and skip those optimizations. One way to do that is using RefCell:

use std::cell::RefCell;

#[derive(Debug, Clone)]
struct Movement {
    x: i32,
    y: i32,
}

fn main() {
    let mut current = Some(RefCell::new(Movement { x: 1, y: 2 }));
    //                     ^^^^^^^^^^^^ put it in a `RefCell`
    let mut stack = Vec::new();

    stack.push(&current);

    // note: as_ref, not as_mut
    current.as_ref().unwrap().borrow_mut().x = 2;

    println!("m: {:?}", current);
    println!("stack.m: {:?}", stack.pop());
    current = None;
}

If you need to share the object around different threads, you may want Mutex or RwLock instead. If the thing being shared is something trivially copiable, like a single i32, you could use Cell<i32> (non-thread-safe) or AtomicI32 (thread-safe). Collectively these are called interior mutability types because they allow mutation of the internal value through a shared reference.

But interior mutability only gets you so far. Putting references to local variables in a Vec is not very flexible because the Vec cannot outlive any of the variables that it references. A common idiom is to move objects into a Vec and use indices, rather than built-in references, to access them. Here's one way to do that:

#[derive(Debug, Clone)]
struct Movement {
    x: i32,
    y: i32,
}

fn main() {
    // this object will be moved into the vector
    let temp = Some(Movement { x: 1, y: 2 });

    let mut stack = Vec::new();

    // now we set `current` to be the index of the object in the vector, and use
    // `stack[current_index]` to refer to the object itself
    let current_index = stack.len();
    stack.push(temp);
    
    // you could also do something like this to get a mutable reference, but it
    // would prevent you from doing anything else with `stack` until you are done with
    // `current` (`stack[current_index]` does not have this limitation):
    //let current = stack.last_mut().unwrap()

    stack[current_index].as_mut().unwrap().x = 2;

    println!("m: {:?}", stack[current_index]);
    println!("stack.m: {:?}", stack.pop());
}

There are more options. For instance, you could put the RefCell in an Rc (reference-counted smart pointer) and clone it to share the ownership between the Vec and a local variable. In the absence of more information about your goals I would lean toward indexing the Vec in order to avoid the complication of shared mutation, but the design is ultimately your choice.

See also

To get a mutable reference to a value in a vec you can do the following:

let idx = stack.len() - 1;
if let Some(pos) = stack.get_mut(idx) {
  pos.x = 2
} else {
  ..
}

This specifically gets the last object you put in the vec with stack.push(current). Note that I would then move the objects into the vec completely and not just references to them, and access them using the above method. Here is your example corrected for this:

#[derive(Debug, Clone)]
struct Movement {
  x: i32,
  y: i32,
}

fn main() {
  let mut stack = Vec::new();
  stack.push(Movement { x: 1, y: 2 });

  let idx = stack.len()-1;
  let mut current = stack.get_mut(idx).unwrap();
  current.x = 2;

  println!("m: {:?}", current);
  println!("stack.m: {:?}", stack.pop());
}
Related