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)
} |
- 您实际上是在代码中访问v还是不透明?如果它不透明,则不需要所有这些。
- 它是不透明的,只有FFI实现可以触摸它。但是,您可以更具体地说什么是不必要的吗?您是指使用UnsafeCell吗?或*mut c_void(我认为这是必需的...)
- UnsafeCell通知Rust编译器一个值可能随时更改,即使编译器可以看到对其的共享引用。这对于编译器要使用该值时非常重要,这样它就知道它不能仅将其保存在寄存器中。但是,如果编译器从未接触过该字段,因为它仅被FFI函数所接触,则注释不是必需的。
- 当然,您不能知道空隙的大小是多少?
- @Stargateur UnsafeCell不需要Sized,因此我们可以在其中放入DST ...
- 我对Rust中的这些概念不是很了解,但是我认为,不管Rust的任何魔术功能如何,都不可能创造出可以保持空白的东西。在C中整个无效点。您不能写void a;,只能有void *a;。这是有道理的,因为虚无是……什么都没有。您不能对*a做任何事情,因为它是空类型。有人必须知道使用它的真正类型是什么。所以我不知道Rust将如何做到这一点。
- 动态大小类型(DST)与未大小/零大小类型不同。
- void不允许作为C中的值,那么为什么您期望Rust中有一个值?
- 我认为您想要一个UnsafeCell<*mut c_void>。指向未知类型数据的可变指针,该指针可能在存在指针时被其他代码突变。
- @Shepmaster如果c_void完全无人居住,则类似于UnsafeCell<!>,但实际上,尽管c_void无人居住,但*mut c_void却不是:它将指向一个不透明的值。我期望表达的事实是,即使我们拥有该对象的共享引用,该不透明值也可以修改/更改。
- @Phoenix虽然语义很不一样。 UnsafeCell<*mut c_void>包含其他抽象:它是指向指针的指针。这也意味着在没有unsafe引用的情况下,get方法无法准备好发送到FFI。
- 指向指针的指针-为什么您相信呢?我认为这可能是您问题的根源。
- @Shepmaster,因为我们只能从UnsafeCell获取*mut T,这使其像指针一样工作。它支持DST的事实也暗示它是一个间接的(意味着指针,也许我在这里错了?)。因此,如果我们使用指针作为包含的值,它将是指向指针的指针。
- @EarthEngine,如果您可以将问题编辑为与您要解决的实际高级问题有关,则将很有帮助。这里发生的事情是,您想要部分走入错误的兔子洞,陷入困境,并提出一个非常具体的问题,而这个问题已经做出了错误的假设。因此,我建议您退后几步,重点关注要实现的目标:要包装哪种API,提供哪种操作,在C中用户可见类型是什么样的,您希望用户可见的类型在Rust中看起来像吗?
- 但是要从字面上回答您的问题,请记住,UnsafeCell不是指针,因此看来您想要的是*mut UnsafeCell<c_void>,您可以通过简单的强制转换从*mut c_void获得。那就算安全了。它根本不是您想要的。
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从技术上讲您可以,但这仅是由于实现和向后兼容的限制。
- 这就是我的想法。但是,UnsafeCell被称为"内部可变性的根",并且此示例似乎暗示*mut T是另一个根。我对吗? (也许我应该再提一个问题...)
- @EarthEngine内部可变性意味着您可以仅对共享引用(&T)进行更改。原始指针不允许您这样做。通过*mut T进行某些更改并不是内部可变性,那就是常规可变性。
- 我知道。但是,当您查看我的FFIStruct示例时,我想掩盖一个事实,即self.v可以被更改,而仅对其持有共享引用&self。这是内部可变性吗?另外,请检查我的&Cell<c_void>示例。
- @EarthEngine self.v不能通过FFI代码进行更改,因为指针本身是作为不可变的引用(通过&self)访问的。可以更改self.v指向的值,因为它位于原始指针之后。
- @Shepmaster只是一个简单的问题:FFIStruct是否具有内部可变性?指针后面的多少级别无关紧要:从概念上讲,它们全归结构所拥有。
- @EarthEngine老实说我无法回答这个问题-我不知道^ _ ^
- 在您创建对某些数据的共享引用之前,@ EarthEngine内部可变性对您而言并不重要。我的意思是直接指向该数据,而不是通过原始指针传递。因此,我认为在此讨论中对UnsafeCell和内部可变性的每一个提及都是红色的鲱鱼。您永远都没有共享的引用。
在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>之间进行转换非常容易且符合人体工程学(至今仍具有表现力)。
- 我不认为这是您想要的类型。恐怕您的问题已经有些不适了,这就是为什么答案也变得混乱的原因。关键是,UnsafeCell仅与共享引用相关。如果您执行FFI并且一切都在ptr间接之后,那么您不必担心UnsafeCell。只需使用*mut并将其包装为新类型即可。 nomicon有一些进一步的信息。