Search code examples
rustffirust-pin

Should I use Pin when making C++ call a Rust method through a pointer?


I have C++ code that calls Rust code with data. It knows which object to send the data to. Here's an example of the Rust function that C++ calls back:

extern "C" fn on_open_vpn_receive(
    instance: Box<OpenVpn>,
    data: *mut c_uchar,
    size: *mut size_t,
) -> u8 

It receives the pointer as a Box, so I created a function openvpn_set_rust_parent that sets which object the C++ must call back. This object is a pointer to itself. I'm using Pin so the Box is not reallocated to somewhere else, making C++ call an invalid address.

impl OpenVpn {
    pub fn new() -> Pin<Box<OpenVpn>> {
        let instance = unsafe { interface::openvpn_new(profile.as_ptr()) };
        let o = OpenVpn { instance: instance };
        let p = Box::pin(o);
        unsafe {
            interface::openvpn_set_rust_parent(o.instance, p.as_ptr());
        };
        p
    }
}

Signature:

pub fn openvpn_set_rust_parent(instance: *mut OpenVpnInstance, parent: *mut OpenVpn)

I don't know how to transform p into *mut OpenVpn to pass to C++. Is my idea ok? I think the usage of Pin is good here, and I think this is a good way of calling the object from C++.


Solution

  • It doesn't matter. Pin isn't a deeply magical type that forces your value to never move. Really, it boils down to strongly-worded documentation and some guide rails that prevents you from doing bad things within safe Rust code. Pin can be circumvented by unsafe code, which includes any FFI code.

    Having the Pin inside your Rust code might help you keep the Rust code accurate and valid, but it has nothing useful to add for the purposes of calling Rust from other languages.

    Pin is defined as repr(transparent), which means that you can use it in your FFI signature as long as the inner type is safe to use in FFI:

    #[stable(feature = "pin", since = "1.33.0")]
    #[lang = "pin"]
    #[fundamental]
    #[repr(transparent)]
    #[derive(Copy, Clone)]
    pub struct Pin<P> {
        pointer: P,
    }
    

    I'm using Pin so the Box is not reallocated to somewhere else, making C++ call an invalid address.

    Pin doesn't do this, Box does this. When you box something, you move the value to the heap. The Box itself is just a pointer. The address of the pointer will move around, but the address of the data in the heap will not.

    Note that the second address (0x55..30, on the heap) printed is the same, even though the Box itself has moved:

    fn main() {
        let a = 42;
    
        let b = Box::new(a);
        println!("{:p}", &b);  // 0x7ffe3598ea80
        println!("{:p}", &*b); // 0x556d21528b30
    
        let c = b;
        println!("{:p}", &c);  // 0x7ffe3598ea88
        println!("{:p}", &*c); // 0x556d21528b30
    }
    

    See also: