Borrow Differing Immutability

I'm trying to use the SDL2 crate. I'm trying to create a Surface instance, but its constructor-method needs to immutably borrow my event_pump variable like so:

let mut event_pump = context.event_pump().unwrap();

...

let surface = window.surface(&event_pump).unwrap();

Later on in my code, I need to call a mutable method on my event_pump instance called poll_iter():

for event in event_pump.poll_iter() {
    // ...other code...
}

I get an error because I'm trying to immutably borrow event_pump first, then mutably borrow event_pump when poll_iter() is called. The weird thing is that the window.surface(&event_pump) method doesn't even use the event_pump in surface()'s implementation. It looks like it's just there to define a lifetime parameter. Is this a normal strategy for defining lifetimes?

Anyhow, the issue I'm running into is that the EventPump type doesn't have any straightforward way of copying/cloning it, and I can't create another instance from my SDL context as it only allows for 1 to be instantiated in its lifetime.

I'm fairly sure that this might be more of a design issue with how I've written my code. Here's the full thing:

mod cartridge;
mod defs;
mod error;
mod utils;

use std::fs;
use std::time::Duration;

use sdl2::{
    event::Event,
    keyboard::Keycode,
    pixels::{PixelFormatEnum, Color},
    rect::{Rect, Point},
    surface::Surface,
};

const WIN_WIDTH: u32 = 160;
const WIN_HEIGHT: u32 = 144;
const WIN_PIXEL_COUNT: u32 = WIN_WIDTH * WIN_HEIGHT;
const WIN_TITLE: &'static str = "Gameboy Emulator";
const WIN_FMT: PixelFormatEnum = PixelFormatEnum::RGB888;
const WIN_PITCH: u32 = WIN_WIDTH * 3;

fn main() {
    let win_rect = Rect::new(0, 0, WIN_WIDTH, WIN_HEIGHT);
    let context = sdl2::init().expect("Unable to create SDL context");
    let mut event_pump = context.event_pump().unwrap();

    let video = context.video().expect("Unable to initialize video subsystem");
    let window = video
        .window(WIN_TITLE, WIN_WIDTH, WIN_HEIGHT)
        .build()
        .unwrap();

    // 1st place it's used (merely to set a lifetime parameter)
    let surface = window.surface(&event_pump).unwrap();

    let mut buffer = (0..WIN_PIXEL_COUNT)
        .map(|i| if i % 2 == 0 { vec![0u8, 0, 0, 0] } else { vec![255u8, 255, 255, 255] })
        .flatten()
        .collect::<Vec<u8>>();

    'running: loop {
        let mut next_surface = Surface::from_data(&mut buffer[..], WIN_WIDTH, WIN_HEIGHT, WIN_PITCH, WIN_FMT).unwrap();

        let _ = surface.blit(win_rect, &mut next_surface, None);

        // 2nd time used (poll_iter() is a mutable method)
        for event in event_pump.poll_iter() {
            match event {
                Event::Quit {..} |
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
                    break 'running
                },
                _ => {}
            }
        }

        surface.update_window();
        std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 4));
    }
}

A quick look at the SDL2 crate leaves me to believe that this is intentional. The reason event_pump is borrowed by the surface method is seems explicitly to prevent you from mutating the window while you have a reference to the Surface.

So it looks like you're only supposed to acquire that surface when you intend to draw to it, and then call update_window/finish on it as soon as you're done and then you'll get to borrow event_pump again. The docs recommend using a Canvas instead, because it is more flexible and probably performs much better.

Though you should be able to put it inside your loop like this:

    'running: loop {
        let mut next_surface = Surface::from_data(&mut buffer[..], WIN_WIDTH, WIN_HEIGHT, WIN_PITCH, WIN_FMT).unwrap();

        for event in event_pump.poll_iter() {
            match event {
                Event::Quit {..} |
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
                    break 'running
                },
                _ => {}
            }
        }
        {
            // Our 'render pass' happens after all events are processed.
            // We only borrow the surface long enough to draw.
            let surface = window.surface(&event_pump).unwrap();
            let _ = surface.blit(win_rect, &mut next_surface, None);
            surface.update_window();
        }
        std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 4));
    }

Ahh, so I was also using SDL incorrectly as well. That makes sense, and I ended up ditching this in favor of the canvas method as well.

After more research, it looked like the error wasn't quite what I thought it was either. It looked like calling surface.update_window() actually caused the compiler error. Commenting that call out or moving it out of the loop seemed to resolve the error.

I kind of understand why that error happened: 2 mutable borrows to a variable is illegal. The interesting thing is that we're able to call mutability within a loop with technically invokes that code once per iteration. That's technically an N-amount of mutable borrows. Maybe the point is that mutable borrows can't happen in written areas of the code?

Having 2 simultaneous mutable borrows is not allowed, but the borrow inside the loop is freed at the end of the block before the next iteration starts.

I'm trying to wrap my head around 2 simultaneous borrows. At first, I thought it was just borrowing the same mutable variable multiple times, but that doesn't appear to be the case.

For example, this compiles:

#[derive(Debug)]
struct Test {
    pub a: i32,
}

impl Test {
    pub fn new() -> Test {
        Test { a: 0 }
    }
    
    pub fn increment(&mut self) {
        self.a += 1;
    }
}

fn borrow_mut(test: &mut Test) {
    test.a = 10;
    dbg!(test);
}

fn main() {
    let mut test = Test::new();
    
    borrow_mut(&mut test);
    borrow_mut(&mut test);
    
    test.increment();
}

By simultaneous, is more related to concurrency?

No it's not. The compiler can't prove or disprove dynamic access patterns like "do I have concurrently running threads accessing this thing" in general. (Some of them can be proven subject to constraints, however – that is how Send and Sync work.)

Simultaneity here refers to the statically-analyzable scopes. It means that you are not allowed to do this, for example:

let mut x = 0;
let p1 = &mut x;     // -- scope of first borrow -----+
let p2 = &mut x;     // -- scope of second borrow --+ |
                     //                             | |
println!("{}", *p2); // <---------------------------+ |
println!("{}", *p1); // <-----------------------------+

since it would require the two borrows to overlap.

You are doing none of the in the above code: the borrows follow one another, and the first one expires before the second one begins, since borrow_mut consumes the mutable reference – it doesn't return it (or a reborrow of it), nor does it stash it somwhere. Consequently, the code compiles.

1 Like

I think I see now. So it's a matter of borrowing multiple times, and when those borrows are utilized. If they overlap, then the compiler will raise an error. That said, when I encounter these kinds of errors, I will know to look for potential overlaps.