How to mutate data using a loop?

How to mutate data using a loop ?

Hi evereryone,

Following the awesome Rust + Web Assembly tutorial, I'm trying to implement Conway's Game of Life like the tutorial explains, but in a terminal application.
The code is basically the same expect for displaying the data, which I'll do eventually using Termion.

The game of life is implemented by creating a struct called Universe :

pub struct Universe {
    width: u32,
    height: u32,
    cells: Vec<Cell>,
}

(Cell is an enum, either Cell::Dead or Cell::alive).

The Universe is brought to its new state by calling the tick() function :

impl Universe {
  pub fn tick(&mut self) -> Self {
  //content
  }
}

Now in the tutorial, this function is called in a loop by the javascript code :

const renderLoop = () => {
  pre.textContent = universe.render();
  universe.tick();
  requestAnimationFrame(renderLoop);
}

But I want to call it in my main.rs, like so :

let mut universe = Universe::new_random();
println!("{}", universe);

println!("{}", universe.tick());
println!("{}", universe.tick());
//etc.

Ideally in a loop:

loop {
  println!("{}", universe.tick());
}

Which compiles, but since tick()'s signature is pub fn tick(&mut self) -> Self, the result of universe.tick() is always the same, it refers to the data without mutating it.
So I changed the signature to pub fn tick(mut self) -> Self, which fails, rustc tells me:

17 |     let universe = Universe::new_random();
   |         -------- move occurs because `universe` has type `util::Universe`, which does not implement the `Copy` trait  
20 |     println!("{}", universe.tick());
   |                    -------- value moved here
21 |     println!("{}", universe.tick());
   |                    ^^^^^^^^ value used here after move

Now I've read the book about memory allocation, I don't want to copy anything and have the memory overloaded. What I want to do is re-write the memory each time I use the tick() function, in a loop, ideally.

What can I do?

Your tick fn should take &mut self but not return anything:

pub fn tick(&mut self) {
...
}
2 Likes

Holy crap that works ! Had to rewrite my function a bit of course. Thanks a lot.

The signature fn tick(&mut self) -> Self means it takes a uniquely borrowed reference of Self, and returns a fresh new copy of Self that owns all its content. Basically:

  • T: You own it. You can do whatever on it until you borrow it out. It will be dropped when it goes out of scope.

  • &T: You borrowed a shared reference of it. You can only read on it to prevent concurrency problem, either between or within thread. The only allowed exception here is an interior mutability which allows Rc/Arc, Cell/RefCell, and Mutex/RwLock.

  • &mut T: You borrowed a unique reference of it.The compiler guarantees that no other shared/unique references can be coexist during its lifetime. Due to this property, you can freely modify it's content even it's behind Mutex!

4 Likes

Cool, thanks for the recall.

Then why does fn tick(& mut self), a function that returns (), actually change the data (and not once, but a thousand times), if it takes a borrowed reference as an argument?

It changes the data because you're accessing it through the mutable reference. This allows you to modify the data in place, without moving it/changing the owner.

To compare:

  • fn tick(mut self) -> Self
    • You're handing over ownership of self, and saying "please give me back an updated version".
    • The self object is moved into the function, and then (potentially) back out again.
  • fn tick(&mut self)
    • You're handing over a reference to self, and saying "please make changes to the data this points to".
    • The self object is not moved into the function - it stays where it is.
1 Like

Perfect, I got the picture now. I'm grateful.

edit : I see there is a ticked box button to select which reply brings the solution to my question. Vitalyd brought the solution and you brought the explanation. Had to chose one, sorry :wink:

1 Like

As a rule of thumb, you either mutate or you clone (doing both at the same time has the flexibility disadvantage of mutating and the performance cost of an additional clone: the worst of both worlds).

That's why a function like fn mutate_and_clone (&mut T) -> T has a very bad signature.

If you intend mutation, then using fn mutate (&mut T) suffices: after calling mutate(&mut my_thing);, you know that my_thing is still accessible and has been mutated (e.g., by a .tick()).

If you want / require a T on return position, then:

  • either you take ownership in argument position: fn move_in_and_out (T) -> T;

    In you example, this is what happens for

    fn tick (mut self: Self) -> Self
    

    then, every time you call tick, you need to rebind the new value:

    let mut universe = ...;
    loop {
        let new_universe = tick(universe);
        // universe is "moved out", we need to use new_universe afterwards,
        // but the previous line may be executed again, so we move new_universe back into universe
        universe = new_universe;
    }
    // or in one line:
    loop {
        universe = tick(universe);
    }
    
    • And this is where having instead a fn tick (&mut self: &mut Self) is, among other things, more ergonomic:

      loop {
          universe.tick();
      }
      
  • or you .clone() and are then free to mutate the newly cloned value before returning it. Example:

    fn clone_and_complete (s: &'_ String) -> String
    {
        let mut new_s = s.clone();
        new_s.push_str(", World!");
        new_s
    }
    
    fn main ()
    {
        let hello = String::from("Hello");
        assert_eq!(clone_and_complete(&hello), "Hello, World!");
        assert_eq!(hello, "Hello");
    }
    
3 Likes

This is so relevant and nice I can barely believe this is real life. Where do you guys come from?

edit: oh yes, rebinding was the solution I thought about, though I didn't know how to implement it. But the fn tick(&mut self) signature is the most elegant.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.