c_strange_t
是一种不透明的C类型,只能在指针后面看到。在包装此类型时,有时我们有责任使用c_free_strange_t(*c_strange_t)
释放内存,而有时我们不负责释放数据,我们只负责准确控制生命周期。
如果此类型可以映射到Rust中的两种类型,其工作方式与str
和String
类似,那将是符合人体工程学的,其中impl Deref<Target=str> for String
。借用的类型需要标记为仅在引用后有效。
这是可能的,它将如何完成?
答案 0 :(得分:2)
这似乎有效,但确实需要使用小unsafe
块,因此您应该使用Miri和Valgrind等常规工具进行测试。这里 1 的主要假设是c_void
无法正常构造。 #[repr(transparent)]
用于确保FooBorrowed
newtype与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);
}
如果C库实际上给你一个指针,你需要再做一些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类型来创建保证的opaque类型:
extern "C" {
type my_opaque_t;
}