Rust的生存期如何影响可变性?

问题描述 投票:2回答:1

我通过显式注释函数签名来测试我对Rust生命周期的理解,并创建了一个不确定的示例。

在此示例中,我正在模拟共享一本书并在其中翻页的概念。为此,我使用了单个可变引用,该引用传递给borrow_and_read函数,该函数更新curr_page结构的Book字段。我的Book结构和main函数如下所示:

#[derive(Debug)]
pub struct Book<'a> {
    pub title: &'a str,
    pub curr_page: Option<i32>,
    pub page_count: i32,
}

fn borrow_and_read<'a>(a_book: &'a mut Book<'a>) {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

fn main() {
    let mut the_book: Book = Book {
        title: "The Book",
        curr_page: None,
        page_count: 104,
    };

    let a_book: &mut Book = &mut the_book;

    borrow_and_read(a_book);
    borrow_and_read(a_book);

    observe_book(&*a_book);
}

pub fn observe_book<'a>(a_book: &'a Book<'a>) {
    println!("Observing: {:?}", a_book);
}

([Playground

对于我的borrow_and_read函数的第一个实现,我让编译器添加注释,并编译所有内容:

fn borrow_and_read(a_book: &mut Book) {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

然后我尝试添加单个生命周期批注,为引用和Book本身的实例指定生命周期:

fn borrow_and_read<'a>(a_book: &'a mut Book<'a>) {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

这产生了以下错误:

error[E0499]: cannot borrow `*a_book` as mutable more than once at a time
  --> src/main.rs:25:21
   |
24 |     borrow_and_read(a_book);
   |                     ------ first mutable borrow occurs here
25 |     borrow_and_read(a_book);
   |                     ^^^^^^
   |                     |
   |                     second mutable borrow occurs here
   |                     first borrow later used here

error[E0502]: cannot borrow `*a_book` as immutable because it is also borrowed as mutable
  --> src/main.rs:27:18
   |
24 |     borrow_and_read(a_book);
   |                     ------ mutable borrow occurs here
...
27 |     observe_book(&*a_book);
   |                  ^^^^^^^^
   |                  |
   |                  immutable borrow occurs here
   |                  mutable borrow later used here

[仔细考虑了我最初尝试的内容之后,我决定将Book的可变引用和Book本身的实例的生存期分开是有意义的。然后我想到了:

fn borrow_and_read<'a, 'b>(a_book: &'a mut Book<'b>) 
where 'b : 'a {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

哪个确实编译并输出预期结果。

[为什么我最初的错误消息是a_book多次多变地被借用,我感到困惑。我认为我可以绕过一个可变的引用,因为对引用的每次使用都理解该引用是可变的。我的borrow_and_read函数的最终实现似乎证实了这种想法,但我不完全确定为什么指定Book实例的生存期超过where 'b : 'a的可变引用能解决我的问题。

我希望对可变引用和Book实例使用相同的生存期会产生我得到的错误有深入的了解。

rust immutability lifetime borrow-checker mutability
1个回答
0
投票

您原件的问题是使用寿命太短。通过使Book上的借阅与书名("The Book")上的借阅具有相同的长度,可变借阅被迫持续到实际书本本身,这意味着它永远不会被一成不变地借用。

让我们来探索。检查您的固定版本然后查看原始版本对它的限制会更容易。

fn borrow_and_read<'a, 'b>(a_book: &'a mut Book<'b>) 
where 'b : 'a {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

此函数有两个生存期参数:一个用于书籍本身,一个用于书籍上的可变借用。我们还约束'b: 'a,这意味着任何寿命为'a的借贷的有效期都不会超过寿命为'b的借用。 This is actually redundant,因为编译器仍然可以看到。通过使用类型为&'a mut Book<'b>的参数,'a的持续时间不能超过'b

现在让我们来看main。我们将生命周期称为书籍本身'book。我们将生存期称为书的可变借'mtb。最后,我们将不可变借位(在observe_book处)称为'imb。让我们看看每个生命必须持续多长时间。

// Initialize `the_book`. 'book has to start before this.

// Mutably borrow `the_book`. 'mtb has to start here.
let a_book: &mut Book = &mut the_book;

// Use the mutable borrow. 'mtb has to still be valid.
borrow_and_read(a_book);
// Use the mutable borrow. 'mtb has to still be valid.
borrow_and_read(a_book);

// Deref the mutable borrow and reborrow immutably.
// 'imb has to start here, so 'mtb has to end here.
// 'imb is a reference to `the_book`, so 'book has to still be active.
observe_book(&*a_book);

// The variables are no longer needed, so any outstanding lifetimes can end here
// That means 'imb and 'book end here.

所以问题的症结在于,使用此设置,'mtb必须在'book之前结束。现在让我们看一下该函数的原始版本。

fn borrow_and_read<'a>(a_book: &'a mut Book<'a>) {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

现在我们只有一个生命周期参数,这将强制标题的生命周期和可变借用的生命周期相同。这意味着'mtb'book必须相同。但是我们只是表明'mtb必须在'book之前结束!因此,由于存在这种矛盾,编译器给了我们一个错误。我不知道错误为何是cannot borrow * a_book as mutable more than once at a time的技术细节,但是我想像编译器对变量的“用法”的理解与我们对生存期的讨论类似。由于'book必须持续到对observe_book'mtb的调用与'book相同,所以将'book的使用视为可变借用的使用。同样,我对此并不完全确定。可能值得提出问题,以查看消息是否可以改进。


我实际上确实躺在上面。尽管Rust不会执行隐式类型强制,但会执行终生强制。寿命较长的借款可以被强制为寿命较短的借款。最终在这里并没有太大关系,但是值得了解。

这本书的标题是字符串文字,其类型为&'static str,其中'static是一个特殊的生存期,可在程序的整个过程中持续使用。数据被嵌入程序本身的二进制文件中。当我们初始化the_book时,它可能具有Book<'static>类型,但同样可以强制缩短为Book<'book>的寿命。当我们采用可变借入时,我们被迫拥有'book,但我们仍然没有其他约束。

[当我们称为'book: 'mtb的单参数版本时,borrow_and_read'book都必须被强制缩短为较短的通用寿命。 (在这种情况下,由于'mtb'book: 'mtb将起作用-实际上,这是使用寿命最长的使用寿命)。使用两参数版本时,无需强制。 'mtb'book可以原样使用。

现在,当我们取消引用'mtb并一成不变地重新借款时,任何可变借款都不能处于活动状态。这意味着a_bookmtb'book都必须结束的较短寿命。但是'mtb的生存期为a_book,我们正在使用它,因此'book不能结束。因此出现错误。

使用两参数版本时,'book不会被强制缩短寿命,因此可以继续。

© www.soinside.com 2019 - 2024. All rights reserved.