c_strange_t
是一个不透明的C类型,只能通过指针来访问。在封装此类型时,有时我们需要使用c_free_strange_t(*c_strange_t)
来释放内存,而其他时候,我们不需要负责释放数据,只需要准确控制生命周期。
如果可以将此类型映射为Rust中的2种类型,并以类似于str
和String
的方式工作,则会更加人性化,其中borrowed type需要标记为仅在引用后有效。
这是否可能,并且如何实现呢?
c_strange_t
是一个不透明的C类型,只能通过指针来访问。在封装此类型时,有时我们需要使用c_free_strange_t(*c_strange_t)
来释放内存,而其他时候,我们不需要负责释放数据,只需要准确控制生命周期。
如果可以将此类型映射为Rust中的2种类型,并以类似于str
和String
的方式工作,则会更加人性化,其中borrowed type需要标记为仅在引用后有效。
这是否可能,并且如何实现呢?
unsafe
块,因此您应该在像Miri和Valgrind这样的正常工具下进行测试。这里做出的主要假设1是c_void
不能正常构建。使用#[repr(transparent)]
确保FooBorrowed
新类型与c_void
具有相同的内存布局。一切都应该以“只是一个指针”的形式结束:use std::{ffi::c_void, mem, ops::Deref};
#[repr(transparent)]
struct FooBorrowed(c_void);
struct FooOwned(*mut c_void);
fn fake_foo_new(v: u8) -> *mut c_void {
println!("C new called");
Box::into_raw(Box::new(v)) as *mut c_void
}
fn fake_foo_free(p: *mut c_void) {
println!("C free called");
let p = p as *mut u8;
if !p.is_null() {
unsafe { Box::from_raw(p) };
}
}
fn fake_foo_value(p: *const c_void) -> u8 {
println!("C value called");
let p = p as *const u8;
unsafe {
p.as_ref().map_or(255, |p| *p)
}
}
impl FooBorrowed {
fn value(&self) -> u8 {
fake_foo_value(&self.0)
}
}
impl FooOwned {
fn new(v: u8) -> FooOwned {
FooOwned(fake_foo_new(v))
}
}
impl Deref for FooOwned {
type Target = FooBorrowed;
fn deref(&self) -> &Self::Target {
unsafe { mem::transmute(self.0) }
}
}
impl Drop for FooOwned {
fn drop(&mut self) {
fake_foo_free(self.0)
}
}
fn use_it(foo: &FooBorrowed) {
println!("{}", foo.value())
}
fn main() {
let f = FooOwned::new(42);
use_it(&f);
}
unsafe
操作:fn fake_foo_borrowed() -> *const c_void {
println!("C borrow called");
static VALUE_OWNED_ELSEWHERE: u8 = 99;
&VALUE_OWNED_ELSEWHERE as *const u8 as *const c_void
}
impl FooBorrowed {
unsafe fn new<'a>(p: *const c_void) -> &'a FooBorrowed {
mem::transmute(p)
}
}
fn main() {
let f2 = unsafe { FooBorrowed::new(fake_foo_borrowed()) };
use_it(f2);
}
正如您所指出的,FooBorrowed::new
返回一个具有不受限制的生命周期引用,这非常危险。在许多情况下,您可以构建更小的作用域,并使用提供生命周期的内容:
impl FooBorrowed {
unsafe fn new<'a>(p: &'a *const c_void) -> &'a FooBorrowed {
mem::transmute(*p)
}
}
fn main() {
let p = fake_foo_borrowed();
let f2 = unsafe { FooBorrowed::new(&p) };
use_it(f2);
}
这可以防止您在指针变量有效期之外使用引用,其真实生命周期不被保证,但在许多情况下“足够接近”。更重要的是要短而不是长!
1 — 在Rust的未来版本中,您应该使用extern类型创建保证的不透明类型:
extern "C" {
type my_opaque_t;
}