Hello,
I am actually using Piston in Rust for write my own Game Engine. And the borrow checker his a demon for my project.
use std::collections::HashMap;
use opengl_graphics::{TextureSettings,OpenGL};
use piston_window::*;
use crate::Traits::Drawable;
use crate::Scene::Scene;
use piston_window::WindowSettings;
pub struct GameRenderer {
txt_map: HashMap<&'static str,&'static G2dTexture>,
window: PistonWindow,
scene_visible: &'static str,
scene_map: HashMap<&'static str,&'static mut Scene>,
events_render: Vec<Box<&'static Fn(&mut Event) -> bool>>,
events_key: HashMap<&'static piston_window::Key,&'static Vec<Box<&'static Fn(&mut Event) -> bool>>>
}
impl Drawable for GameRenderer {
fn draw(&mut self,window: &mut GameRenderer,e: &piston_window::Event) {
}
}
impl GameRenderer {
pub fn new(x: u32,y: u32,title: &str) -> GameRenderer {
let mut window: PistonWindow =
WindowSettings::new(title, [x, y])
.exit_on_esc(true)
.opengl(OpenGL::V3_2)
.vsync(true)
.resizable(false)
.decorated(true)
.build()
.unwrap();
return GameRenderer{scene_visible: "",events_key: HashMap::new(),events_render: Vec::new(),window,scene_map: HashMap::new(),txt_map: HashMap::new()};
}
pub fn run_game<F>(&mut self) {
while let Some(mut e) = self.window.next() {
if let Some(r) = e.render_args() {
for l1 in &self.events_render {
l1(&mut e);
}
let mut i = self.scene_map.get(&self.scene_visible).unwrap();
i.draw(self,&mut e);
}
if let Some(b) = e.press_args() {
if let Button::Keyboard(key) = b {
match self.events_key.get(&key) {
Some(ed) => {
for de in *ed {
de(&mut e);
}
},
_ => {}
}
if self.events_key.contains_key(&key) {
for l in *self.events_key.get(&key).unwrap() {
l(&mut e);
}
}
}
}
}
}
}
And the error:
error[E0596]: cannot borrow `**i` as mutable, as it is behind a `&` reference
--> src/GameRenderer.rs:47:17
|
46 | let mut i = self.scene_map.get(&self.scene_visible).unwrap();
| ----- help: consider changing this to be a mutable reference: `&mut &mut Scene::Scene`
47 | i.draw(self,&mut e);
| ^ `i` is a `&` reference, so the data it refers to cannot be borrowed as mutable
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/GameRenderer.rs:47:17
|
46 | let mut i = self.scene_map.get(&self.scene_visible).unwrap();
| -------------- immutable borrow occurs here
47 | i.draw(self,&mut e);
| ^^----^^^^^^^^^^^^^
| | |
| | immutable borrow used by call, in later iteration of loop
| mutable borrow occurs here
To borrow something mutably from a HashMap you need to use get_mut (this will fix your first error). Also, why does draw need a mutable reference? Won't a shared reference suffice? You can fix your second error by removing the Scene from the HashMap and then using it to draw. The reason simply borrowing it won't suffice is because draw takes a mutable reference to GameRenderer,and since the Scene is inside GameRenderer, Rust thinks it is possible to alias the Scene, which is disallowed for mutable references.
It isn't possible to safely create &'static mut _, so you will have to use unsafe to do that or change this struct.
Advice: Remove all of the references to structs in GameRenderer, for example
This will make your code easier to reason about, and more readable.
piston_window::Key is Copy so there is no reason to hide it behind a reference.
The dyn keyword I put in is best practice to, and is there to show that Fn refers to the dynamically dispatched trait object, not the trait itself. If you look at the docs for Box, you can store unsized things in it, which is what allows Box<dyn _>.
Also logically GameRenderer looks like it is supposed to hold everything, if so it should own everything, not just references to them. This sort of owns everything struct doesn't usually play well with the rest of Rust, so I would advice you to break it up into smaller orthogonal parts.
You may think, that borrow checker is a deamon for you, but let consider only lines 46-47 (where it actually complains). In line 46 you are taking immutable reference to &'a self (to search for something in scene_map), getting as result some &'a Scene. Those 'a lifetimes are for now very important - you have reference to Scene, which is valid only valid as long, as reference to &'a self is valid. To achieve this, you may never do any changes to &'a self, unless &'a Scene is borrowed. However you are trying to do so in line 47, passing some mutable reference to draw. You literally allow i.draw(...) method to do some changes on self, including removal of &'a Scene from your map, while its still borrowed (and passed as another &self into i.draw()), so when there would be any later call on this &'a Scene, it would be access violation, which is not possible in rust just because of borrow checker.
For more concrete case imagine, that in your Drawable::draw(...) implementation, you are removing (for some reason) some Scene from scene_map (it is perfectly legal to do so, as long, as it has &mut GameRenderer). You are unlucky, and removed Scene is the one, you are actually drawing. Scene is destroyed, but you are still holding reference to it (via &mut self!). Next time you will do anything with self, you will get crash. Borrow checker is your guard here, preventing you from migrating your bad C++ habbits into Rust codebase.
Also stop using &'static str. It allows you to use only compile time constants, and its really never what you really want (unless you define compile time constant, but not when you are using it). If you really want not to have overhead of heap allocated string everywhere, generalize your class over lifetime, and wherever you are using &'static str use &'a str if you want readonly access, or Cow<'a, str> if you will need to modify this value (which is probably the case at least for scene_visible).
I came from Java so I make some mistakes because many things doesn't exists in Java like the borrow checker or the lifetimes and mutability. Also java doesn't use references. I have a Belgian friends and like me he has some difficulties to use the borrow checker and lifetimes.
I am going to rewrite my code properly and I am going to send to you the result.
Sorry for my English quality, I'm French.
Thank's for these responses which help me a lot. I am starting to understand borrow checker and lifetimes (I've already read the Rust Doc, Rust Book and Rust by Example many times). Indeed, I don't hate Rust, I love it ! But I've already past many nights at fighting with the borrow checker and sometimes I want to kill the borrow checker creator.
On one of Rust Meetups someone told that, there are three stages of attitude to borrow checker:
You are fighting it
You are using it
You are working like it was everywhere
You are just on first stage, don't worry - it's really easier that it seems to be on the beginning. The key thing, is that when it starts complaining, instead of automatically fixing error (which tends to be not so hard, as long, as error messages in Rust are pretty clear), try to understand why exactly is it complaining.
I feel like you i always get that errors and look for workaround it take me a lot of time. and pretty disheartening me. for me this is the dark side of rust maybe i wrong, but i think it block a lot of people that start learning rust. may be some day if become more simple, or i will understood that i wrong