How can you resolve the need to mutably borrow in

2019-09-19 01:08发布

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

标签: rust
1条回答
贼婆χ
2楼-- · 2019-09-19 02:05

tl;dr Give your wrapper struct two different lifetimes:

struct Game<'a, 'b: 'a> {
    glyph_cache: &'a mut GlyphCache<'b>,
}

impl<'a, 'b> Game<'a, 'b> {
    pub fn new(glyph_cache: &'a mut GlyphCache<'b>) -> Self {
        Game { glyph_cache: glyph_cache }
    }
}

Through commenting out code, removing wrapping code, creating replacement structs, etc., your example can be further minimized:

struct GlyphCache<'a>(&'a str);
struct Game<'a>(&'a mut GlyphCache<'a>);

fn main() {
    let mut glyph_cache = GlyphCache("dummy");
    loop {
        if true {
            &mut glyph_cache;
        } else {
            Game(&mut glyph_cache);
        }
    }
}

You can then unroll the loop one time and play with inlining the conditionals to find the case the compiler is complaining about:

struct GlyphCache<'a>(&'a str);
struct Game<'a>(&'a mut GlyphCache<'a>);

fn main() {
    let mut glyph_cache = GlyphCache("dummy");

    Game(&mut glyph_cache);
    &mut glyph_cache;
}

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:

  1. 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 the GlyphCache. This checking is only done at the function signature level; the body of the constructor is not checked for the usage in main

  2. 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.

查看更多
登录 后发表回答