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

And*_*ren 5 rust

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

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

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

基本上有3种选择:

  1. 瘦包装器,需要与close()C库相同的调用;
  2. RAII风格没有暴露close(),只有一个Drop实现;
  3. C#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();
    }
}
Run Code Online (Sandbox Code Playgroud)

She*_*ter 4

按照惯例,我相信您只会实施Drop. 我不知道有任何标准库类型实现了允许用户手动(调用方法)和自动(通过删除)处置资源的模式。

这甚至导致了一些奇怪的情况。例如,通过类似的函数关闭文件fclose可能会产生错误。但是,Rust 析构函数无法向用户返回失败代码。这意味着类似的错误会被吞掉

这导致您可能想要同时支持两者。您的close方法可以返回 a Result,然后您可以忽略 中的结果Drop


正如Jsor 指出的那样,您可能希望您的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");
}
Run Code Online (Sandbox Code Playgroud)

如果关闭设备时可能发生某种可恢复的错误,您可以将对象返回给用户以重试:

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)
        },
    }
}
Run Code Online (Sandbox Code Playgroud)