2016-05-11 16 views
5

Tôi đã tạo một trình bao bọc quanh thư viện C tạo thiết bị mà bạn phải đóng một cách rõ ràng.Cách quấn thư viện gốc với ngữ nghĩa init/exit

Việc viết các hàm FFI thô dễ dàng, nhưng làm cách nào để làm cho nó hoạt động hiệu quả trong một trình bao bọc cấp cao hơn?

Cụ thể, tôi có nên sử dụng kiểu RAII và chỉ sử dụng Drop để đảm bảo đóng được gọi khi nó nằm ngoài phạm vi, thay vì hiển thị phương thức close() cho người gọi? Cách nào là thành ngữ nhất trong Rust?

Về cơ bản có 3 lựa chọn:

  1. wrapper mỏng đòi hỏi cùng close() cuộc gọi như thư viện C;
  2. Kiểu RAII không bị lộ close(), chỉ thực hiện Drop;
  3. C# dispose() cách triển khai theo dõi trạng thái đã đóng và cho phép cả hai hình thức đóng.

Hình thức cuối cùng trông như thế này:

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

Lựa chọn số 2. Tôi không bao giờ thấy tùy chọn 1 hoặc 3 được sử dụng trong gỉ. Trong ví dụ của bạn, điều gì sẽ xảy ra khi 'close' được gọi nhiều lần? – malbarbo

+0

Cũng được phát hiện, dĩ nhiên, nếu đóng cửa, bạn nên dùng phương thức close() chứ không phải trong drop(). Đã chỉnh sửa. –

Trả lời

4

Idiomatically, tôi tin rằng bạn sẽ chỉ thực hiện Drop. Tôi không biết về bất kỳ loại thư viện chuẩn nào thực hiện mẫu cho phép người dùng xử lý tài nguyên theo cách thủ công (gọi phương thức) và tự động (bằng cách thả).

Điều này thậm chí còn dẫn đến một số trường hợp lạ. Ví dụ, đóng một tập tin thông qua một chức năng như fclose có thể phát sinh lỗi. Tuy nhiên, một destructor Rust không thể trả về một mã lỗi cho người dùng. Điều này có nghĩa là errors like that are swallowed.

Điều này dẫn đến lý do bạn có thể muốn hỗ trợ cả hai. Phương thức close của bạn có thể trả lại Result và sau đó bạn có thể bỏ qua kết quả đó trong Drop.


Như Jsor points out, bạn có thể muốn phương pháp close của bạn để chấp nhận các loại theo giá trị. Tôi cũng nhận ra rằng bạn có thể sử dụng giá trị NULL để cho biết liệu giá trị đã được đóng hay chưa.

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

Nếu bạn đã có một số loại lỗi thu hồi có thể xảy ra khi đóng thiết bị, bạn có thể trả lại đối tượng lại cho người sử dụng để thử lại:

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) 
     }, 
    } 
} 
+1

Đối với trường hợp báo cáo lỗi, bạn vẫn có thể có nhiều ngữ nghĩa mộc mạc hơn bằng cách có 'fn close (self) -> Result' thay vì' fn close (& mut self) -> Result'. Khi 'Lỗi' trả về kết quả có thể chứa' self'so, bạn có thể thực hiện xử lý thêm. Vấn đề thực sự ở đây là sử dụng sau khi phát hành tài nguyên. – LinearZoetrope

+0

gần hơn sau. – LinearZoetrope

+0

@Jsor giống như cập nhật đó? – Shepmaster

Các vấn đề liên quan