AC-*_*C-5 5 immutability lifetime mutability rust borrow-checker
我正在通过显式注释函数签名来测试我对 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);
}
Run Code Online (Sandbox Code Playgroud)
(游乐场)
对于该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),
};
}
Run Code Online (Sandbox Code Playgroud)
然后我尝试添加一个生命周期注释,指定引用和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),
};
}
Run Code Online (Sandbox Code Playgroud)
这产生了以下错误:
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
Run Code Online (Sandbox Code Playgroud)
在仔细考虑我最初尝试的内容之后,我认为将可变引用的生命周期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),
};
}
Run Code Online (Sandbox Code Playgroud)
它确实编译并输出了预期的结果。
我很困惑为什么我的初始错误消息被a_book多次可变地借用。我认为我可以传递单个可变引用,因为每次使用引用都知道该引用是可变的。我的borrow_and_read函数的最终实现似乎证实了这种想法,但我不完全确定为什么指定Book实例的生命周期比可变引用的生命周期长,从而where 'b : 'a解决了我的问题。
我希望对可变引用和Book实例使用相同的生命周期如何产生我得到的错误有一个深入的了解。
您原来的问题是生命周期太有限。通过使 上的借用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),
};
}
Run Code Online (Sandbox Code Playgroud)
该函数有两个生命周期参数:一个用于书籍本身,另一个用于书籍上的可变借阅。我们还进行了约束'b: 'a,这意味着任何具有生命周期的借用的'a有效期不超过具有生命周期的借用'b。这实际上是多余的,因为编译器无论如何都可以看到这一点。通过使用类型为 的参数&'a mut Book<'b>,'a已经不能持续比 更长时间了'b。
现在我们来看一下main。我们将其称为书本本身的生命周期'book。我们将书的可变借用生命周期称为生命周期'mtb。最后,我们将调用不可变借用 (at 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.
Run Code Online (Sandbox Code Playgroud)
所以这里问题的关键是,通过这个设置,'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),
};
}
Run Code Online (Sandbox Code Playgroud)
现在我们只有一个生命周期参数,它强制标题的生命周期和可变借用的生命周期相同。这意味着'mtb和'book必须相同。但我们只是表明这'mtb必须提前结束'book!因此,由于存在这种矛盾,编译器给了我们一个错误。我不知道为什么错误是cannot borrow*a_book的技术细节as mutable more than once at a time,但我想编译器对变量的“用法”的思考与我们谈论生命周期的方式类似。由于'book必须持续到对observe_bookand的调用'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_book一成不变地解引用并重新借用它时,任何可变借用都不能处于活动状态。这意味着,以及被迫结束的mtb更短的寿命。但它有生命周期,而且我们正在使用它,所以不能结束。因此出现了错误。'book'mtba_book'book'book
对于双参数版本,'book不会被迫缩短生命周期,因此可以继续。