如何安全地创建UnsafeCell <c_void>?

时间:2018-10-02 03:43:45

标签: rust unsafe

UnsafeCell documentation

  

UnsafeCell<T>类型是获取被视为可变的可别名数据的唯一合法方法。

唯一的构造方法是:

pub const fn new(value: T) -> UnsafeCell<T>

但是,无法创建c_void,我们只能创建*mut c_void*const c_void

是否可以从UnsafeCell<c_void>创建*mut c_void?这样,我们就可以让编译器知道指针可以指向可变的东西。

还是没有必要?即使我们知道某些FFI调用会突变它指向的数据并且有多个引用,也可以始终使用*mut c_void吗?

一个用例将是:

struct FFIStruct { v: UnsafeCell<c_void>, other_fields: ... }
impl FFIStruct {
    // We don't want to require &mut self, as we 
    // are sure private call_ffi() will always be called 
    // sequentially, and we don't want to stop
    // status() being callable during the call
    fn call_ffi(&self){ ffi_function(self.v.get()) }
    pub fn status(&self) -> FFIStatus { ... }
}

现在我们如何创建FFIStruct?还是只使用*mut c_void就可以了?

创建&Cell<c_void>

的示例代码

需要#![feature(as_cell)]

unsafe fn get_cell<'a>(p: *mut c_void) -> &'a Cell<c_void> {
    Cell::from_mut(&mut *p)
}

2 个答案:

答案 0 :(得分:3)

TL; DR:只需使用*mut Foo。在这里 不需要任何类型的单元格。


免责声明:目前还没有正式的Rust内存模型。

无法创建这种类型的句点,因为您无法 1 创建c_void的实例。

问题是,您不需要创建这样的类型。 Aliasing is not spatial but temporal。您可以有多个*mut T指向同一地点,直到您尝试访问一个地点都没关系。实质上,这会将其转换为参考,并且在该参考存在时需要保持别名要求。

  

原始指针不属于Rust的安全内存模型。

     

The Rustonomicon

  

不同于引用和智能指针,原始指针:

     
      
  • 允许通过具有不变和可变的指针或指向同一位置的多个可变的指针来忽略借用规则
  •   
  • 不能保证指向有效内存
  •   
  • 允许为空
  •   
  • 不执行任何自动清理
  •   
     

¸— The Rust Programming Language

另请参阅:

1 从技术上讲,您可以,但这仅仅是因为实现和向后兼容的限制。

答案 1 :(得分:-1)

some internal discussion in the Rust foruma discussion on RFC 1861之后,我意识到c_void只是一种常见的解决方法,并且存在其他选项,例如struct Opaque<UnsafeCell<()>>

因此,我得出的结论是,这里需要的是*const UnsafeCell<c_void>。根据其类型,我们知道:

  • 这是原始指针,因此适合立即发送到FFI;
  • 原始指针假定为const,这意味着对UB *mut T的任何强制转换都将由UB和程序员来避免。这样可以保护它指向在Rust中被修改的内存(除非通过UnsafeCell,原因);
  • 它包含UnsafeCell,因此它意味着内部可变性。这证明FFI功能能够对其进行更改;
  • 对于c_void,无法创建UnsafeCell<c_void>。它们只能由FFI创建。

Demostrate

use std::cell::UnsafeCell;
use std::os::raw::c_void;

//Let's say all those functions are in an FFI module,
//with the exact same behaviour
fn ffi_create() -> *mut c_void {
    Box::into_raw(Box::new(0u8)) as *mut c_void
}
unsafe fn ffi_write_u8(p: *mut c_void, v:u8) {
    *(p as *mut u8) = v;
}
unsafe fn ffi_read_u8(p: *mut c_void) -> u8 {
    *(p as *mut u8)
}

fn main() {
    unsafe {
        //let's ignore ffi_destroy() for now
        let pointer = ffi_create() as *const UnsafeCell<c_void>;
        let ref_pointer = &pointer;        
        ffi_write_u8((&*pointer).get(), 7);
        let integer = ffi_read_u8((&**ref_pointer).get());
        assert_eq!(integer, 7);
    }
}

有趣的是,在*mut c_void*const UnsafeCell<c_void>之间进行转换是多么容易且符合人体工程学。