Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

rust

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 = hlt::types::Location {
    x: oflow(coord.0 + l.x as i32, game_map.width),
    y: oflow(coord.1 + l.y as i32, game_map.width),
};
let scan = game_map.get_site(scanLoc, types::STILL);
if (&scan.owner != id) | (scan.owner != 0u8) {
    let ang = game_map.get_angle(l, scanLoc);
    debug!("angle b/w: {}", ang);
    return (l, 2);
}

This is the compiler error:

error[E0502]: cannot borrow `*game_map` as immutable because it is also borrowed as mutable
   --> src/MyBot.rs:112:27
      |
  110 |             let scan = game_map.get_site(scanLoc, types::STILL);
      |                        -------- mutable borrow occurs here
  111 |             if (&scan.owner != id) | (scan.owner != 0u8) {
  112 |                 let ang = game_map.get_angle(l, scanLoc);
      |                           ^^^^^^^^ immutable borrow occurs here
  ...
  116 |         }
      |         - mutable borrow ends here

This is the code for the GameMap functions and struct:

#[derive(Clone, Debug)]
pub struct GameMap {
    pub width: u16, // Number of columns.
    pub height: u16, // Number of rows.
    pub contents: Vec<Vec<Site>>,
}

impl GameMap {
    pub fn in_bounds(&self, l: Location) -> bool {
        // ...
    }
    pub fn get_distance(&self, l1: Location, l2: Location) -> u16 {
        // ...
    }
    pub fn get_angle(&self, l1: Location, l2: Location) -> f64 {
        // ...
    }
    pub fn get_location(&self, l: Location, d: u8) -> Location {
        // ...
    }
    pub fn get_site(&mut self, l: Location, d: u8) -> &mut Site {
        // ...
    }
}

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?

like image 938
Zarkoix Avatar asked Nov 13 '16 20:11

Zarkoix


1 Answers

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?
like image 68
Shepmaster Avatar answered Oct 03 '22 20:10

Shepmaster