Question about C ABI stability

I have a blah-dll crate that gets compiled to a C DLL. It defines an auto-generated public API like this:

// original rust code
enum MyEnum<T> {
    A,
    B,
    C(T),
}

struct MyType { 
    t: usize 
}
// blah-dll/lib.rs
#[no_mangle] #[repr(C)] pub struct MyEnumMyTypeWrapper  { 
    pub object: MyEnum<MyType> 
}

#[no_mangle] #[repr(C)] pub extern "C" fn my_enum_c(value: usize) -> MyEnumMyTypeWrapper  { 
    MyEnumMyTypeWrapper { object: MyEnum::C(value) } 
}

Now I have a DLL file plus a generated wrapper with all the public function pointer definitions and I want to call my_enum_c on that generated DLL:

// auto-generated bindings to blah.dll
struct BlahDll {
    lib: Box<Library>,
    // compiler wants to know where MyEnumMyTypeWrapper is defined
    my_enum_c: Symbol<unsafe extern fn(usize) -> MyEnumMyTypeWrapper>,
}

fn main() {
    let lib = load_library("blah-dll.so")?;
    let value = lib.my_enum_c(5);
}

This doesn't compile because the compiler wants to know the struct definition of MyEnumMyType. My question is this: The Rust layout isn't stable, but if I generate the exact same layout of MyEnumMyType again with a #[repr(C)] attribute, it should be stable, shouldn't it?

#[repr(C)] 
pub struct MyEnumMyTypeWrapperA { 
    pub object: MyEnum<MyType> 
}

and:

enum MyEnumMyType { A, B, C(MyType) }   // no repr(C)

#[repr(C)] 
pub struct MyEnumMyTypeWrapperB { 
    pub object: MyEnumMyType 
}

Is the binary layout of MyEnumMyTypeWrapperA equal to the binary layout of MyEnumMyTypeWrapperB? Thanks in advance for any help.

Nope. You would have to use a repr attribute in order to get ABI stability guarantees for the enum. Or heap allocate it and store it behind a raw pointer.

Okay, thanks, but if I do this:

#[repr(C)] 
enum MyEnum<T> {
    A,
    B,
    C(T),
}

#[repr(C)] 
struct MyType { 
    t: usize 
}

#[repr(C)] 
pub struct MyEnumMyTypeWrapperA { 
    pub object: MyEnum<MyType> 
}

and:

#[repr(C)]
enum MyEnumMyType { A, B, C(MyType) }

#[repr(C)] 
pub struct MyEnumMyTypeWrapperB { 
    pub object: MyEnumMyType 
}

... would this be stable? I need to do this because I can't export generics across a C boundary, so I have to generate duplicate MyEnums for every T.

Second, could there be a lint / warning in rustc that a type that is marked as repr(C) also needs the sub-fields to be repr(C) to be ABI stable? I.e. basically a compiler pass that iterates through all structs marked with #[repr(C)], then looks up the types of the fields and warns if the types aren't also marked as #[repr(C)].

I believe this is should be covered by the existing ffi-safe lint. (Sadly, that lint seems a lot less potent than I thought it was, as it doesn't warm for even the simple case of returning something #[repr(Rust)] from an extern "C" fn.)

Well, I didn't realize there was an FFI lint, because it doesn't work: Compiling the code above doesn't emit a warning.

For the record: yes, this should be stable. But I also don't see a reason you can't use generics in this scenario, though I don't understand your full use case.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.