提问人:Vladimir Matveev 提问时间:5/25/2018 最后编辑:Vladimir Matveev 更新时间:11/2/2019 访问量:2324
即使 NLL 开启,循环中也会发生双重可变借用错误
Double mutable borrow error in a loop happens even with NLL on
问:
假设我有几个结构,如以下示例所示,并且在方法中我需要使用用户提供的缓冲区拉取下一个事件,但是如果此事件是注释,并且忽略注释标志设置为 true,则需要再次拉取下一个事件:next()
struct Parser {
ignore_comments: bool,
}
enum XmlEvent<'buf> {
Comment(&'buf str),
Other(&'buf str),
}
impl Parser {
fn next<'buf>(&mut self, buffer: &'buf mut String) -> XmlEvent<'buf> {
let result = loop {
buffer.clear();
let temp_event = self.parse_outside_tag(buffer);
match temp_event {
XmlEvent::Comment(_) if self.ignore_comments => {}
_ => break temp_event,
}
};
result
}
fn parse_outside_tag<'buf>(&mut self, _buffer: &'buf mut String) -> XmlEvent<'buf> {
unimplemented!()
}
}
但是,即使我启用了以下功能,此代码也会出现双重借用错误:#![feature(nll)]
error[E0499]: cannot borrow `*buffer` as mutable more than once at a time
--> src/main.rs:14:13
|
14 | buffer.clear();
| ^^^^^^ second mutable borrow occurs here
15 |
16 | let temp_event = self.parse_outside_tag(buffer);
| ------ first mutable borrow occurs here
|
note: borrowed value must be valid for the lifetime 'buf as defined on the method body at 12:5...
--> src/main.rs:12:5
|
12 | fn next<'buf>(&mut self, buffer: &'buf mut String) -> XmlEvent<'buf> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0499]: cannot borrow `*buffer` as mutable more than once at a time
--> src/main.rs:16:53
|
16 | let temp_event = self.parse_outside_tag(buffer);
| ^^^^^^ mutable borrow starts here in previous iteration of loop
|
note: borrowed value must be valid for the lifetime 'buf as defined on the method body at 12:5...
--> src/main.rs:12:5
|
12 | fn next<'buf>(&mut self, buffer: &'buf mut String) -> XmlEvent<'buf> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 2 previous errors
我可以(至少大约)理解为什么在关闭 NLL 功能的情况下会发生错误,但我不明白为什么 NLL 会发生错误。
无论如何,我的最终目标是在没有标志的情况下实现它,所以我也尝试这样做(它是递归的,这真的很不幸,但是我想出的所有非递归版本都不可能在没有 NLL 的情况下工作):
fn next<'buf>(&mut self, buffer: &'buf mut String) -> XmlEvent<'buf> {
buffer.clear();
{
let temp_event = self.parse_outside_tag(buffer);
match temp_event {
XmlEvent::Comment(_) if self.ignore_comments => {}
_ => return temp_event,
}
}
self.next(buffer)
}
在这里,我试图将借用限制在一个词法块内,并且该块中的任何内容都不会泄漏到外部。但是,我仍然收到错误:
error[E0499]: cannot borrow `*buffer` as mutable more than once at a time
--> src/main.rs:23:19
|
15 | let temp_event = self.parse_outside_tag(buffer);
| ------ first mutable borrow occurs here
...
23 | self.next(buffer)
| ^^^^^^ second mutable borrow occurs here
24 | }
| - first borrow ends here
error: aborting due to previous error
再说一次,NLL不会修复它。
我已经很久没有遇到我不明白的借用检查错误了,所以我希望它实际上是一些简单的东西,我出于某种原因忽略了:)
我真的怀疑根本原因与显式生存期有某种联系(特别是,打开 NLL 标志的错误有这些关于它的注释),但我不明白这里到底出了什么问题。'buf
答:
这是当前实现非词法生存期的局限性,这可以通过以下简化情况来显示:
fn next<'buf>(buffer: &'buf mut String) -> &'buf str {
loop {
let event = parse(buffer);
if true {
return event;
}
}
}
fn parse<'buf>(_buffer: &'buf mut String) -> &'buf str {
unimplemented!()
}
fn main() {}
此限制可防止 NLL 情况 #3:跨函数的条件控制流
用编译器开发人员的术语来说,当前非词法生存期的实现是“不区分位置的”。位置敏感度最初是可用的,但以性能的名义被禁用。
在示例的上下文中:该值只需要有条件地具有生存期 - 在可能执行也可能不执行的返回点。但是,当我们“对位置不敏感”时,我们只是跟踪必须在任何地方拥有的生命周期,而不考虑该生命周期必须在哪里。在这种情况下,这意味着我们让它在任何地方都保持,这就是编译失败的原因。
event
'buf
event
一个微妙的问题是,目前的分析在一个方面是位置敏感的——借款发生的地方。借款的长度不是。
好消息是,重新添加位置敏感度的概念被视为对非词法生存期实现的增强。坏消息:
这可能是也可能不是在 [Rust 2018] 版本之前。
(注意:它没有进入 Rust 2018 的初始版本)
这取决于非词法生存期的底层实现(甚至更新!),以提高性能。您可以使用以下命令选择加入这个半实现的版本:-Z polonius
rustc +nightly -Zpolonius --edition=2018 example.rs
RUSTFLAGS="-Zpolonius" cargo +nightly build
由于这是跨函数的,因此有时可以通过内联函数来解决此问题。
评论
unsafe
unsafe
我发布了一个问题(具有循环和非词汇生存期的借用检查器问题),这个问题的答案回答了这个问题。
我将在这里记录一个解决方法,也可以回答这个问题。假设你有这样的代码,它只用 Polonius 编译:
struct Inner;
enum State<'a> {
One,
Two(&'a ()),
}
fn get<'s>(_inner: &'s mut Inner) -> State<'s> {
unimplemented!()
}
struct Outer {
inner: Inner,
}
impl Outer {
pub fn read<'s>(&'s mut self) -> &'s () {
loop {
match get(&mut self.inner) {
State::One => (), // In this case nothing happens, the borrow should end and the loop should continue
State::Two(a) => return a, // self.inner ought to be borrowed for 's, that's just to be expected
}
}
}
}
正如另一个答案中所说:
一个微妙的问题是,目前的分析在一个方面是位置敏感的——借款发生的地方。借款的长度不是。
事实上,在条件分支中再次借用所需的引用可以编译它!当然,这使得这个假设在参考上是透明的,因此您的里程可能会有所不同,但再次借用似乎是一个足够简单的解决方法。get
struct Inner;
enum State<'a> {
One,
Two(&'a ()),
}
fn get<'s>(_inner: &'s mut Inner) -> State<'s> {
unimplemented!()
}
struct Outer {
inner: Inner,
}
impl Outer {
pub fn read<'s>(&'s mut self) -> &'s () {
loop {
match get(&mut self.inner) {
State::One => (), // In this case nothing happens, the borrow should end and the loop should continue
State::Two(a) => {
return match get(&mut self.inner) { // Borrowing again compiles!
State::Two(a) => a,
_ => unreachable!(),
}
}, // self.inner ought to be borrowed for 's, that's just to be expected
}
}
}
}
fn main() {
println!("Hello, world!");
}
评论
rustc
temp_event
rustc
XmlEvent
buffer
temp_event
XmlEvent