Wrappers for Object Oriented FFI

Hello, I am writing wrappers for a native C API which contains various Create and Destroy functions. These "resources" are exposed via i32 handles upon which various methods operate. As a consumer of these APIs, I cannot be sure, but I suspect, that they mutate the state of these "resources." As a newcomer to Rust, I am wondering...

  1. When should I mark the parameters to a safe function that wraps an unsafe function as mut?
  2. Is RefCell the only option when a wrapper object retains a reference to an underlying resource?

For more context, please see the below code snippet.

use std::cell::RefCell;

fn main() {
    let resource_a = RefCell::new(ResourceA::new());
    let resource_b = RefCell::new(ResourceB::new(&resource_a));
    resource_a.borrow_mut().do_something(0, 1);
    resource_b.borrow_mut().do_something(2, 3, 4);
}

// FFI Functions

pub unsafe extern "system" fn destroy_resource_a(handle_a: i32) {
    println!("destroy_resource_a({})", handle_a);
}

pub unsafe extern "system" fn do_something_a(handle_a: i32, x: i32, y: i32) {
    println!("do_something_a({}, {}, {})", handle_a, x, y);
}

pub unsafe extern "system" fn initialize_resource_a() -> i32 {
    let handle_a = rand::random::<i32>();
    println!("initialize_resource_a() -> {}", handle_a);
    handle_a
}

pub unsafe extern "system" fn destroy_resource_b(handle_a: i32, handle_b: i32) {
    println!("destroy_resource_b({}, {})", handle_a, handle_b);
}

pub unsafe extern "system" fn do_something_b(handle_b: i32, x: i32, y: i32, z: i32) {
    println!("do_something_b({}, {}, {}, {})", handle_b, x, y, z);
}

pub unsafe extern "system" fn initialize_resource_b(handle_a: i32) -> i32 {
    let handle_b = rand::random::<i32>();
    println!("initialize_resource_b({}) -> {}", handle_a, handle_b);
    handle_b
}

// Wrappers

struct ResourceA {
    handle_a: i32,
}

impl ResourceA {
    pub fn do_something(&mut self, x: i32, y: i32) {
        unsafe {
            do_something_a(self.handle(), x, y)
        }
    }

    pub unsafe fn handle(&self) -> i32 {
        self.handle_a
    }

    pub fn new() -> ResourceA {
        let handle_a = unsafe { initialize_resource_a() };

        ResourceA {
            handle_a
        }
    }
}

impl Drop for ResourceA {
    fn drop(&mut self) {
        unsafe {
            destroy_resource_a(self.handle_a);
        }
    }
}

struct ResourceB<'a> {
    resource_a: &'a RefCell<ResourceA>,
    handle_b: i32,
}

impl<'a> ResourceB<'a> {
    pub fn do_something(&mut self, x: i32, y: i32, z: i32) {
        unsafe {
            do_something_b(self.handle(), x, y, z)
        }
    }

    pub unsafe fn handle(&self) -> i32 {
        self.handle_b
    }

    pub fn new(resource_a: &RefCell<ResourceA>) -> ResourceB {
        let handle_a = unsafe { resource_a.borrow().handle() };
        let handle_b = unsafe { initialize_resource_b(handle_a) };

        ResourceB {
            resource_a,
            handle_b,
        }
    }
}

impl<'a> Drop for ResourceB<'a> {
    fn drop(&mut self) {
        unsafe {
            destroy_resource_b(self.resource_a.borrow().handle(), self.handle_b);
        }
    }
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.