When compiling this part of a Piston game, I need to mutably borrow an object in different match arms. As far as I can tell, they should all go out of scope of each other, but yet a compile error is generated.
A simplified example that demonstrates the issue:
extern crate opengl_graphics;
extern crate piston_window;
use opengl_graphics::GlGraphics;
use opengl_graphics::glyph_cache::GlyphCache;
use piston_window::{Button, Context, Input, Key, OpenGL, PistonWindow, Size, text, WindowSettings};
struct Game<'a> {
glyph_cache: &'a mut GlyphCache<'a>,
}
impl<'a> Game<'a> {
pub fn new(glyph_cache: &'a mut GlyphCache<'a>) -> Self {
Game { glyph_cache: glyph_cache }
}
}
fn main() {
let mut glyph_cache = GlyphCache::new("./FiraSans-Regular.ttf").unwrap();
let opengl = OpenGL::V3_2;
let mut window: PistonWindow = WindowSettings::new("Title",
Size {
width: 1024,
height: 768,
})
.opengl(opengl)
.build()
.unwrap();
let mut gl = GlGraphics::new(opengl);
while let Some(event) = window.next() {
match event {
Input::Render(args) => {
gl.draw(args.viewport(),
|context, graphics| draw(context, graphics, &mut glyph_cache));
}
Input::Press(Button::Keyboard(key)) => {
match key {
Key::Space => {
Game::new(&mut glyph_cache);
// In real code, the `Game` instance is then used here,
// until going out of scope.
}
_ => {}
}
}
_ => {}
}
}
}
fn draw(context: Context, graphics: &mut GlGraphics, glyph_cache: &mut GlyphCache) {
text([1.0, 1.0, 1.0, 1.0],
72,
"Hello, World!",
glyph_cache,
context.transform,
graphics);
}
I would think that when Game
goes out of scope and is dropped, it would no longer effect the borrow. The compile error generated from stable Rust 1.16.0:
error[E0499]: cannot borrow `glyph_cache` as mutable more than once at a time
--> src\main.rs:35:25
|
35 | |context, graphics| draw(context, graphics, &mut glyph_cache));
| ^^^^^^^^^^^^^^^^^^^ ----------- borrow occurs due to use of `glyph_cache` in closure
| |
| second mutable borrow occurs here
...
41 | Game::new(&mut glyph_cache);
| ----------- first mutable borrow occurs here
...
49 | }
| - first borrow ends here
error[E0499]: cannot borrow `glyph_cache` as mutable more than once at a time
--> src\main.rs:41:40
|
41 | Game::new(&mut glyph_cache);
| ^^^^^^^^^^^
| |
| second mutable borrow occurs here
| first mutable borrow occurs here
...
49 | }
| - first borrow ends here
tl;dr Give your wrapper struct two different lifetimes:
Through commenting out code, removing wrapping code, creating replacement structs, etc., your example can be further minimized:
You can then unroll the loop one time and play with inlining the conditionals to find the case the compiler is complaining about:
Amusingly, a related question was asked in the last few days, and Why does linking lifetimes matter only with mutable references? is relevant to this question as well. This thoroughly describes the cause, which ends up being lifetime variance (or the lack thereof, perhaps).
As I understand it (and I might not), there's two pieces combining:
When the outer and inner lifetimes are the same, there's the possibility that the
Game
constructor function might take ownership of a reference from theGlyphCache
. This checking is only done at the function signature level; the body of the constructor is not checked for the usage inmain
Lifetimes are currently lexical. That means that although the struct is dropped immediately, the borrow/re-borrow/transfer of a borrow that might have happened lasts longer, conflicting with the next iteration of the loop.