Why C struct with [f32; 2] is ABI safe, while [f32; 2] by itself isn't?

In this example the compiler will only warn about call_b. Why? Switching from repr(C) to repr(transparent) doesn't generate the warning either.

#[repr(C)]
pub struct A {
    x: [f32; 2],
}

pub extern "C" fn call_a() -> A {
    todo!()
}

pub extern "C" fn call_b() -> [f32; 2] {
    todo!()
}

Another question I have is what should I worry about in such cases? I have some C++ structs using arrays (e.g. float x[2]) inside. Can I corrupt the data if I use such structs on Rust side (with repr(C)) and pass them back to C++?

But there aren't any default representation structs in the question.

I think the issue here is that a C array type “decays to a pointer” when passed to a function (in Rust terms, [f32; 2] is auto-referenced), but C struct types do not get that treatment and are passed by value (like all Rust types), whether or not they contain arrays as fields, and thus are less surprising.

5 Likes

Oh, I misunderstood. (Deleted)

Thank you! This mostly answers my questions. The only thing I am still not sure about is repr(transparent), shouldn't it generate the warning as well?

More generally, improper_ctypes is not very thorough or precise. Consider it advice when it fires, not a full validation of your design.

1 Like

The problem is on C side.

Because of strange (and stupid, if you'll ask me) quirk of history C function couldn't receive array or return array. Test the following program:

typedef int Array[2];

void foo(Array a) {
}

void foo(int* a) {
}

Array bar() {
}

Compiler complains that bar returns array which is flat out forbidden while foo, that tries to receive it, receives pointer, instead (look for “function is duplicated” error message).

But it's perfectly legal to embed array and pass it as part of structure in C!

And yes, #[repr(transparent)] is Ok, too.

1 Like

It may not be very precise or thorough, but in this particular case it's 100% correct. It's legal to put array in a C struct and pass that in or out of C function.

Yes, but in that case you would use a repr(C) struct to match C.

  • improper_ctypes is not about “can something like this be done in C at all”, it's about “probable mistakes”. I understand this to mean: places where what C expects will surprise a Rust programmer. (This is not an objective criterion!)
  • repr(transparent) means “Structs and enums with this representation have the same layout and ABI as the single non-zero sized field.” There is no C equivalent of that; it's impossible to write a C struct type that has the ABI of a C array type.

So, according to my understanding of the intent of improper_ctypes — and the issue I linked above — this case perhaps should be warned about. And, per the issue I linked above, this is one of the cases that haven't yet been nailed down as definitely-warn or definitely-not-warn by the Rust maintainers, but at least one member thinks that it should warn.

I think that — and the subjectivity of “probable mistakes” — is quite enough to tell users not to rely on it firing or not firing as a source of truth.

1 Like

Yes, but if you put C array into C struct you get the exact same layout as Rust type has. Only that one may actually be passed into an C function and returned from C function.

Why? It's perfectly possible to safely pass such object into C. And C++ even have std::array which is defined to match precisely that type (note: this container is an aggregate type with the same semantics as a struct holding a C-style array T[N] as its only non-static data member).

Why? What's the point of such warning would be?

Is this somehow guaranteed that for one-field struct [repr(C)] and [repr(transparent)] are the same? I guess this is a sensible thing to happen, but is it guaranteed? The former is obviously correct by definition, problem is the latter.

The pointer arithmetic on array elements seems to be guaranteed and transparent struct with one member is supposed to have the exact same layout as what's inside, so where may problem come from?

What does [repr(transparent)] even mean if not that?

It has the same layout as the array, yes. But is it guaranteed that this -

#[repr(C)]
struct Wrapper([f32; 2])

...has the same layout, too?

I don't see how can that be satisfied otherwise:

Pointer to a struct can be cast to a pointer to its first member (or, if the member is a bit-field, to its allocation unit). Likewise, a pointer to the first member of a struct can be cast to a pointer to the enclosing struct. There may be unnamed padding between any two members of a struct or after the last member, but not before the first member.

Layout of single-member struct is pretty much restricted to what people expect in C via that rule.

And there are lots of C code that depends on this rule, too! Most often various OOP-wannabe schemed like Glib and other such things. std::array, too.

1 Like