为什么可变借用的生命周期不会在函数调用完成后结束?

6

我正在为halite.io编写一个机器人,并且在理解借用的一些影响方面遇到了问题。以下是无法编译的代码:

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);
}

以下是编译器错误:

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

这是关于GameMap函数和结构的代码:

#[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 {
        // ...
    }
}

为什么Rust要以可变方式借用函数,即使它正在借用函数,它也不会在返回结果时返回借用(结束生命周期),这样它就可以在之后继续借用呢?

5
欢迎来到Stack Overflow!在提问之前,我们期望您在提问之前进行充分的研究。例如,已经有77个关于同样错误消息的问题存在。也许您可以进一步[编辑]您的问题,解释为什么这个问题与所有这些问题都不同? - Shepmaster
问题是为什么纯函数调用会像可变借用一样,以及为什么借用的生命周期扩展到了那一行之外。我将编辑问题以反映这一点。 - Zarkoix
我不确定你是否仔细阅读了错误信息。它说game_map被可变地借用,因为调用了get_site,而你没有在代码中包含它(我已经编辑过了)。请参见[MCVE]以获取有关如何在问题中呈现代码的进一步信息。我不确定如何有意义地回答“为什么Rust可变地借用[需要对self进行可变引用的方法]”这个问题。 - Shepmaster
首先,你发现该方法并添加它的能力绝对是令人难以置信的。其次,更重要的是,为什么借用的生命周期不会在函数调用结束时结束呢?这样它将会在之后继续可供借用。 - Zarkoix
@Zarkoix:我同意,他真的很棒 ;) - Matthieu M.
1个回答

17
编辑注:这个具体问题已经通过引入非词法生命周期得到解决。
让我们来看一个小的复现:
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

我们的GameMap只拥有一个Site,但这已经足够了。调用get_site方法返回一个引用(在这种情况下它是可变的):
fn get_site(&mut self) -> &mut Site

多亏了生命周期省略,这与以下内容相同

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

这意味着返回的引用允许指向GameMap内部的某个内容(它确实如此)。然后我们将该引用存储在一个变量-site中!
这意味着我们不能再使用任何不可变引用来引用game_map,因为它们可能已经被(或将来会被)通过可变引用对地图所做的更改所失效:
  • 在任何时候,您只能拥有一个可变引用或任意数量的不可变引用。
  • 引用必须始终有效。
Rust编程语言中关于引用和借用的章节 为什么Rust要以可变方式借用函数,即使它正在借用函数,它是否不会在返回结果时返回借用(结束生命周期),以便以后可以再次借用?
Rust会可变地借用你的结构体,因为你调用了一个需要可变引用(&mut self)的方法。该方法随后返回一个可变引用,并将结构体的借用转移到返回值上。当返回值超出作用域时,借用就结束了。

那么,如何解决这个问题呢?可能最灵活的解决方案是引入一个作用域来限制可变借用:

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();
}

另一种方法是相同的思路,但需要您根本不将借用存储在变量中。因此可变借用不会超出该语句范围:
if game_map.get_site().owner == 42 {
    game_map.do_anything();
}

常见的惯用 Rust 代码会有一个方法的 foofoo_mut 变体,当您不需要可变性时使用。但如果您需要在不可变借用 site 仍然存在时改变 game_map,则这可能无法帮助。
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();
}

参见:


2
如果我在9个月前看到这个答案,它一定会对我有很大的帮助!再次感谢Shepmaster的出色回答(点赞) - Simon Whitehead

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接