我通过显式注释函数签名来测试我对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
实例使用相同的生存期会产生我得到的错误有深入的了解。
您原件的问题是使用寿命太短。通过使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_book
和mtb
和'book
都必须结束的较短寿命。但是'mtb
的生存期为a_book
,我们正在使用它,因此'book
不能结束。因此出现错误。
使用两参数版本时,'book
不会被强制缩短寿命,因此可以继续。