如何在没有未定义行为的情况下在Rust之间共享不安全状态?

时间:2014-07-23 07:34:23

标签: rust undefined-behavior

我有一个静态数组类型,允许您创建多个只读"视图"切入其拥有的数据;但在Dropassert!上没有"悬挂"引用不再存在的数据的视图。

看起来你可以通过在结构中添加堆分配的整数来实现这一点,就像在不安全的指南中一样;类似的东西:

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

这实际上是安全的吗?

The unsafe guide说:

  

原始指针比其他指针类型具有更少的保证   由Rust语言和图书馆提供。例如,他们

     

...    - 被认为是可发送的(如果其内容被认为是可发送的),因此编译器在确保其使用时不提供任何帮助   线程安全的;例如,可以同时访问*mut int   没有同步的两个线程。

...然而

  

反方向,从*const到参考&,不是   安全。 &T始终有效,因此至少是原始指针   *const T必须对类型为T的有效实例有效。此外,结果指针必须满足别名和可变性定律   引用。

看起来虽然上面的例子可以使用,但它实际上是未定义的行为。在将*const i32转换为&i32以递增和递减引用计数时,&i32必须满足指针别名规则;它不会,因为可以同时删除多个Foo(可能,虽然在上面的示例中没有特别说明)。

你如何&#34;正确&#34;以不会导致未定义行为的方式实现这种行为?

2 个答案:

答案 0 :(得分:3)

  

可以同时删除多个Foo

不,它们至少不能完全在同一时间,因为析构函数按顺序运行。只要Foo个对象保留在一个线程中,就永远不会有多个&mut借用.count的时间点。具有&mut借位的两个地方都非常受限制,并且在操纵Foo时没有其他count操作可能发生。

然而,关键点是“留在单个线程”。如果你有多个线程中的对象,你可以一次发生两个Foo操作:创建两个Foo,并将一个传递到另一个线程,现在每个线程都可以做无论什么,只要它想要,但它们都指向相同的数据。这有问题(和未定义的行为)有两个原因:

  1. 别名&mut s;两个线程都可以执行其中一个位置&mut在同一点借用。
  2. 数据竞赛;这两个线程在没有同步的情况下更新同一块内存。 (这是重要的一点,以前的规则被设计为防止数据竞争的工具,等等。)
  3. 解决此问题的一种方法是使用marker traits阻止Foo被授予另一个线程。特别是,SendSync不会自动为原始指针实现,因此默认行为就是您想要的。

    另一种解决方法,但允许在线程之间共享/发送正在改变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);
}
相关问题