我有一个静态数组类型,允许您创建多个只读"视图"切入其拥有的数据;但在Drop
它assert!
上没有"悬挂"引用不再存在的数据的视图。
看起来你可以通过在结构中添加堆分配的整数来实现这一点,就像在不安全的指南中一样;类似的东西:
extern crate libc;
use libc::{c_void, calloc, free, size_t};
use std::mem::size_of;
struct Foo {
count: *mut i32,
value: i32,
}
impl Foo {
fn new(parent: Option<&mut Foo>) -> Foo {
match parent {
Some(p) => {
unsafe {
let tmp = &mut *p.count;
*tmp += 1;
println!("Created a new record, the count is now: {}", *tmp);
}
return Foo {
value: 0,
count: p.count,
};
}
None => unsafe {
let counter = calloc(size_of::<i32> as size_t, 1 as size_t) as *mut i32;
println!("counter record: {}", *counter);
return Foo {
value: 0,
count: counter,
};
},
}
}
fn count(&self) -> i32 {
unsafe {
return *self.count;
}
}
}
drop
实施更新计数器的位置:
impl Drop for Foo {
fn drop(&mut self) {
unsafe {
let tmp = &mut *self.count;
*tmp -= 1;
println!("Dropped a record, the count is now: {}", *tmp);
if *tmp == -1 {
println!("counter record: {}", *self.count);
free(self.count as *mut c_void);
println!("The final record was dropped");
}
}
}
}
此代码工作正常,测试:
fn main() {
let mut parent = Foo::new(None);
{
let child1: Foo;
let child2: Foo;
let child3: Foo;
let child4: Foo;
let child5: Foo;
{ child1 = Foo::new(Some(&mut parent)); }
{ child2 = Foo::new(Some(&mut parent)); }
{ child3 = Foo::new(Some(&mut parent)); }
{ child4 = Foo::new(Some(&mut parent)); }
{ child5 = Foo::new(Some(&mut parent)); }
assert!(parent.count() == 5);
}
assert!(parent.count() == 0);
}
收率:
counter record: 0x7f909f7fc010
Created a new record, the count is now: 1
Created a new record, the count is now: 2
Created a new record, the count is now: 3
Created a new record, the count is now: 4
Created a new record, the count is now: 5
Dropped a record, the count is now: 4
Dropped a record, the count is now: 3
Dropped a record, the count is now: 2
Dropped a record, the count is now: 1
Dropped a record, the count is now: 0
Dropped a record, the count is now: -1
counter record: 0x7f909f7fc010
The final record was dropped
这实际上是安全的吗?
原始指针比其他指针类型具有更少的保证 由Rust语言和图书馆提供。例如,他们
... - 被认为是可发送的(如果其内容被认为是可发送的),因此编译器在确保其使用时不提供任何帮助 线程安全的;例如,可以同时访问
*mut int
没有同步的两个线程。
...然而
反方向,从
*const
到参考&
,不是 安全。&T
始终有效,因此至少是原始指针*const T
必须对类型为T
的有效实例有效。此外,结果指针必须满足别名和可变性定律 引用。
看起来虽然上面的例子可以使用,但它实际上是未定义的行为。在将*const i32
转换为&i32
以递增和递减引用计数时,&i32
必须满足指针别名规则;它不会,因为可以同时删除多个Foo
(可能,虽然在上面的示例中没有特别说明)。
你如何&#34;正确&#34;以不会导致未定义行为的方式实现这种行为?
答案 0 :(得分:3)
可以同时删除多个
Foo
不,它们至少不能完全在同一时间,因为析构函数按顺序运行。只要Foo
个对象保留在一个线程中,就永远不会有多个&mut
借用.count
的时间点。具有&mut
借位的两个地方都非常受限制,并且在操纵Foo
时没有其他count
操作可能发生。
然而,关键点是“留在单个线程”。如果你有多个线程中的对象,你可以一次发生两个Foo
操作:创建两个Foo
,并将一个传递到另一个线程,现在每个线程都可以做无论什么,只要它想要,但它们都指向相同的数据。这有问题(和未定义的行为)有两个原因:
&mut
s;两个线程都可以执行其中一个位置&mut
在同一点借用。解决此问题的一种方法是使用marker traits阻止Foo
被授予另一个线程。特别是,Send
和Sync
不会自动为原始指针实现,因此默认行为就是您想要的。
另一种解决方法,但允许在线程之间共享/发送正在改变count
以存储AtomicIsize
,以避免数据争用。您需要注意使用正确的操作来确保析构函数中的线程安全,否则您可能会在还有其他引用的情况下解除分配。
答案 1 :(得分:1)
除了不是线程安全之外,您还需要使用UnsafeCell
:
UnsafeCell<T>
类型是获取被认为是可变的别名数据的唯一合法方式
use std::cell::UnsafeCell;
struct Foo {
counter: UnsafeCell<*mut i32>,
}
impl Foo {
fn new(parent: Option<&mut Foo>) -> Foo {
unsafe {
match parent {
Some(p) => {
let counter = *p.counter.get();
*counter += 1;
println!("Created a new record, the count is now: {}", *counter);
Foo {
counter: UnsafeCell::new(counter),
}
}
None => {
let counter = Box::into_raw(Box::new(0));
println!("counter record: {}", *counter);
Foo {
counter: UnsafeCell::new(counter),
}
}
}
}
}
fn count(&self) -> i32 {
unsafe { **self.counter.get() }
}
}
impl Drop for Foo {
fn drop(&mut self) {
unsafe {
let counter = *self.counter.get();
*counter -= 1;
println!("Dropped a record, the count is now: {}", *counter);
if *counter == -1 {
println!("counter record: {}", *counter);
Box::from_raw(self.counter.get());
println!("The final record was dropped");
}
}
}
}
fn main() {
let mut parent = Foo::new(None);
{
let _child1 = Foo::new(Some(&mut parent));
let _child2 = Foo::new(Some(&mut parent));
let _child3 = Foo::new(Some(&mut parent));
let _child4 = Foo::new(Some(&mut parent));
let _child5 = Foo::new(Some(&mut parent));
assert!(parent.count() == 5);
}
assert!(parent.count() == 0);
}