1

I am relatively new to Rust and wanted to learn it by building a game with Piston.

There I have a Renderer struct that renders the scene and a main loop that handles game-logic events. They both need a mutable borrow and I understand that multiple borrows can lead to undefined behaviour, but I don't understand how it can in my case, since I always use the object in different scopes and in the same thread.

I had a look at this question and feel like I am supposed to use Rc and/or RefCell to solve this, but this seems too extreme. I also don't think I need ownership in the renderer (or do I, then why?), because the renderer lives shorter than the main loop.

Another solution would be to pass the mutable object every time it is needed, but in the real case this is almost for every function and I come from Java/C++ where the below code worked.

struct Renderer<'a> {
    w: &'a mut Window,
}

impl<'a> Renderer<'a> {
    fn render(&mut self) {
        let w = &mut self.w;
        // complex render operation needing the window mutably
    }
}

struct Window {
    //...
}

impl Window {
    fn poll_event(&mut self) {
        //some internal code of piston
    }
}

fn main() {
    let mut w = Window {};
    let mut renderer = Renderer { w: &mut w };

    loop {
        {
            //first event handling
            w.poll_event();
        }
        {
            //AFTERWARDS rendering
            renderer.render();
        }
    }
}

With the error:

error[E0499]: cannot borrow `w` as mutable more than once at a time
  --> src/main.rs:29:13
   |
24 |     let mut renderer = Renderer { w: &mut w };
   |                                           - first mutable borrow occurs here
...
29 |             w.poll_event();
   |             ^ second mutable borrow occurs here
...
36 | }
   | - first borrow ends here

My Rc approach compiles fine, but seems over the top:

use std::rc::Rc;

struct Renderer {
    w: Rc<Window>,
}

impl Renderer {
    fn render(&mut self) {
        let w = Rc::get_mut(&mut self.w).unwrap();
        // complex render operation needing the window mutably
    }
}

struct Window {
    //...
}

impl Window {
    fn poll_event(&mut self) {
        //some internal code of piston
    }
}

fn main() {
    let mut w = Rc::new(Window {});
    let mut renderer = Renderer { w: w.clone() };

    loop {
        {
            //first event handling
            Rc::get_mut(&mut w).unwrap().poll_event();
        }
        {
            //AFTERWARDS rendering
            renderer.render();
        }
    }
}

All I actually need to do is delaying the &mut. This works with Rc but I don't need ownership - so all the unwrap stuff will never fail since I (can) use it in different scopes.

Can this predicament be resolved or am I forced to use Rc in this case?

If there is a more "Rusty" way of doing this, please let me know.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
clotodex
  • 193
  • 2
  • 10
  • 2
    I am currently building a game with piston, and I *do* pass the mutable object every time it is needed. There is the straightforward solution, you know what you do, and it will not cause any trouble. – Boiethios Sep 19 '17 at 08:24
  • Thank you!, However I am still interested in a solution using Cell/RefCell/Box etc, since I don't quite understand the use cases yet. Otherwise it is nice to see that I am not alone in building this pattern :) – clotodex Sep 19 '17 at 08:33
  • Can you show us your attempt at replacing the mutable borrow with the appropriate container types? Using `Rc` and `RefCell` is one possible approach, but you seem reluctant to use them in your question. – E_net4 Sep 19 '17 at 09:41
  • You've been provided two idiomatic solutions (pass where needed and `Rc`) and you've essentially rejected both of them. Perhaps you can explain what **you** consider idiomatic so we aren't left to guess what kind of answer might be accepted. – Shepmaster Sep 19 '17 at 12:27
  • 2
    Also, it's **really unlikely** that you want to use `Rc::get_mut` at all. See https://stackoverflow.com/q/39065232/155423 and https://stackoverflow.com/q/44012575/155423). – Shepmaster Sep 19 '17 at 12:41
  • @Shepmaster thank you for those links, I'll have a look at them. I guess I am ok with the approach of passing all values to the functions. However I am way too unexperienced to know all possibilities of the cell types and smart pointers - Thus my question: Is there any **intended** way of doing this. Since it seems that there isn't one, I am fine with the function approach. – clotodex Sep 19 '17 at 12:49

0 Answers0