为什么以下代码中的调用self.f2()
会使借阅检查程序运行?是不是在另一个范围内的else块?这是一个相当难的问题!
use std::str::Chars;
struct A;
impl A {
fn f2(&mut self) {}
fn f1(&mut self) -> Option<Chars> {
None
}
fn f3(&mut self) {
if let Some(x) = self.f1() {
} else {
self.f2()
}
}
}
fn main() {
let mut a = A;
}
error[E0499]: cannot borrow `*self` as mutable more than once at a time
--> src/main.rs:16:13
|
13 | if let Some(x) = self.f1() {
| ---- first mutable borrow occurs here
...
16 | self.f2()
| ^^^^ second mutable borrow occurs here
17 | }
| - first borrow ends here
自我借用的范围是否以self.f1()
来电开始和结束?一旦来自f1()
的呼叫返回f1()
不再使用自己,因此借用检查器不应该对第二次借用有任何问题。请注意,以下代码也失败了......
// ...
if let Some(x) = self.f1() {
self.f2()
}
// ...
我认为第二次借用应该没有问题,因为f1
和f3
未与self
同时使用f2
。
答案 0 :(得分:9)
我举了一个例子来展示这里的范围规则:
struct Foo {
a: i32,
}
impl Drop for Foo {
fn drop(&mut self) {
println!("Foo: {}", self.a);
}
}
fn generate_temporary(a: i32) -> Option<Foo> {
if a != 0 { Some(Foo { a: a }) } else { None }
}
fn main() {
{
println!("-- 0");
if let Some(foo) = generate_temporary(0) {
println!("Some Foo {}", foo.a);
} else {
println!("None");
}
println!("-- 1");
}
{
println!("-- 0");
if let Some(foo) = generate_temporary(1) {
println!("Some Foo {}", foo.a);
} else {
println!("None");
}
println!("-- 1");
}
{
println!("-- 0");
if let Some(Foo { a: 1 }) = generate_temporary(1) {
println!("Some Foo {}", 1);
} else {
println!("None");
}
println!("-- 1");
}
{
println!("-- 0");
if let Some(Foo { a: 2 }) = generate_temporary(1) {
println!("Some Foo {}", 1);
} else {
println!("None");
}
println!("-- 1");
}
}
打印:
-- 0
None
-- 1
-- 0
Some Foo 1
Foo: 1
-- 1
-- 0
Some Foo 1
Foo: 1
-- 1
-- 0
None
Foo: 1
-- 1
简而言之,似乎if
子句中的表达式同时存在于if
块和else
块中。
一方面它并不奇怪,因为它确实需要比if
块更长寿,但另一方面它确实阻止了有用的模式。
如果您更喜欢直观的解释:
if let pattern = foo() {
if-block
} else {
else-block
}
去掉了:
{
let x = foo();
match x {
pattern => { if-block }
_ => { else-block }
}
}
虽然你希望它可以去掉:
bool bypass = true;
{
let x = foo();
match x {
pattern => { if-block }
_ => { bypass = false; }
}
}
if not bypass {
else-block
}
你并不是第一个因此被绊倒的人,所以尽管改变了某些代码的含义(特别是警卫),这可能会在某个时候解决。
答案 1 :(得分:4)
这很烦人,但你可以通过引入内部范围并稍微改变控制流程来解决这个问题:
fn f3(&mut self) {
{
if let Some(x) = self.f1() {
// ...
return;
}
}
self.f2()
}
正如评论中所指出的,这没有额外的支撑。这是因为if
或if...let
表达式具有隐式范围,并且借用持续此范围:
fn f3(&mut self) {
if let Some(x) = self.f1() {
// ...
return;
}
self.f2()
}
这是Sandeep Datta和mbrubeck之间的IRC聊天记录:
mbrubeck: std:tr :: Chars包含对创建它的字符串的借用引用。完整类型名称为
Chars<'a>
。因此f1(&mut self) -> Option<Chars>
没有省略f1(&'a mut self) -> Option<Chars<'a>>
,这意味着只要self
仍然借用f1
self
的返回值在范围内。Sandeep Datta:我可以使用&#39; b代替Chars来避免这个问题吗?
mbrubeck:如果您实际上正在从
&self -> Chars
返回某个迭代器,那就不是了。但是,如果您可以通过&mut self -> Chars
(而非AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:fileURL options:nil]; //local file url with custom scheme AVAssetResourceLoader *loader = [asset resourceLoader]; [loader setDelegate:self queue:dispatch_get_main_queue()]; self.playerItem = [AVPlayerItem playerItemWithAsset:asset]; self.player = [AVPlayer playerWithPlayerItem:self.playerItem]; [self.playerView setPlayer:self.player];
)创建可以解决问题的功能。
答案 2 :(得分:3)
可变引用是非常强保证:只有一个指向特定内存位置的指针。由于您已经有一次&mut
借款,因此您也无法获得第二次借款。这将在多线程上下文中引入数据争用,并在单线程上下文中迭代失效和其他类似问题。
现在,借用是基于词汇范围,因此第一次借用持续到功能结束时段。最终,我们希望放宽这个限制,但这需要一些工作。
答案 3 :(得分:3)
以下是如何摆脱虚假错误的方法。我是Rust的新手,所以在下面的解释中可能会出现严重错误。
use std::str::Chars;
struct A<'a> {
chars: Chars<'a>,
}
'a
这里是一个生命周期参数(就像C ++中的模板参数一样)。类型可以通过Rust中的生命周期进行参数化。
Chars
类型也需要一个生命周期参数。这意味着Chars
类型可能有一个需要生命周期参数的成员元素。生命周期参数仅对引用有意义(因为这里的生命实际上意味着&#34;借用的生命周期&#34;)。
我们知道Chars
需要保留对创建它的字符串的引用,'a
可能会用来表示源字符串的生命周期。
这里我们只提供'a
作为Chars
的生命周期参数,告诉Rust编译器Chars
的生命周期与结构A
的生命周期相同。 IMO&#34; a&#39; a型A&#34;应该被理解为&#34;结构A&#34;中包含的引用的生命周期。
我认为struct实现可以独立于struct本身进行参数化,因此我们需要使用impl
关键字重复参数。在这里,我们将名称&#39; a绑定到结构A的生命周期。
impl<'a> A<'a> {
名称'b
是在函数f2
的上下文中引入的。在这里,它用于绑定引用&mut self
的生命周期。
fn f2<'b>(&'b mut self) {}
名称'b
是在函数f1
的上下文中引入的。'b
与'b
引入的f2
没有直接关系以上。
此处用于绑定引用&mut self
的生命周期。不用说这个引用也与前一个函数中的&mut self
没有任何关系,这是对self
的新独立借用。
如果我们没有使用显式生命周期注释,Rust会使用它的生命周期省略规则来得到以下函数签名...
//fn f1<'a>(&'a mut self) -> Option<Chars<'a>>
正如您所看到的,这会将引用&mut self
参数的生命周期绑定到从此函数返回的Chars
对象的生存期(此Chars
对象不必与self.chars
)这是荒谬的,因为返回的Chars
将比&mut self
引用更长。因此,我们需要将两个生命周期分开如下......
fn f1<'b>(&'b mut self) -> Option<Chars<'a>> {
self.chars.next();
请记住&mut self
借用self
,&mut self
引用的任何内容也是借用。因此,我们不能在这里返回Some(self.chars)
。 self.chars
不是我们的(错误:无法摆脱借来的内容。)。
我们需要创建一个self.chars
的克隆,以便可以发出它。
Some(self.chars.clone())
请注意,返回的Chars
与struct A的生命周期相同。
现在这里f3
没有变化,没有编译错误!
fn f3<'b>(&'b mut self) {
if let Some(x) = self.f1() { //This is ok now
} else {
self.f2() //This is also ok now
}
}
主要功能只是为了完整......
fn main() {
let mut a = A { chars:"abc".chars() };
a.f3();
for c in a.chars {
print!("{}", c);
}
}
我更新了代码,使终身关系更加清晰。
答案 4 :(得分:1)
从Rust 2018开始,Rust 1.31中的original code will work as-is可用。这是因为Rust 2018启用了non-lexical lifetimes。