Why doesn't the lifetime of a mutable borrow end when the function call is complete?

后端 未结 1 1615
北海茫月
北海茫月 2020-12-07 03:35

I\'m writing a bot for halite.io, and am having issues understanding some of the effects of borrowing. Here is the code that will not compile:

let scanLoc =          


        
相关标签:
1条回答
  • 2020-12-07 03:41

    Editor's note: This specific problem has been solved by the introduction of non-lexical lifetimes.

    Let's look at a tiny reproduction:

    struct Site {
        owner: u8,
    }
    
    struct GameMap {
        site: Site,
    }
    
    impl GameMap {
        fn do_anything(&self) {}
    
        fn get_site(&mut self) -> &mut Site {
            &mut self.site
        }
    }
    
    fn main() {
        let mut game_map = GameMap {
            site: Site { owner: 0 },
        };
        let site = game_map.get_site();
        game_map.do_anything();
    }
    
    error[E0502]: cannot borrow `game_map` as immutable because it is also borrowed as mutable
      --> src/main.rs:22:5
       |
    21 |     let site = game_map.get_site();
       |                -------- mutable borrow occurs here
    22 |     game_map.do_anything(); // Compiler error!
       |     ^^^^^^^^ immutable borrow occurs here
    23 | }
       | - mutable borrow ends here
    

    Our GameMap only owns a single Site, but that's enough. The call to get_site returns a reference (in this case it happens to be mutable):

    fn get_site(&mut self) -> &mut Site
    

    Thanks to lifetime elision, this is the same as

    fn get_site<'a>(&'a mut self) -> &'a mut Site
    

    This means that the returned reference is allowed to point to something inside of GameMap (which it does). Then we keep that reference in a variable - site!

    That means that we can no longer use any immutable references to game_map as they might have been (or will in the future be) invalidated by the changes that can be made to the map through the mutable reference:

    • At any given time, you can have either one mutable reference or any number of immutable references.
    • References must always be valid.

    — The Rust Programming Language chapter on references and borrowing

    Why does Rust borrow the function mutably, and even if it is borrowing the function would it not return the borrow (ending the lifetime) when returning the result, so it would be available to borrow afterwards?

    Rust borrows your struct mutably because you are calling a method that requires a mutable reference (&mut self). That method then returns a mutable reference, transferring the borrow of the struct to the returned value. The borrow ends when the returned value goes out of scope.


    So, how do you fix it? Probably the most flexible solution is to introduce a scope to constrain the mutable borrow:

    let zhu_li_do_the_thing = {
        let site = game_map.get_site();
        site.owner == 5 || site.owner == 42
    };
    
    if zhu_li_do_the_thing {
        game_map.do_anything();
    }
    

    Another is the same idea, but requires that you never store the borrow in a variable at all. Thus the mutable borrow doesn't last beyond that statement:

    if game_map.get_site().owner == 42 {
        game_map.do_anything();
    }
    

    It's common for idiomatic Rust code to have foo and foo_mut variants of a method, for when you don't need mutability. This may not help if you need to mutate game_map while the immutable borrow of site is still outstanding.

    fn get_site(&self) -> &Site {
        &self.site
    }
    
    fn get_site_mut(&mut self) -> &mut Site {
        &mut self.site
    }
    
    let site = game_map.get_site();
    if site.owner == 5 || site.owner == 42 {
        game_map.do_anything();
    }
    

    See also:

    • Returning a reference from a HashMap or Vec causes a borrow to last beyond the scope it's in?
    • What are non-lexical lifetimes?
    0 讨论(0)
提交回复
热议问题