在rust中存储2份用Box::into_raw()创建的裸指针,能保证安全吗?
Can safety be guaranteed when storing 2 copies of a raw pointer created with Box::into_raw() in rust?
我想实现一个以 2 种不同顺序存储值的数据结构。
值只插入一次(按两个顺序)并且在不删除整个数据结构(“仅追加”)的情况下无法删除。它们仍然可以原地变异。
可以通过其中一个命令不可变或可变地访问这些值,并且相应的函数分别采用 self
的不可变借用或 self
的可变借用。改变一个值将应用于两个订单中的值。
代码的简化版本如下所示:
pub struct TwoOrders<V> {
first_order: Vec<*mut V>,
second_order: Vec<*mut V>,
}
impl<V> TwoOrders<V> {
pub fn new() -> Self {
Self {
first_order: vec![],
second_order: vec![],
}
}
pub fn insert(&mut self, value: V) {
let ptr = Box::into_raw(Box::new(value));
// insert ptr into self.first_order in some position
// insert ptr into self.second_order in another position
}
pub fn get_from_first_order(&self, index: usize) -> Option<&V> {
self.first_order
.get(index)
.map(|v| unsafe { v.as_ref().unwrap() })
}
pub fn get_from_second_order(&self, index: usize) -> Option<&V> {
self.second_order
.get(index)
.map(|v| unsafe { v.as_ref().unwrap() })
}
pub fn get_mut_from_first_order(
&mut self,
index: usize,
) -> Option<&mut V> {
self.first_order
.get_mut(index)
.map(|v| unsafe { v.as_mut().unwrap() })
}
pub fn get_mut_from_second_order(
&mut self,
index: usize,
) -> Option<&mut V> {
self.second_order
.get_mut(index)
.map(|v| unsafe { v.as_mut().unwrap() })
}
}
impl<V> Drop for TwoOrders<V> {
fn drop(&mut self) {
self.first_order.clear();
for value_ptr in self.second_order.drain(..) {
std::mem::drop(unsafe { Box::from_raw(value_ptr) })
}
}
}
我的问题是:这真的安全吗?
我假设因为对一个值的可变引用只能通过 self
的可变借用来获得,这意味着借用检查器保证在其生命周期内只有一个对该值的独占引用, 所以结构本身在概念上就像一个编译时锁。
原始指针永远不会暴露在结构之外,并且任何函数都不允许以违反引用安全要求的方式访问值。
Box::into_raw
阻止值被自动删除,只有原始指针的一个副本被手动实现的 Drop
(使用 Box::from_raw
)删除。
我的假设是否正确?
另一个问题是:Rust 中是否存在任何现有结构(在标准库中或其他方面)可以在不使用原始指针的情况下解决这个用例,而且不必使用引用计数指针或锁?
A simplified version of the code looks something like this
如果这不是完整的代码,那么没有人可以肯定地说。任何其他 unsafe
用法可能意味着这些 unsafe
块确实不安全。 unsafe
用法是否安全不能单独确定。
不过,看起来你已经掌握了基础知识。仅通过 &self -> &T
和 &mut self -> &mut T
公开内部结构是一个很好的经验法则。使用原始指针而不是试图保留 Box
s 是正确的方法。您不需要 MaybeUninit
或 ManuallyDrop
.
您 缺少的东西是 TwoOrders
结构中的 PhantomData<V>
字段:
pub struct TwoOrders<V> {
first_order: Vec<*mut V>,
second_order: Vec<*mut V>,
_marker: PhantomData<V>,
}
这告诉编译器您的结构拥有 运行 个 V
的析构函数。这是必需的,以便编译器可以进行丢弃检查分析,以防止丢弃中的生命周期问题。见 Rust Nomicon on PhantomData.
我想实现一个以 2 种不同顺序存储值的数据结构。
值只插入一次(按两个顺序)并且在不删除整个数据结构(“仅追加”)的情况下无法删除。它们仍然可以原地变异。
可以通过其中一个命令不可变或可变地访问这些值,并且相应的函数分别采用 self
的不可变借用或 self
的可变借用。改变一个值将应用于两个订单中的值。
代码的简化版本如下所示:
pub struct TwoOrders<V> {
first_order: Vec<*mut V>,
second_order: Vec<*mut V>,
}
impl<V> TwoOrders<V> {
pub fn new() -> Self {
Self {
first_order: vec![],
second_order: vec![],
}
}
pub fn insert(&mut self, value: V) {
let ptr = Box::into_raw(Box::new(value));
// insert ptr into self.first_order in some position
// insert ptr into self.second_order in another position
}
pub fn get_from_first_order(&self, index: usize) -> Option<&V> {
self.first_order
.get(index)
.map(|v| unsafe { v.as_ref().unwrap() })
}
pub fn get_from_second_order(&self, index: usize) -> Option<&V> {
self.second_order
.get(index)
.map(|v| unsafe { v.as_ref().unwrap() })
}
pub fn get_mut_from_first_order(
&mut self,
index: usize,
) -> Option<&mut V> {
self.first_order
.get_mut(index)
.map(|v| unsafe { v.as_mut().unwrap() })
}
pub fn get_mut_from_second_order(
&mut self,
index: usize,
) -> Option<&mut V> {
self.second_order
.get_mut(index)
.map(|v| unsafe { v.as_mut().unwrap() })
}
}
impl<V> Drop for TwoOrders<V> {
fn drop(&mut self) {
self.first_order.clear();
for value_ptr in self.second_order.drain(..) {
std::mem::drop(unsafe { Box::from_raw(value_ptr) })
}
}
}
我的问题是:这真的安全吗?
我假设因为对一个值的可变引用只能通过 self
的可变借用来获得,这意味着借用检查器保证在其生命周期内只有一个对该值的独占引用, 所以结构本身在概念上就像一个编译时锁。
原始指针永远不会暴露在结构之外,并且任何函数都不允许以违反引用安全要求的方式访问值。
Box::into_raw
阻止值被自动删除,只有原始指针的一个副本被手动实现的 Drop
(使用 Box::from_raw
)删除。
我的假设是否正确?
另一个问题是:Rust 中是否存在任何现有结构(在标准库中或其他方面)可以在不使用原始指针的情况下解决这个用例,而且不必使用引用计数指针或锁?
A simplified version of the code looks something like this
如果这不是完整的代码,那么没有人可以肯定地说。任何其他 unsafe
用法可能意味着这些 unsafe
块确实不安全。 unsafe
用法是否安全不能单独确定。
不过,看起来你已经掌握了基础知识。仅通过 &self -> &T
和 &mut self -> &mut T
公开内部结构是一个很好的经验法则。使用原始指针而不是试图保留 Box
s 是正确的方法。您不需要 MaybeUninit
或 ManuallyDrop
.
您 缺少的东西是 TwoOrders
结构中的 PhantomData<V>
字段:
pub struct TwoOrders<V> {
first_order: Vec<*mut V>,
second_order: Vec<*mut V>,
_marker: PhantomData<V>,
}
这告诉编译器您的结构拥有 运行 个 V
的析构函数。这是必需的,以便编译器可以进行丢弃检查分析,以防止丢弃中的生命周期问题。见 Rust Nomicon on PhantomData.