I'm currently testing out arenas. I actually expected this code to compile but fail at runtime, so pleasantly surprised the compiler caught the issue. But I don't know if it's reasoning is correct. Can someone explain it to me?
我目前正在测试竞技场。我本来以为这段代码会编译,但在运行时失败了,令人惊喜的是,编译器发现了这个问题。但我不知道它的推理是否正确。有人能给我解释一下吗?
use bumpalo::Bump;
fn main() {
let mut bump = Bump::new();
let b1: &mut Bob = bump.alloc(Bob::default());
let b2: &mut Bob = bump.alloc(Bob::default());
let b3: &mut Bob = bump.alloc(Bob::new(Some(b1), Some(b2)));
println!("{:?}", b2);
{
bump.reset();
}
println!("{:?}", b2);
}
#[derive(Debug)]
struct Bob {
son: Option<*mut Bob>,
parent: Option<*mut Bob>,
string: String,
}
impl Bob {
fn new(son: Option<*mut Bob>, parent: Option<*mut Bob>) -> Bob {
Bob {
son,
parent,
..Bob::default()
}
}
}
impl Default for Bob {
fn default() -> Bob {
Bob {
son: None,
parent: None,
string: String::from("Hello"),
}
}
}
Error:
错误:
error[E0502]: cannot borrow `bump` as mutable because it is also borrowed as immutable
--> src\main.rs:10:9
|
6 | let b2: &mut Bob = bump.alloc(Bob::default());
| -------------------------- immutable borrow occurs here
...
10 | bump.reset();
| ^^^^^^^^^^^^ mutable borrow occurs here
11 | }
12 | println!("{:?}", b2);
| -- immutable borrow later used here
Relevant code from bumpalo:
来自umpalo的相关代码:
pub fn alloc<T>(&self, val: T) -> &mut T {
self.alloc_with(|| val)
}
#[inline(always)]
#[allow(clippy::mut_from_ref)]
pub fn alloc_with<F, T>(&self, f: F) -> &mut T
where
F: FnOnce() -> T,
{
#[inline(always)]
unsafe fn inner_writer<T, F>(ptr: *mut T, f: F)
where
F: FnOnce() -> T,
{
// This function is translated as:
// - allocate space for a T on the stack
// - call f() with the return value being put onto this stack space
// - memcpy from the stack to the heap
//
// Ideally we want LLVM to always realize that doing a stack
// allocation is unnecessary and optimize the code so it writes
// directly into the heap instead. It seems we get it to realize
// this most consistently if we put this critical line into it's
// own function instead of inlining it into the surrounding code.
ptr::write(ptr, f())
}
let layout = Layout::new::<T>();
unsafe {
let p = self.alloc_layout(layout);
let p = p.as_ptr() as *mut T;
inner_writer(p, f);
&mut *p
}
}
pub fn reset(&mut self) {
// Takes `&mut self` so `self` must be unique and there can't be any
// borrows active that would get invalidated by resetting.
unsafe {
if self.current_chunk_footer.get().as_ref().is_empty() {
return;
}
let mut cur_chunk = self.current_chunk_footer.get();
// Deallocate all chunks except the current one
let prev_chunk = cur_chunk.as_ref().prev.replace(EMPTY_CHUNK.get());
dealloc_chunk_list(prev_chunk);
// Reset the bump finger to the end of the chunk.
cur_chunk.as_ref().ptr.set(cur_chunk.cast());
// Reset the allocated size of the chunk.
cur_chunk.as_mut().allocated_bytes = cur_chunk.as_ref().layout.size();
debug_assert!(
self.current_chunk_footer
.get()
.as_ref()
.prev
.get()
.as_ref()
.is_empty(),
"We should only have a single chunk"
);
debug_assert_eq!(
self.current_chunk_footer.get().as_ref().ptr.get(),
self.current_chunk_footer.get().cast(),
"Our chunk's bump finger should be reset to the start of its allocation"
);
}
}
更多回答
Uh, this is pretty basic. All immutable references to bump
, like b1
, b2
, and b3
must end their lifetimes before you take a mutable reference to bump
to call reset
. Exactly so that you don't dereference b3
after deallocating it.
这是最基本的对bump的所有不可变引用(如b1、b2和b3)都必须结束其生命期,然后才能将对bump的可变引用用于调用reset。这样你就不会在释放b3后取消引用它。
优秀答案推荐
The signature for Bump.alloc
is:
Bump.alloc的签名是:
pub fn alloc<T>(&self, val: T) -> &mut T
This signature is exploiting Lifetime Elision rules to avoid including explicit lifetime information. The relevant rule is:
该签名利用生存期省略规则来避免包含显式生存期信息。相关的规则是:
If there are multiple input lifetime positions, but one of them is &self
or &mut self
, the lifetime of self is assigned to all elided output lifetimes.
All references in a signature must have some lifetime, so the compiler infers some for this function. Based on the above rule, the signature is effectively:
签名中的所有引用都必须有一定的生存期,因此编译器会为该函数推断出一些生存期。基于上述规则,签名是有效的:
pub fn alloc<T>(&'a self, val: T) -> &'a mut T
...or in other words, the result of this function must not outlive the Bump
that self
refers to, which is hopefully intuitive because the Bump
owns the memory that the allocation belongs to.
...或者换句话说,此函数的结果不能超过Self引用的Bump,这希望是直观的,因为Bump拥有分配所属的内存。
The Bump.reset
function has this signature:
Bump.Reset函数具有以下签名:
pub fn reset(&mut self)
This method takes a mut
(exclusive) reference to self
, which means it cannot be called while any other reference to the same object is "live". "Live" here means that later code might make use of it.
此方法接受对self的mut(独占)引用,这意味着当对同一对象的任何其他引用是“live”时,不能调用它。这里的“Live”意味着以后的代码可能会使用它。
Your code tries to use b2
after calling reset
, which means that the b2
reference remains live across the call to reset
. b2
has the same lifetime as this &mut self
, so that is illegal. The reference b2
is effectively a reference to the Bump
object (a part of the memory that belongs to it), and so it shares the same lifetime as described above.
您的代码在调用reset后尝试使用b2,这意味着b2引用在调用reset时仍然有效。b2和this有相同的生存期&mut self,所以这是非法的。引用b2实际上是对Bump对象(属于它的内存的一部分)的引用,因此它共享与上述相同的生存期。
If you remove that final reference to b2
after reset
then this code should compile, because then none of the references to parts of bump
will still be live at the time of reset
.
如果你在重置后删除了对b2的最后一个引用,那么这段代码应该可以编译,因为在重置时,对bump的部分引用都不会存在。
更多回答
我是一名优秀的程序员,十分优秀!