Extern block uses type which is not FFI-safe

I have struct with private fields:

pub struct CoreTextHandle {
    font: CTFont,
    has_a: bool,
}

that I'm passing to an extern function (C) defined as follows (note the struct in question is passed as a pointer):

extern "C" {
    fn make_system_handle(
        log: MR_Word,
        family: *const c_char,
        style: *const c_char,
        weight: f64,
        slant: f64,
        stretch: f64,
        weight_class: MR_Integer,
        width_class: MR_Integer,
        font: *mut CoreTextHandle,
        fs0: MR_Word,
        fs: *mut MR_Word,
    );
}

and I'm getting a warning:

warning: `extern` block uses type `macos::CoreTextHandle` which is not FFI-safe: this struct has unspecified layout
  --> src/fonts/fontcode-rust/src/macos.rs:49:15
   |
49 |         font: *mut CoreTextHandle,
   |               ^^^^^^^^^^^^^^^^^^^
   |
   = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct

My expectation is that since I'm passing a pointer and not accessing the fields of the struct from C (I.e. it's an opaque pointer) the layout should not matter. The unsafe code guidelines back this up by saying, "Instead, one would typically pass #[repr(C)] structs (or possibly pointers to Rust-structs, if those structs are opaque on the other side, or the callee is defined in Rust)."

It's worth noting that I can't just add repr(C) because the font field comes from another crate and it is not repr(C).

Given I am passing a pointer to the struct and treat it as an opaque pointer on the C side I don't see why I'm getting the warning... anyone have any insights/advice?

Well, it depends, the compiler would have a point should you try to read some kind of interpretation on the other side of the boundary, as though its layout is not guaranteed, but if it's something that only rust is going to worry about (IE a callback uses it later) then it should be fine. Do take care to make sure its alignment is okay though.

You can if you're willing to fork the dependency crate that contains CTFont.
This fork doesn't necessarily have to be permanent.
You can submit a PR to the original project so that at a later date you can switch back from the fork to the original crate, once the PR has been merged.

Of course this would change the ABI of the dependency crate, so I think the chances of it being accepted are higher if you put the new code behind a build flag.

1 Like

The compiler doesn't know that you're treating it as an opaque pointer. If you cast it to a void pointer, the warning should go away.

2 Likes

If you have an opaque ptr, I think the most proper way to type it would be to use an opaque ptr in the C signature, and only map it to the ptr of the rust type in the safe (or unsafe) wrapper.

As I understand it, if your struct and all of its members aren't #[repr(C)], then I believe even the layout of a *mut CoreTextHandle is unstable / unspecified. In practice this won't happen for your struct, but if, for instance, CTFont was unsized, it would lead to CoreTextHandle being unsized, and thus to *mut CoreTextHandle being a double-wide fat ptr. You're 99% good with your current code, but I'm not really sure if that's actually guaranteed or it's just because ptrs to sized rust structs happen to have the same layout as C ptrs.

In any case, to get rid of the warning, I would recommend using *mut c_void in the extern function header. I assume since C doesn't know about CTFont, the C function has some similarly-opaque ptr, and this is correct anyways. Code wrapping the function can cast to/from *mut CoreTextHandle, and it should work equivalently to your current code.

1 Like

Just want to follow up to close this out. Based on the replies I initially decided to just leave my code as it was. As further work took place though I ended up needing to change the pointers in question to void pointers for unrelated reasons anyway. This got rid of the warnings as a side effect. Thanks all for the replies.

I recently opened an issue about this where it's being discussed if warnings about opaque pointers should possible be their own warning: https://github.com/rust-lang/rust/issues/66220

Ahh thanks for opening the issue. We're still getting this warning in another part of the code, again where the struct is passed by pointer. It would be good to have the lint be smarter or a more specific one that can be #[allowed].