假设我有几个结构,如下例所示,在next()
方法中,我需要使用用户提供的缓冲区来提取下一个事件,但如果此事件是注释,则忽略注释标记为设置为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的情况下工作):< / p>
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)
}
在这里,我尝试将借用限制在词法块中,并且来自此块的 nothing 泄漏到外部。但是,我仍然收到错误:
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标志的错误有关于它的这些注释),但我无法理解究竟是什么错误这里。
答案 0 :(得分:7)
这是a limitation of the current implementation的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:跨功能的条件控制流
在编译器开发人员术语中,非词汇生命周期的当前实现是“位置不敏感”。位置灵敏度最初可用,但已在性能名称中禁用。
I asked Niko Matsakis about this code:
在您的示例的上下文中:值
event
只需要有条件地使用生命周期'buf
- 在可能执行或不执行的返回点。但是当我们“位置不敏感”时,我们只跟踪event
在任何地方必须具有的生命周期,而不考虑生命周期必须保持的位置。在这种情况下,这意味着我们将其保持在任何地方,这就是编译失败的原因。一个微妙的事情是,当前的分析在一个方面对位置敏感 - 借款发生在那里。借款的长度不是。
好消息是,将这种位置敏感性概念加回来被视为对非词汇生命周期实施的增强。坏消息:
可能会或可能不会出现在[Rust 2018]版本之前。
(注意:它确实不进入Rust 2018的初始版本)
这取决于非词汇生命周期的(甚至是更新的!)底层实现,可以提高性能。您可以使用-Z polonius
rustc +nightly -Zpolonius --edition=2018 example.rs
RUSTFLAGS="-Zpolonius" cargo +nightly build
因为这是跨函数,您有时可以通过内联函数解决此问题。
答案 1 :(得分:3)
我发布了一个问题(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
}
}
}
}
如另一个答案中所述:
一件微妙的事情是,当前的分析在某一方面对位置敏感,即发生借贷的位置。借用的期限不是。
实际上,再次在条件分支内借用了所需的引用 使其可以编译!当然,这会假设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!");
}