即使 NLL 打开,循环中的双可变借用错误也会发生
Double mutable borrow error in a loop happens even with NLL on
假设我有如下示例中的几个结构,并且在 next()
方法中我需要使用用户提供的缓冲区来拉取下一个事件,但是如果此事件是注释,则忽略注释flag 设置为 true,我需要再次拉取下一个事件:
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 没有修复它。
我已经很久没有遇到我不明白的借用检查错误了,所以我希望它实际上是一些简单的东西,但出于某种原因我忽略了它:)
我真的怀疑根本原因与显式 'buf
生命周期有某种联系(特别是,打开 NLL 标志的错误有这些注释),但我不明白到底是什么这里错了。
这是 a limitation of the current implementation of non-lexical lifetimes 这可以用这个简化的案例显示:
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 case #3:跨函数的条件控制流
在编译器开发人员的术语中,非词法生命周期的当前实现是 "location insensitive"。位置敏感度最初是可用的,但以性能的名义被禁用了。
I asked Niko Matsakis about this code:
In the context of your example: the value event
only has to have the lifetime 'buf
conditionally — at the return point which may or may not execute. But when we are "location insensitive", we just track the lifetime that event
must have anywhere, without considering where that lifetime must hold. In this case, that means we make it hold everywhere, which is why you get a compilation failure.
One subtle thing is that the current analysis is location sensitive in one respect — where the borrow takes place. The length of the borrow is not.
好消息是,将位置敏感度的概念添加回来被视为对非词汇生命周期实现的增强。坏消息:
That may or may not be before the [Rust 2018] edition.
(注意:它没有进入 Rust 2018 的初始版本)
这取决于(甚至更新!)提高性能的非词汇生命周期的底层实现。您可以使用 -Z polonius
:
选择加入这个半实现的版本
rustc +nightly -Zpolonius --edition=2018 example.rs
RUSTFLAGS="-Zpolonius" cargo +nightly build
因为这是跨函数,有时您可以通过内联函数来解决这个问题。
我发布了一个问题 (A borrow checker problem with a loop and non-lexical lifetimes),这个问题的答案已经回答了这个问题。
我将在此处记录一个解决方法,它也可以回答这个问题。假设你有这样的代码,只能用 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
}
}
}
}
正如另一个答案中所说:
One subtle thing is that the current analysis is location sensitive in one respect — where the borrow takes place. The length of the borrow is not.
确实,再次借用需要的参考在条件分支中使其编译!当然,这假设 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!");
}
假设我有如下示例中的几个结构,并且在 next()
方法中我需要使用用户提供的缓冲区来拉取下一个事件,但是如果此事件是注释,则忽略注释flag 设置为 true,我需要再次拉取下一个事件:
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 没有修复它。
我已经很久没有遇到我不明白的借用检查错误了,所以我希望它实际上是一些简单的东西,但出于某种原因我忽略了它:)
我真的怀疑根本原因与显式 'buf
生命周期有某种联系(特别是,打开 NLL 标志的错误有这些注释),但我不明白到底是什么这里错了。
这是 a limitation of the current implementation of non-lexical lifetimes 这可以用这个简化的案例显示:
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 case #3:跨函数的条件控制流
在编译器开发人员的术语中,非词法生命周期的当前实现是 "location insensitive"。位置敏感度最初是可用的,但以性能的名义被禁用了。
I asked Niko Matsakis about this code:
In the context of your example: the value
event
only has to have the lifetime'buf
conditionally — at the return point which may or may not execute. But when we are "location insensitive", we just track the lifetime thatevent
must have anywhere, without considering where that lifetime must hold. In this case, that means we make it hold everywhere, which is why you get a compilation failure.One subtle thing is that the current analysis is location sensitive in one respect — where the borrow takes place. The length of the borrow is not.
好消息是,将位置敏感度的概念添加回来被视为对非词汇生命周期实现的增强。坏消息:
That may or may not be before the [Rust 2018] edition.
(注意:它没有进入 Rust 2018 的初始版本)
这取决于(甚至更新!)提高性能的非词汇生命周期的底层实现。您可以使用 -Z polonius
:
rustc +nightly -Zpolonius --edition=2018 example.rs
RUSTFLAGS="-Zpolonius" cargo +nightly build
因为这是跨函数,有时您可以通过内联函数来解决这个问题。
我发布了一个问题 (A borrow checker problem with a loop and non-lexical lifetimes),这个问题的答案已经回答了这个问题。
我将在此处记录一个解决方法,它也可以回答这个问题。假设你有这样的代码,只能用 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
}
}
}
}
正如另一个答案中所说:
One subtle thing is that the current analysis is location sensitive in one respect — where the borrow takes place. The length of the borrow is not.
确实,再次借用需要的参考在条件分支中使其编译!当然,这假设 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!");
}