如何使用init / exit语义包装本机库

时间:2016-05-11 20:08:12

标签: rust

我在C库周围创建了一个包装器,它创建了一个必须明确关闭的设备。

编写原始FFI函数很简单,但如何在更高级别的包装器中使其符合人机工程学?

具体来说,我应该使用RAII样式并仅使用Drop来确保在超出范围时调用close,而不是将close()方法暴露给调用者吗?哪种方式最适合Rust?

基本上有3种选择:

  1. 需要与C库进行相同close()调用的精简包装器;
  2. RAII样式,没有公开close(),只有Drop实施;
  3. C#dispose() - 样式实现,跟踪关闭状态并允许两种形式的关闭。
  4. 最后一个表格如下:

    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();
        }
    }
    

1 个答案:

答案 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)
        },
    }
}