4

According to the answer this question of mine: How to hold Rust objects in Rust code created through C++? I can pass back to C something allocated in Rust inside a Box, and then receive it back again as a reference to &T, because Rust allocates Sized structs respecting the C ABI.

I want to do the same thing but now for Rc<RefCell<T>>. Should I return a Box to the Rc<RefCell<T>>? My guess it no since Rc does not implement Sized, which is required for T in Box<T> according to the Box page. So this would not work:

#[no_mangle]
pub extern "C" fn foo_new() -> Box<Rc<RefCell<T>>> {
    Box::new(Foo { glonk: false })
}

How can I make this work? Basically I need to create a Rust struct that can be accessed by many, and mutably borrowed by one of these many. That's why I choose Rc<RefCell<T>>. Is there perhaps another type of structure that can do what I want and be C-friendly?

PPP
  • 1,279
  • 1
  • 28
  • 71
  • 1
    `Rc` _does_ implement `Sized`. How did you get to the opposite conclusion? – E_net4 Jun 19 '20 at 09:50
  • @E_net4theclosevoter I couldn't find `impl Sized for Rc` on Rc's page on Rust site. So I should use `Box>>`? I can't use `Rc>` simply? I couldn't find if it has the same property as `Box` (that is, its memory layout is like C layout) – PPP Jun 19 '20 at 18:24

1 Answers1

7

Both smart pointers Rc<T> and Box<T> are indeed sized types, they implement Sized. This information is not presented in the documentation, probably because of the special nature of Sized: it is always automatically derived for all types where it's applicable, and that implementation cannot be added or lifted (unlike Send and Sync).

With that out of the way, there is little benefit in encapsulating another smart pointer into a Box. If there is an intention to move the actual smart pointer (Rc) across the C FFI boundary and back, there is only one thing to keep in mind: the layout of an Rc<T> is not compatible with a raw pointer, even when T is Sized (unlike Box<T>).

Therefore, we need to convert it to a raw pointer and back explicitly. The functions into_raw and from_raw are also available for Rc.

/// create a new Foo and give it to caller
#[no_mangle]
pub extern "C" fn foo_new() -> *const RefCell<Foo> {
    Rc::into_raw(Rc::new(RefCell::new(Foo { glonk: false })))
}

/// clone the pointers into two
#[no_mangle]
pub extern "C" fn foo_clone(foo: *const RefCell<Foo>) -> (*const RefCell<Foo>, *const RefCell<Foo>) {
    unsafe {
        let ptr = Rc::from_raw(foo);
        let c = Rc::clone(ptr);
        (ptr, c)
    }
}

/// get a property of a Foo without returning ownership
#[no_mangle]
pub extern "C" fn foo_glonk(foo: *const RefCell<Foo>) -> bool {
    unsafe { (*foo).borrow().glonk }
}

/// return ownership of the Foo,
/// so it can be freed in Rust-land
#[no_mangle]
pub extern "C" fn foo_return(foo: *const RefCell<Foo>)  {
    let _ = Rc::from_raw(foo);
}
E_net4
  • 27,810
  • 13
  • 101
  • 139