英文:
Understanding Rust Closures: Why do they Continuously Hold Mutable References?
问题
I'm currently learning about closures in Rust and am trying to understand why they continuously hold references until their last use.
Consider the following code:
fn borrows_mutably_func(list: &mut Vec<i32>) {
list.push(4);
}
fn main() {
let mut list = vec![1, 2, 3];
println!("Before defining: {:?}", list);
borrows_mutably_func(&mut list);
println!("After calling first (func): {:?}", list);
borrows_mutably_func(&mut list);
println!("After calling second (func): {:?}", list);
let mut borrows_mutably_closure = || list.push(5);
borrows_mutably_closure();
println!("After calling first (closure): {:?}", list);
borrows_mutably_closure();
println!("After calling second (closure): {:?}", list);
}
In this code, borrows_mutably_func
is a function that mutates list
by adding an element to it. This function can be called multiple times without any issues. However, when I try to replicate this behavior using a closure (borrows_mutably_closure
), the code fails to compile.
From my understanding, this issue arises because the closure captures list
by a mutable reference and holds onto this reference continuously until its last use. This differs from the function, which releases the reference after each call.
My question is: Why does Rust enforce this behavior for closures? Why can't the closure release the mutable reference after its first use (like the function does) and re-acquire it just before its second use? This approach seems more intuitive to me, as it would allow the closure to behave similarly to the function without continuously holding the mutable reference. Any insights would be greatly appreciated!
英文:
I'm currently learning about closures in Rust and am trying to understand why they continuously hold references until their last use.
Consider the following code:
fn borrows_mutably_func(list: &mut Vec<i32>) {
list.push(4);
}
fn main() {
let mut list = vec![1, 2, 3];
println!("Before defining: {:?}", list);
borrows_mutably_func(&mut list);
println!("After calling first (func): {:?}", list);
borrows_mutably_func(&mut list);
println!("After calling second (func): {:?}", list);
let mut borrows_mutably_closure = || list.push(5);
borrows_mutably_closure();
println!("After calling first (closure): {:?}", list);
borrows_mutably_closure();
println!("After calling second (closure): {:?}", list);
}
In this code, borrows_mutably_func
is a function that mutates list
by adding an element to it. This function can be called multiple times without any issues. However, when I try to replicate this behavior using a closure (borrows_mutably_closure
), the code fails to compile.
From my understanding, this issue arises because the closure captures list
by a mutable reference and holds onto this reference continuously until its last use. This differs from the function, which releases the reference after each call.
My question is: Why does Rust enforce this behavior for closures? Why can't the closure release the mutable reference after its first use (like the function does) and re-acquire it just before its second use? This approach seems more intuitive to me, as it would allow the closure to behave similarly to the function without continuously holding the mutable reference. Any insights would be greatly appreciated!
答案1
得分: 4
因为这不可行。闭包本质上是一个结构体,它存储了它所闭合的所有内容。所以在这里,你在第13行实际上实例化了一个类似于以下结构的结构体:
struct BorrowsMutablyClosure<'a> {
list: &'a mut Vec<i32>
}
impl BorrowsMutablyClosure<'_> {
fn call_mut(&mut self) {
self.list.push(5);
}
}
函数则是一个完全不同的情况,你在每次调用时传入了引用,这就是函数如何获取引用的方式。
你可以用同样的方式处理闭包,如果你将引用作为参数传入,闭包就不需要在内部存储引用了。
这种方法对我来说更直观
但这是没有意义的。在创建闭包的瞬间,可变引用必须一直存在,直到闭包被销毁,因为闭包需要将它存储起来。闭包没有机制来释放然后重新获取可变引用。
英文:
> My question is: Why does Rust enforce this behavior for closures? Why can't the closure release the mutable reference after its first use (like the function does) and re-acquire it just before its second use?
Because it's not feasible. A closure is, in essence, a structure which stores all the things it closes over. So here on line 13 you essentially instantiate a structure similar to:
struct BorrowsMutablyClosure<'a> {
list: &'a mut Vec<i32>
}
impl BorrowsMutablyClosure<'_> {
fn call_mut(&mut self) {
self.list.push(5);
}
}
The function is a completely different case, you pass in the reference during each call, that's how the reference is available to the function.
You can do the exact same thing with the closure, if you pass in the reference as parameter, it won't need to store the reference internally.
> This approach seems more intuitive to me
But it makes no sense. The mutable reference is necessarily outstanding from the instant you create the closure to the instant the closure is destroyed, since the closure needs to store it. There is no mecanism for the closure to release then re-acquire a mutable reference.
答案2
得分: 2
闭包实际上与可变引用的行为相同,不同之处在于,在第一个示例中,您没有创建可变引用然后“重新获取”它,而是创建了两个单独的可变引用,两者的生命周期都不会与list
的其他用途重叠。
如果您改为创建一个跨越两次调用的可变引用,您将得到与第二个示例(使用闭包)相同的错误:
let list_ref = &mut list;
borrows_mutably_func(list_ref);
// [E0502]: cannot borrow `list` as immutable because it is also borrowed as mutable
println!("After calling first (func): {:?}", list);
borrows_mutably_func(list_ref);
基本上,Rust 不跟踪可变引用在仍然活动时是否已“释放”;只要可变引用仍然存活,借用检查器要求不得以其他方式访问相同的值,即使该可变引用可以被“释放”然后“重新获取”。
要满足借用检查器的要求,您必须显式释放和重新获取可变引用,方法是使用具有不同生命周期的两个不同的可变引用,就像您的第一个示例那样。
英文:
The closure is actually behaving the same as a mutable reference would ─ the difference is that in the first example you are not creating a mutable reference and then "re-acquiring" it, you are creating two separate mutable references, neither of which has a lifetime overlapping another use of list
.
If you instead create one mutable reference whose lifetime spans both calls, you get the same error as the second example (with the closure):
let list_ref = &mut list;
borrows_mutably_func(list_ref);
// [E0502]: cannot borrow `list` as immutable because it is also borrowed as mutable
println!("After calling first (func): {:?}", list);
borrows_mutably_func(list_ref);
Basically, Rust doesn't keep track of whether a mutable reference is currently "released" when it is still live; so long as the mutable reference is still alive, the borrow checker requires that the same value is not accessed otherwise, even if that mutable reference could be "released" and then "reacquired".
To satisfy the borrow checker you will have to explicitly release and reacquire the mutable reference by using two different mutable references with separate lifetimes, like your first example does.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论