我有一个使用四叉树的程序。这个树存储了对另一个容器(Vec)拥有的数据的可变借用。我在每个游戏循环中重建四叉树,但我不想重新分配,所以我clear
了四叉树的底层Vec,而不是从头重建它。
下面是一个简单的例子,演示了同样的问题。这里我只是使用另一个Vec,而不是四叉树,因为它有同样的问题。
struct A;
fn main() {
let mut owned_data = vec![A, A, A];
let mut mut_borrowed_data = vec![];
'_outer: loop {
mut_borrowed_data.clear();
'_inner: for borrow in &mut owned_data {
mut_borrowed_data.push(borrow);
}
}
}
这会产生以下错误:
error[E0499]: cannot borrow `owned_data` as mutable more than once at a time
--> src\main.rs:8:30
|
8 | '_inner: for borrow in &mut owned_data {
| ^^^^^^^^^^^^^^^ `owned_data` was mutably borrowed here in the previous iteration of the loop
问题并不在于我在外层循环的前一次迭代中可变地借用,如果我删除它编译的mut_borrowed_data.push(data);
,因为借用检查器意识到owned_data
的可变借用在每个外层循环结束时被丢弃,因此可变借用的数量最大为1。该可变借用被“移动”到该容器中(如果我错了,请纠正我),因此它没有被删除,并且借位检查器不满意。如果我没有clear
,将有可变借位的多个副本,借位检查器不够聪明,无法意识到我只推入mut_borrowed_data
一次,而且每个外循环都推入clear
.
但就目前情况而言,在任何时候都只有一个可变借位的示例,那么下面的代码安全吗?
struct A;
fn main() {
let mut owned_data = vec![A, A, A];
let mut mut_borrowed_data = vec![];
'_outer: loop {
mut_borrowed_data.clear();
'_inner: for borrow in &mut owned_data {
let ptr = borrow as *mut A;
let new_borrow = unsafe { &mut *ptr };
mut_borrowed_data.push(new_borrow);
}
}
}
现在编译。owned_data
的可变借位(名为borrow
)没有被移入mut_borrowed_data
,因此它在外循环结束时被丢弃。这意味着owned_data
只能被借用一次。不安全代码 * 获取指向数据的指针的副本,取消引用它,并创建指向该数据的新借用。(同样,如果我说错了,请纠正我)。因为这里使用的是复制而不是移动,所以编译器允许borrow
和new_borrow
同时存在。这种unsafe的使用可能会违反借用规则,但是只要我在创建new_borrow
之后不使用borrow
,而且只要我清除mut_borrowed_data
,那么我认为这是安全/健全的。
此外,(我认为)只要清除mut_borrowed_data vec,借位检查器给出的保证仍然有效,它不会让我在一个循环中两次推入mut_borrowed_data
,因为new_borrow
在第一次插入后被移动。
我不想使用RefCell
,因为我希望它尽可能地提高性能。四叉树的全部目的是提高性能,所以我希望尽可能地减少它引入的任何开销。增加借位计数可能是便宜的,但分支(检查该值是否〈= 1),数据的间接性和简单性的降低让我感到太多的不愉快。
我在这里使用unsafe安全吗?有没有什么东西会绊倒我?
1条答案
按热度按时间dw1jzc5e1#
让我们从这个开始:你的代码是安全的,而且相当可靠。
不安全代码获取指向数据的指针的副本,解引用它并创建指向该数据的新借位(如果我说错了,请再次纠正我)。因为这使用了副本而不是移动,所以编译器允许
borrow
和new_borrow
同时存在。borrow
和new_borrow
可以同时存在的原因并不是因为原始指针在引用移动时被复制,而是因为当你将引用转换为原始指针时,你分离了生存期链--编译器无法再跟踪new_borrow
的源代码。它不允许我在一个循环中两次推入
mut_borrowed_data
,因为new_borrow
是在第一次插入之后移动的。是,但也不是:
您可以通过隐藏原始的
borrow
使其更安全一些,这样它就不能再使用了:但是它仍然不是完美的,原因是因为你分离了生存期,你得到了一个无限的,本质上是
'static
的引用,这意味着它可以使用比允许的更长的时间,例如:这并不意味着这种方法不好(这可能是我会使用的),但您需要小心。