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_book
and的调用'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
对于双参数版本,'book
不会被迫缩短生命周期,因此可以继续。