我在C库周围创建了一个包装器,它创建了一个必须明确关闭的设备。
编写原始FFI函数很简单,但如何在更高级别的包装器中使其符合人机工程学?
具体来说,我应该使用RAII样式并仅使用Drop
来确保在超出范围时调用close,而不是将close()
方法暴露给调用者吗?哪种方式最适合Rust?
基本上有3种选择:
close()
调用的精简包装器; close()
,只有Drop
实施; dispose()
- 样式实现,跟踪关闭状态并允许两种形式的关闭。最后一个表格如下:
pub enum NativeDevice {} // Opaque pointer to C struct
fn ffi_open_native_device() -> *mut NativeDevice { unimplemented!() }
fn ffi_close_native_device(_: *mut NativeDevice) {}
fn ffi_foo(_: *mut NativeDevice, _: u32) -> u32 { unimplemented!() }
pub struct Device {
native_device: *mut NativeDevice,
closed: bool,
}
impl Device {
pub fn new() -> Device {
Device {
native_device: ffi_open_native_device(),
closed: false,
}
}
pub fn foo(&self, arg: u32) -> u32 {
ffi_foo(self.native_device, arg)
}
pub fn close(&mut self) {
if !self.closed {
ffi_close_native_device(self.native_device);
self.closed = true;
}
}
}
impl Drop for Device {
fn drop(&mut self) {
self.close();
}
}
答案 0 :(得分:4)
惯用法,我相信你只会实施Drop
。我不知道任何标准库类型实现允许用户手动(调用方法)和自动(通过删除)处理资源的模式。
这甚至会导致一些奇怪的情况。例如,通过fclose
之类的函数关闭文件会产生错误。但是,Rust析构函数无法向用户返回失败代码。这意味着errors like that are swallowed。
这导致可能想要支持两者的原因。您的close
方法可能会返回Result
,然后您可以在Drop
中忽略该结果。
作为Jsor points out,您可能希望close
方法按值接受类型。我还意识到你可以使用NULL
值来表明该值是否已经关闭。
use std::ptr;
enum NativeDevice {} // Opaque pointer to C struct
fn ffi_open_native_device() -> *mut NativeDevice {
0x1 as *mut NativeDevice
}
fn ffi_close_native_device(_: *mut NativeDevice) -> u8 {
println!("Close was called");
0
}
struct Device {
native_device: *mut NativeDevice,
}
impl Device {
fn new() -> Device {
let dev = ffi_open_native_device();
assert!(!dev.is_null());
Device {
native_device: dev,
}
}
fn close(mut self) -> Result<(), &'static str> {
if self.native_device.is_null() { return Ok(()) }
let result = ffi_close_native_device(self.native_device);
self.native_device = ptr::null_mut();
// Important to indicate that the device has already been cleaned up
match result {
0 => Ok(()),
_ => Err("Something wen't boom"),
}
}
}
impl Drop for Device {
fn drop(&mut self) {
if self.native_device.is_null() { return }
let _ = ffi_close_native_device(self.native_device);
// Ignoring failure to close here!
}
}
fn main() {
let _implicit = Device::new();
let explicit = Device::new();
explicit.close().expect("Couldn't close it");
}
如果关闭设备时可能会出现某种可恢复的错误,可以将对象返回给用户再试一次:
enum Error {
RecoverableError(Device),
UnknownError,
}
fn close(mut self) -> Result<(), Error> {
if self.native_device.is_null() {
return Ok(());
}
let result = ffi_close_native_device(self.native_device);
match result {
0 => {
self.native_device = ptr::null_mut();
// Important to indicate that the device has already been cleaned up
Ok(())
},
1 => Err(Error::RecoverableError(self)),
_ => {
self.native_device = ptr::null_mut();
// Important to indicate that the device has already been cleaned up
Err(Error::UnknownError)
},
}
}