Mutable borrows in loops [Design help]

Hi, I am new to Rust having gone through only the Rust book, working on a project which requires capturing a screenshot of the primary display. I am using the scrap crate for this.

fn screen_shot_and_save_image(iter:i32) {
    let one_second = Duration::new(1, 0);
    let one_frame = one_second / 60;
    let mut buffer = None;
    let display = Display::primary().expect("Couldn't find primary display.");
    let mut capturer = Capturer::new(display).expect("Couldn't begin capture.");
    let (mut w, mut h) =  (capturer.width(), capturer.height());;
    
    while buffer.is_none() {
       // Wait until there's a frame.
        match capturer.frame() {
            Ok(buf) => {
                buffer = Some(buf);
            }
            Err(error) => {
                if error.kind() == WouldBlock {
                    // Keep spinning.
                    thread::sleep(one_frame);
                    continue;
                } else {
                    panic!("Error: {}", error);
                }
            }
        };
    }   
    //work with buffer
}

the capturer.frame() has a signature of

scrap::common::dxgi::Capturer

pub fn frame<'a>(&'a mut self) -> io::Result<Frame<'a>>
Error:
cannot borrow `capturer` as mutable more than once at a time
`capturer` was mutably borrowed here in the previous iteration of the looprustcE0499

Based on similar questions, I understand that Rust does not allow mutable borrows in a loop. However I need to use this function in a while loop, incase I get the WouldBlock error, which returns a blank image intermittently. I cannot make good sense of the compiler error or suggestion so any help will be much appreciated, thank you!

From a quick look at the scrap crate, the Frame's slice of bytes is borrowed from the Capturer, so as long as you hold onto the Frame, the borrow will survive. The frame method borrows mutably from the Capturer, but I don't know if it really should...there's a lot of unsafe code in the crate.

You could copy the Frame's bytes into a separate buffer instead of holding the reference the method provides.

1 Like

Unforunately, scrap does something which is a complete sin in Rust - use lifetime parameters on the &mut self argument.
The signature should have been" pub fn frame(&mut self) -> io::Result<Frame<'_>>.
This way, the returned frame can have a shorter lifetime that the mutable borrow. But because the signature mentions the same lifetime for &mut self and the return value, the mutable borrow must live as long as the returned value lives. Since you are storing the returned value in buffer, the returned value must out-live the loop (since buffer outlives the loop`). As a consequence, the mutable borrow is extended to the next iteration.
As far as I can tell, you need to either file an issue and a PR for this crate, or fork it and maintain your own copy of it.

No, those are the same. In

pub fn frame(&mut self) -> io::Result<Frame<'_>>

the '_ here means "please use lifetime elision rules to determine the lifetime”, and the lifetime elision rules say that since there is an &mut self, that lifetime is used for returned lifetimes, and so it is completely equivalent to what was actually written.

pub fn frame<'a>(&'a mut self) -> Result<Frame<'a>>

The “sin” you are thinking of is probably the one of linking &'a mut self to a parameter on the type itself,

impl<'a> Foo<'a> {
    fn foo(&'a mut self) -> ...

which is indeed usually a mistake. But, in this case, where there is no lifetime on the type, then a method's return value must be either borrowing from self or be fully owned.

Looking at the docs, Frame derefs to [u8], so probably the right solution here is to copy the data out of the frame into a Vec<u8>, inside the loop. That way, there is no borrow of the Capturer kept around.

5 Likes

I think loop is nicer for these situations than a while:

let buffer = loop {
    match capturer.frame() {
        Ok(buf) => break buf,
        Err(error) => {
            thread::sleep(one_frame);
        }
    }
};

I think this works better with the borrow checker and has the added benefit of not wrapping the buffer in an Option.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.