How to make C++ call Rust object's function for non-opaque Rust struct?

Suppose I have a struct

struct MyStruct{
    pub field: u8
}

impl MyStruct{
    pub fn do_something(&mut self, value: u8) {
         self.field += value;
    }
}

#[no_mangle]
pub extern "C" fn do_something_callback(
    my_struct: ________,
    value: u8,
) 

fn main() {
    let my_struct = MyStruct{field: 0};
    let cpp_object = create_cpp_object();
    //tells C++ object to call my_struct when it has data
    set_callback_in_cpp(cpp_object, my_struct);
}

and then I want to call C++ from Rust and specify that C++ calls a callback on Rust that calls a 'method' on MyStruct.

This is not like this approach: https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs where I create an opaque struct to represent the Rust object. In this case it's a non opaque object.

How can do_something_callback's signature be so C++ can call it back on an instance of MyStruct?

So, if C / C++ knows the struct / it is not opaque, it needs to be #[repr(C)]:

#[repr(C)]
pub
struct MyStruct {
    pub field: u8
}
  • // In C / C++:
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    typedef struct {
        uint8_t field;
    } MyStruct;
    
    typedef struct cpp_object cpp_object_t; // Opaque C++ object
    
    cpp_object_t * create_cpp_object();
    
    typedef void (*callback_t)(MyStruct *, uint8_t);
    
    void set_callback_in_cpp (
        cpp_object_t *,
        MyStruct,
        callback_t);
    
    #ifdef __cplusplus
    } // extern "C"
    #endif
    
    • I am inferring / guessing what your use case is, you haven't been very clear about it.

Then, what the Rust code ought to do, is to define an extern "C" function with a signature matching that of callback_t:

type callback_t = Option<
    unsafe extern "C"
    fn(*mut MyStruct, u8)
>;

extern "C" {
    type cpp_object_t;

    fn create_cpp_object ()
      -> *mut cpp_object_t
    ;

    fn set_callback_in_cpp (
        _: *mut cpp_object_t,
        _: MyStruct,
        _: callback_t,
    );
}

unsafe extern "C"
fn my_struct_call_do_something (
    p_struct: *mut MyStruct,
    x: u8,
)
{
    ::scopeguard::defer_on_unwind!({ // Unwinding is UB on an `extern "C"` boundary
        ::std::process::abort();
    });
    let s: &mut MyStruct = p_struct.as_mut().expect("Got NULL");
    s.do_something(x);
}

fn main ()
{
    let my_struct = MyStruct { field: 0 };
    let cpp_object = unsafe { create_cpp_object() };
    // tells C++ object to call my_struct when it has data
    unsafe {
        set_callback_in_cpp(
            cpp_object,
            my_struct,
            Some(my_struct_call_do_something),
        );
    }
}
  • w.r.t.

    extern "C" {
        type cpp_object_t;
    }
    

    this cannot yet be done on stable Rust, so the current polyfill / equivalent is to do:

    mod opaque {
        pub
        struct cpp_object_t {
            _opaque_unsend_unsync: ::core::marker::PhantomData<*mut u8>,
        }
    }
    use opaque::cpp_object_t;
    
2 Likes

what if MyStruct has types not C friendly? Like Vec<String>?

Then you use the opaque object pattern, by Box-ing your object on the Rust side:

  // In C / C++:
  #ifdef __cplusplus
  extern "C" {
  #endif

+ typedef struct VecString VecString_t; // opaque object
  typedef struct {
-     uint8_t field;
+     VecString_t * vec_string; // pointer to it.
  } MyStruct;

and in Rust:

  #[repr(C)]
  pub
  struct MyStruct {
-     pub field: u8,
+     /// A type with the same ABI as a pointer, such as `Option<Box< _ >>`
+     pub vec_string: Option<Box< Vec<String> >>,
  }

you care about representing the struct in the C++ side, but it's not necessary for me. In that case, can't I use a struct normally in Rust with any members and then make the C++ code call a Box<MyStruct>? I just care about MyStruct on the Rust side, I only need to pass to C++ a reference/pointer to it so Rust knows for who to deliver the call when C++ calls

That's just what an opaque type is.

1 Like

I dont get it, he uses

typedef struct {
    uint8_t field;
} MyStruct;

Which isn't opaque in the C++ side.

How can I make it opaque in C++ so MyStruct in Rust does not need to be C representable?

You originally said you wanted a non-opaque type. The opaque type in @Yandros's most recent post is struct VecString, aka VecString_t. MyStruct is still non-opaque.

It does not seem to me entirely clear why you need to wrap *mut Vec<String> in a repr(C) struct. Technically the C standard allows pointers to different types to have different sizes and layouts, but that is true not just in function parameters but also inside structs, so the idea that wrapping a(n apparently) non-FFI-safe pointer in a repr(C) struct makes it FFI-safe seems wrong to me. Perhaps I am missing something...

The starting point of this thread, from where I've derived my examples, was using a MyStruct type by value, which is necessarily an FFI-safe / non-opaque type. Hence my first suggestion.

After the mention of there being non-FFI-friendly types, such as Vec<String>, I've shown how with (slim) pointer indirection one can make anything FFI-safe, and use that as the field of a well-defined struct: it's not because one part is opaque that everything must be opaque.

Obviously, using MyStruct as a defined new type is not mandatory here.

For the sake of the example, one could also do typedef VecString_t * MyStruct;, i.e., replace every occurence of MyStruct in the previous example by a VecString_t *.

You can even just use void * everywhere in the FFI API, if you want to sacrifice type-safety in exchange of having a fixed / Rust-agnostic header.

This was also my original feeling, but the nomicon appears to contradict it (emphasis mine):

By including a private field and no constructor, we create an opaque type that we can't instantiate outside of this module. (A struct with no field could be instantiated by anyone.) We also want to use this type in FFI, so we have to add #[repr(C)]. And to avoid warning around using () in FFI, we instead use an empty array, which works just as well as an empty type but is FFI-compatible.

I.e. even though the type is opaque and not exotically sized, #[repr(C)] is still required, even just to use it by pointer.

Maybe the Rustonomicon is wrong here?

Let's say the nomicon phrases stuff in a slightly ambiguous manner: slim pointers are guaranteed to "be FFI-safe", if:

  • either the pointee is FFI-safe too (in which case the pointer may be dereferenced by the FFI side, provided non-nullness, alignment, and other safety invariants of the actual pointer instance are met).

  • or the pointee is opaque FFI-wise: indeed, in that case the pointer is not really one, it's kind of a usize-like handle that is used by feeding it into other FFI-exported functions.


In practice, the improper_ctypes lint has historically been flawed with both false positives and false negatives, which is the reason I decided to come up with safer-ffi and a trait-based design of "being FFI-safe"1, which supports the opaque object pattern.

1 I need to rephrase my own rustdoc docs so that it is clear CType can be implemented by opaque types OPAQUE_KIND = OpaqueKind::Opaque).