关于rust:如何安全地创建UnsafeCell

How to create UnsafeCell safely?

UnsafeCell文档说

The UnsafeCell< T > type is the only legal way to obtain aliasable data that is considered mutable.

唯一的构造方法是:

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

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

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

或者这不是必需的吗?

一个用例是:

1
2
3
4
5
6
7
8
9
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 { ... }
}

现在可以使用*mut c_void了,即使我们知道某些FFI调用会改变它指向的数据并且有多个引用呢?我们如何创建FFIStruct?还是只使用*mut c_void可以吗?

创建&Cell<c_void>

的示例代码需要#![feature(as_cell)]

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


TL; DR:只需使用*mut Foo

免责声明:尚无正式的Rust内存模型。

您无法创建这种类型的句点,因为您无法创建

问题是,您不需要创建这种类型。混叠不是空间而是时间。您可以有多个*mut T指向同一个地方,直到您尝试访问一个地方都没有关系。这实际上将其转换为引用,并且在该引用存在时需要保持别名要求。

raw pointers fall outside of Rust's safe memory model.

— The Rustonomicon

Different from references and smart pointers, raw pointers:

  • Are allowed to ignore the borrowing rules by having both immutable and mutable pointers or multiple mutable pointers to the same location
  • Aren’t guaranteed to point to valid memory
  • Are allowed to be null
  • Don’t implement any automatic cleanup

?— The Rust Programming Language

另请参见:

  • 为什么修改可变变量通过原始指针引用值不违反Rusts别名规则?
  • Rust习惯用法定义一个指向C透明指针的字段是什么?
  • 使用Rust中的原始指针的帮助吗?
  • FFI函数可以修改未声明为可变的变量吗?

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


在Rust论坛中进行了一些内部讨论并讨论了RFC 1861之后,我意识到c_void只是一种常见的解决方法,并且存在其他选项,例如struct Opaque<UnsafeCell<()>>

所以我总结了这里需要是*const UnsafeCell<c_void>。根据其类型,我们知道:

  • 这是原始指针,因此适合立即发送到FFI;
  • 原始指针采用const,表示对*mut T将UB和程序员应该避免它。这样可以保护它在Rust中指向修改的内存(除非通过UnsafeCell引起);
  • 它包含UnsafeCell,因此暗示内部可变性。这证明FFI函数可以对其进行更改;
  • 无法像UnsafeCell<c_void>一样创建c_void。它们只能由FFI创建。

演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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>之间进行转换非常容易且符合人体工程学(至今仍具有表现力)。