为什么借用检查器不允许第二个可变借用,即使第一个已经超出范围?
Why does the borrow checker disallow a second mutable borrow even if the first one is already out of scope?
背景
我知道借用检查器不允许超过一个可变借用。例如,下面的代码是无效的:
fn main() {
let mut x = 42;
let a = &mut x;
let b = &mut x;
println!("{} {}", a, b);
}
但是,如果第一次借用因超出范围而被删除,则第二次借用有效:
fn main() {
let mut x = 1;
{
let a = &mut x;
println!("{}", a);
}
let b = &mut x;
println!("{}", b);
}
因为 non-lexical lifetimes (NLL),第一次借用甚至不必超出范围——借用检查器只要求它不再被使用。因此,以下代码在 Rust 2018 中有效:
fn main() {
let mut x = 1;
let a = &mut x;
println!("{}", a);
let b = &mut x;
println!("{}", b);
}
问题
但我不明白为什么下面的代码无效:
use std::str::Chars;
fn main() {
let s = "ab".to_owned();
let mut char_iter = s.chars();
let mut i = next(&mut char_iter);
dbg!(i.next());
let mut j = next(&mut char_iter);
dbg!(j.next());
}
fn next<'a>(char_iter: &'a mut Chars<'a>) -> impl Iterator<Item = char> + 'a {
char_iter.take_while(|&ch| ch != ' ')
}
编译错误信息:
error[E0499]: cannot borrow `char_iter` as mutable more than once at a time
--> src/main.rs:10:22
|
7 | let mut i = next(&mut char_iter);
| -------------- first mutable borrow occurs here
...
10 | let mut j = next(&mut char_iter);
| ^^^^^^^^^^^^^^ second mutable borrow occurs here
11 | dbg!(j.next());
12 | }
| - first borrow might be used here, when `i` is dropped and runs the destructor for type `impl std::iter::Iterator`
从错误消息中我认为可能NLL 还不支持这种情况。所以,我早早放弃了i
:
use std::str::Chars;
fn main() {
let s = "ab".to_owned();
let mut char_iter = s.chars();
{
let mut i = next(&mut char_iter);
dbg!(i.next());
}
let mut j = next(&mut char_iter);
dbg!(j.next());
}
fn next<'a>(char_iter: &'a mut Chars<'a>) -> impl Iterator<Item = char> + 'a {
char_iter.take_while(|&ch| ch != ' ')
}
但我收到了一条更令人困惑的错误消息:
error[E0499]: cannot borrow `char_iter` as mutable more than once at a time
--> src/main.rs:12:22
|
8 | let mut i = next(&mut char_iter);
| -------------- first mutable borrow occurs here
...
12 | let mut j = next(&mut char_iter);
| ^^^^^^^^^^^^^^
| |
| second mutable borrow occurs here
| first borrow later used here
为什么说 first borrow later used here
即使 i
之前已经删除并超出范围?
另一种方法
如果我将 next
函数的签名更改为:
,代码将被编译
fn next(char_iter: impl Iterator<Item = char>) -> impl Iterator<Item = char> {
char_iter.take_while(|&ch| ch != ' ')
}
但是,我还是想明白为什么原来的next
功能不起作用。
让我们在这里破译这个类型推导的魔法。 impl Iterator
实际上是一个具体类型:用 TakeWhile
包裹的 Chars
,所以你可以像这样重写你的方法(顺便说一句,一个有趣的任务是确定 &char
的生命周期):
fn next<'a>(
char_iter: &'a mut Chars<'a>,
) -> TakeWhile<&'a mut Chars<'a>, impl FnMut(&char) -> bool> {
char_iter.take_while(|&ch| ch != ' ')
}
现在您可能会发现输出类型与输入类型一样长,反之亦然。那个lifetime,其实是从原来使用的&str
衍生出来的。因此,您可能会得出结论,结果类型与使用的字符串一样长(即到 main
的末尾),甚至显式 drop(i)
也无济于事,因为编译器知道 Chars
借到最后。为了工作 nll 你必须(不幸的是?)帮助编译器:
fn next<'a, 'b: 'a>(
char_iter: &'a mut Chars<'b>,
) -> TakeWhile<&'a mut Chars<'b>, impl FnMut(&char) -> bool> {
char_iter.take_while(|&ch| ch != ' ')
}
问题在于,您通过声明两者具有相同的生命周期 'a
来明确告诉借用检查器 i
在下一个块中的生命与 char_iter
一样长。
fn next<'a>(char_iter: &'a mut Chars<'a>) -> impl Iterator<Item = char> + 'a {
char_iter.take_while(|&ch| ch != ' ')
}
这意味着只要 char_iter
仍在范围内,编译器就认为 &mut char_iter
仍在使用中。即,直到main()
.
结束
背景
我知道借用检查器不允许超过一个可变借用。例如,下面的代码是无效的:
fn main() {
let mut x = 42;
let a = &mut x;
let b = &mut x;
println!("{} {}", a, b);
}
但是,如果第一次借用因超出范围而被删除,则第二次借用有效:
fn main() {
let mut x = 1;
{
let a = &mut x;
println!("{}", a);
}
let b = &mut x;
println!("{}", b);
}
因为 non-lexical lifetimes (NLL),第一次借用甚至不必超出范围——借用检查器只要求它不再被使用。因此,以下代码在 Rust 2018 中有效:
fn main() {
let mut x = 1;
let a = &mut x;
println!("{}", a);
let b = &mut x;
println!("{}", b);
}
问题
但我不明白为什么下面的代码无效:
use std::str::Chars;
fn main() {
let s = "ab".to_owned();
let mut char_iter = s.chars();
let mut i = next(&mut char_iter);
dbg!(i.next());
let mut j = next(&mut char_iter);
dbg!(j.next());
}
fn next<'a>(char_iter: &'a mut Chars<'a>) -> impl Iterator<Item = char> + 'a {
char_iter.take_while(|&ch| ch != ' ')
}
编译错误信息:
error[E0499]: cannot borrow `char_iter` as mutable more than once at a time
--> src/main.rs:10:22
|
7 | let mut i = next(&mut char_iter);
| -------------- first mutable borrow occurs here
...
10 | let mut j = next(&mut char_iter);
| ^^^^^^^^^^^^^^ second mutable borrow occurs here
11 | dbg!(j.next());
12 | }
| - first borrow might be used here, when `i` is dropped and runs the destructor for type `impl std::iter::Iterator`
从错误消息中我认为可能NLL 还不支持这种情况。所以,我早早放弃了i
:
use std::str::Chars;
fn main() {
let s = "ab".to_owned();
let mut char_iter = s.chars();
{
let mut i = next(&mut char_iter);
dbg!(i.next());
}
let mut j = next(&mut char_iter);
dbg!(j.next());
}
fn next<'a>(char_iter: &'a mut Chars<'a>) -> impl Iterator<Item = char> + 'a {
char_iter.take_while(|&ch| ch != ' ')
}
但我收到了一条更令人困惑的错误消息:
error[E0499]: cannot borrow `char_iter` as mutable more than once at a time
--> src/main.rs:12:22
|
8 | let mut i = next(&mut char_iter);
| -------------- first mutable borrow occurs here
...
12 | let mut j = next(&mut char_iter);
| ^^^^^^^^^^^^^^
| |
| second mutable borrow occurs here
| first borrow later used here
为什么说 first borrow later used here
即使 i
之前已经删除并超出范围?
另一种方法
如果我将 next
函数的签名更改为:
fn next(char_iter: impl Iterator<Item = char>) -> impl Iterator<Item = char> {
char_iter.take_while(|&ch| ch != ' ')
}
但是,我还是想明白为什么原来的next
功能不起作用。
让我们在这里破译这个类型推导的魔法。 impl Iterator
实际上是一个具体类型:用 TakeWhile
包裹的 Chars
,所以你可以像这样重写你的方法(顺便说一句,一个有趣的任务是确定 &char
的生命周期):
fn next<'a>(
char_iter: &'a mut Chars<'a>,
) -> TakeWhile<&'a mut Chars<'a>, impl FnMut(&char) -> bool> {
char_iter.take_while(|&ch| ch != ' ')
}
现在您可能会发现输出类型与输入类型一样长,反之亦然。那个lifetime,其实是从原来使用的&str
衍生出来的。因此,您可能会得出结论,结果类型与使用的字符串一样长(即到 main
的末尾),甚至显式 drop(i)
也无济于事,因为编译器知道 Chars
借到最后。为了工作 nll 你必须(不幸的是?)帮助编译器:
fn next<'a, 'b: 'a>(
char_iter: &'a mut Chars<'b>,
) -> TakeWhile<&'a mut Chars<'b>, impl FnMut(&char) -> bool> {
char_iter.take_while(|&ch| ch != ' ')
}
问题在于,您通过声明两者具有相同的生命周期 'a
来明确告诉借用检查器 i
在下一个块中的生命与 char_iter
一样长。
fn next<'a>(char_iter: &'a mut Chars<'a>) -> impl Iterator<Item = char> + 'a {
char_iter.take_while(|&ch| ch != ' ')
}
这意味着只要 char_iter
仍在范围内,编译器就认为 &mut char_iter
仍在使用中。即,直到main()
.