rustborrow-checkerlifetime-scopingreference-lifetimes

What are the differences between these two borrowing cases?


I am making a game in Rust and have a few different trait GameModules that are a part of the main App.

pub struct AppView<'a> {
    pub frame_delta: &'a f64, 
    pub area: &'a Rect
}
pub trait GameModule: WidgetRef {
    /// Update the AppComponent before each render.
    /// 
    /// Exits app on `Ok(false)` or `Err(_)`.
    fn update(&mut self, world: Rc<RefCell<World>>, app_view: &AppView) -> Result<bool>;
    /// Handle key events.
    /// 
    /// Exits app on `Ok(false)` or `Err(_)`.
    fn key_event(&mut self, world: Rc<RefCell<World>>, app_view: &AppView, key_event: KeyEvent) -> Result<bool>;
    /// Handle mouse events.    /// 
    /// Exits app on `Ok(false)` or `Err(_)`.
    fn mouse_event(&mut self, world: Rc<RefCell<World>>, app_view: &AppView, mouse_event: MouseEvent) -> Result<bool>;
}

I have a main controller that is an GameModule and has a list of AppModules, called TabWidget that updates the correct GameModule. I have an update(&mut self) -> Result<bool> function in pub struct App that has

pub fn update(&mut self) -> Result<bool> {
    let app_view = AppView {
        area: &self.tab_area,
        frame_delta: &self.frame_delta,
    };
    self.tab_widget.update(self.world.clone(), &app_view)
}

This code above works, but I attempted, first to write a function:

pub fn app_view(&self) -> AppView {
    AppView {
        frame_time: &self.frame_time,
        area: &self.tab_area
    }
}

When I used this function in App::update(&mut self) -> Result<bool>, suddenly I encountered a borrowing error.

pub fn update(&mut self) -> Result<bool> {
    let app_view = self.app_view();
    self.tab_widget.update(self.world.clone(), &app_view)
}

and

pub fn update(&mut self) -> Result<bool> {
    self.tab_widget.update(self.world.clone(), &self.app_view)
}

both refused to compile. I think I understand that Rust here is implicitly borrowing self, but App is 'static, and this implementation is for App<'static>, so when I tried:

pub fn app_view(self) -> AppView {
    // ...
}

it required that AppView be assigned the static lifetime which didn't seem like it would make sense.

What are the workarounds here? Am I misunderstanding Rust? Why does one work and the other doesn't?

As it turns out, what I am attempting here falls under RFC #1215 but in my case can be simplified by allowing AppView to be constructed from copies of the data passed in, not references. And in the case that later I do require them to be references, for heavier loads, my semantics allow me to just create one AppView and re-use it for all the functions called in that frame.


Solution

  • let's try to replicate it, see rust playground replica

    fn update(&mut self) -> Result<bool, String>
    {
        let app_view = self.app_view();
        self.tab_widget.update(self.world.clone(), &app_view);
        Ok(true)
    }
    
    error[E0502]: cannot borrow `self.tab_widget` as mutable because it is also borrowed as immutable
      --> src\main.rs
       |
    79 |         let app_view = self.app_view();
       |                        ---- immutable borrow occurs here
    80 |         self.tab_widget.update(self.world.clone(), &app_view);
       |         ^^^^^^^^^^^^^^^^------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
       |         |               |
       |         |               immutable borrow later used by call
       |         mutable borrow occurs here
    

    the error is clear, self.app_view() borrows self completely in app_view, therefore self.tab_widget.update cannot borrow the other part (self.tab_widget) mutably. and you cannot make a function that only borrows a part of a struct, this is a current limitation in rust, see How can multiple parts of self be borrowed here? Isn't self borrowed mutably as well as immutably here?

    AppView is only holding a float and a rectangle that's 2 more floats at most, it can cheaply copy them, it doesn't need to hold references and therefore borrow App, now the code should compile.

    pub struct AppView {
        pub frame_delta: f64,
        pub area: Rect
    }
    

    another way is to construct AppView inside update, you are allowed to borrow multiple parts of a struct, you just need all those borrows to be in the same function.

    fn update(&mut self) -> Result<bool, String> {
        let app_view = AppView{
            frame_delta: &self.frame_time,
            area: &self.tab_area,
        };
        self.tab_widget.update(self.world.clone(), &app_view);
        Ok(true)
    }