【问题标题】:How to wrap a native library with init/exit semantics如何使用 init/exit 语义包装本机库
【发布时间】:2016-05-11 20:08:11
【问题描述】:

我对一个 C 库做了一个封装,它创建了一个必须明确关闭的设备。

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

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

基本上有3个选项:

  1. 需要与 C 库相同的 close() 调用的瘦包装器;
  2. 没有暴露的close(),只有Drop 实现的RAII 样式;
  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();
    }
}

【问题讨论】:

  • 选项编号 2。我从未见过选项 1 或 3 用于 rust。在您的示例中,当close 被多次调用时会发生什么?
  • 很好看,如果关闭当然应该在 close() 方法中,而不是在 drop() 中。已编辑。

标签: rust


【解决方案1】:

按照惯用语,我相信您只需实现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)
        },
    }
}

【讨论】:

  • 对于错误报告的情况,你仍然可以通过使用fn close(self) -&gt; Result 而不是fn close(&amp;mut self) -&gt; Result 来获得更多的 Rustic 语义。结果返回的Error 可能包含self,因此您可以进行额外处理。这里真正的问题是在资源发布后使用。
  • 更接近后者。
猜你喜欢
  • 2016-03-06
  • 2017-04-13
  • 1970-01-01
  • 2014-08-29
  • 1970-01-01
  • 2020-09-06
  • 2014-08-16
  • 2019-12-14
  • 2014-08-26
相关资源
最近更新 更多