# C-style enums in FFI (and a proposal to lump in with unions)

**URL:** https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744
**Category:** internals
**Created:** [October 7, 2015, 4:30pm UTC](https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744 "2015-10-07T16:30:00Z")
**Posts on this page:** 13
**Page:** 1

<div class="post-metadata">

### Author: ![mzabaluev](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mzabaluev/32/3082_2.png) [@mzabaluev](https://internals.rust-lang.org/u/mzabaluev)
#### Post date: [October 7, 2015, 4:30pm UTC](https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744/1 "2015-10-07T16:30:00Z")

</div>

I’ve ran into an issue with FFI that I believe requires clarification, or maybe some additions to the language. Many C APIs have functions with output value types defined as C enumerations. In Rust FFI, these are “naturally” represented by `#[repr(C)]` enums with C-style integer members. This is all good while we receive values that fit our FFI definition. But if the foreign library grows some new enumeration members and is used without changing the Rust-side definition, attempting to match an unknown value of a Rust enum brings undefined behavior.

In the C world it’s normal to update dynamic libraries and expect binaries built against older versions of those libraries to work as long as binary compatibility is preserved, and adding new enum members is not normally considered an API or ABI break (the standard says enums can be backed by implementation-defined integer types, but in practice they are int-sized unless overridden by compiler-specific means). The soname of GLib has been kept the same for a dozen years, and recent releases of the library probably still work with GNOME 2.0 binaries. I think binaries built from Rust sources should be kept to the same expectations.

Some C APIs are pushing it further by defining bit flags with enums and then using the enum types directly in function signatures and structures. It’s apparently legal in C, where enums are full integral types with some value constants sprinkled over them as a vague hint on the expected domain. This brings issues on the input side as well: input parameters and struct members should be declared in FFI with their corresponding enum types for repr compatibility, but the actual values are composed from bit flags and so need to be transmuted.

So how can we deal with that heathen land of C? Can we rely on a tacit assumption that the actual underlying integer value can be received intact by transmuting the enum value into an appropriately repr-sized int, and vice versa? In the long term, would it be worthwhile to add more ergonomic means for passing “vaguely enum” values through FFI?

There is a long-standing [Rust issue](https://github.com/rust-lang/rust/issues/5492) on the lack of support for C unions. I think that is a very similar problem to the one discussed here, and as such both can be solved by adding a single new Rust type: `unsafe enum` as described in the discussion on rust-lang/rust#5492. C-style unsafe enums would be used to unsafely match C enum values, while the struct-membered variety would stand in for unions. What do you think?

---

<div class="post-metadata">

### Author: ![retep998](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/retep998/32/729_2.png) [@retep998](https://internals.rust-lang.org/u/retep998)
#### Post date: [October 7, 2015, 6:45pm UTC](https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744/2 "2015-10-07T18:45:16Z")

</div>

Currently I get around the issue of the lack of C unions, C bitflags, C enums, and even C bitfields by using my own custom macros in winapi.

> <https://github.com/retep998/winapi-rs/blob/master/src/macros.rs>

Also note that `#[repr(C)]` for enums is wrong since it will try to fit the discriminant in a `usize` or `isize` while on Windows enums are always 32-bit

---

<div class="post-metadata">

### Author: ![mzabaluev](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mzabaluev/32/3082_2.png) [@mzabaluev](https://internals.rust-lang.org/u/mzabaluev)
#### Post date: [October 7, 2015, 10:33pm UTC](https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744/3 "2015-10-07T22:33:03Z")

</div>

> [@retep998](#):
>
> Also note that `#[repr(C)]` for enums is wrong since it will try to fit the discriminant in a `usize` or `isize` while on Windows enums are always 32-bit

Ah, but there is `#[repr(i32)]`.

On the other hand, if `#[repr(C)]` on `x86_64-pc-windows-msvc` ends up being something completely different than what the C compiler uses by default, there is a problem.

---

<div class="post-metadata">

### Author: ![huon](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/huon/32/3698_2.png) [@huon](https://internals.rust-lang.org/u/huon)
#### Post date: [October 7, 2015, 11:16pm UTC](https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744/4 "2015-10-07T23:16:50Z")

</div>

> [@retep998](#):
>
> Also note that #[repr(C)] for enums is wrong since it will try to fit the discriminant in a usize or isize while on Windows enums are always 32-bit

Could you clarify? IME `repr(C)` doesn't use `usize`/`isize`, at least, not on Linux. The following code prints `8 4` on the playground:

```rust
use std::mem;

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

fn main() {
    println!("{} {}", mem::size_of::<usize>(), mem::size_of::<C>());
}

```

---

<div class="post-metadata">

### Author: ![retep998](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/retep998/32/729_2.png) [@retep998](https://internals.rust-lang.org/u/retep998)
#### Post date: [October 8, 2015, 12:22am UTC](https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744/5 "2015-10-08T00:22:42Z")

</div>

```rust
#[repr(C)]
enum C {
    A = -1,
    B = 0x80000000,
}

```

That prints a size of 8. Enums in Windows API will sometimes have shenanigans with either negative discriminants or discriminants that are greater than the range provided by an i32. In C/C++ land it is always 32-bit, and if you try to put something greater than a u32 can provide it errors, but in Rust it just increases the size and compiles fine.

---

<div class="post-metadata">

### Author: ![mzabaluev](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mzabaluev/32/3082_2.png) [@mzabaluev](https://internals.rust-lang.org/u/mzabaluev)
#### Post date: [October 8, 2015, 6:23am UTC](https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744/6 "2015-10-08T06:23:46Z")

</div>

> [@retep998](#):
>
> In C/C++ land it is always 32-bit

By the standard, one should not expect to be able to use discriminants that cannot be represented by `int`. So I guess what to do with discriminants that fit in `unsigned int`, but not `int`, is implementation-dependent.

What does MSVC make out of:

```c
#include <stdio.h>

typedef enum {
    A = 0x80000000,
    B = -0x80000000
} C;

int main() {
    C val = A;
    printf("sizeof: %u\n", (unsigned)sizeof(val));
    printf("it %s wrap around!\n", (val == B)? "does" : "does not");
    return 0;
}

```

With GCC on `x86_64-redhat-linux`, it does wrap around. Curiously, it takes option `-Wpedantic` for the compiler to start complaining; even `-Wall -Wextra -std=c11` will not result in any warning. Yeah, we all know `-Wall` is a lie...

> [@retep998](#):
>
> In C/C++ land it is always 32-bit, and if you try to put something greater than a u32 can provide it errors, but in Rust it just increases the size and compiles fine.

There is an issue here, indeed. Rust should be compatible, quirk-for-quirk, with the behavior of the de-facto main C compiler on the target.

---

<div class="post-metadata">

### Author: ![mzabaluev](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mzabaluev/32/3082_2.png) [@mzabaluev](https://internals.rust-lang.org/u/mzabaluev)
#### Post date: [October 8, 2015, 6:48am UTC](https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744/7 "2015-10-08T06:48:21Z")

</div>

A couple more discoveries.

This is 64-bit wide accordingly to GCC:

```rust
typedef enum {
    A = 0x80000000LL,
    B = -0x80000000LL
} C;

```

This doesn’t compile in today’s Rust:

```rust
#[repr(C)]
enum C {
    A = -1,
    B = 0x80000000u32,
}

```

So, it appears that GCC uses the discriminants’ types (including implicit value-based coercions) to decide on the size of the enum, while Rust coerces all values to `isize` and tries to work back from there.

---

<div class="post-metadata">

### Author: ![mzabaluev](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mzabaluev/32/3082_2.png) [@mzabaluev](https://internals.rust-lang.org/u/mzabaluev)
#### Post date: [October 8, 2015, 7:50am UTC](https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744/8 "2015-10-08T07:50:32Z")

</div>

> [@mzabaluev](#):
>
> C uses the discriminants' types (including implicit value-based coercions)

It's not just the value, the literal's numeric base also figures: [http://en.cppreference.com/w/c/language/integer\_constant](http://en.cppreference.com/w/c/language/integer_constant)

---

<div class="post-metadata">

### Author: ![nikomatsakis](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/nikomatsakis/32/5410_2.png) [@nikomatsakis](https://internals.rust-lang.org/u/nikomatsakis)
#### Post date: [October 8, 2015, 10:44pm UTC](https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744/9 "2015-10-08T22:44:13Z")

</div>

> [@mzabaluev](#):
>
> On the other hand, if #[repr(C)] on x86\_64-pc-windows-msvc ends up being something completely different than what the C compiler uses by default, there is a problem.

Huh. I'm trying to remember the details here, but certainly our intention was that #[repr(C)] would match the "main C compiler", to the extent that this is possible. I sort of remember that we decided to just use `i32` unless explicitly specified, as part of the overflow checking work, but maybe I have it wrong. @pnkfelix do you remember?

---

<div class="post-metadata">

### Author: ![mzabaluev](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mzabaluev/32/3082_2.png) [@mzabaluev](https://internals.rust-lang.org/u/mzabaluev)
#### Post date: [October 9, 2015, 7:17am UTC](https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744/10 "2015-10-09T07:17:15Z")

</div>

I have submitted my findings as a [Github issue](https://github.com/rust-lang/rust/issues/28925).

---

<div class="post-metadata">

### Author: ![pnkfelix](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/pnkfelix/32/110_2.png) [@pnkfelix](https://internals.rust-lang.org/u/pnkfelix)
#### Post date: [October 13, 2015, 2:09pm UTC](https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744/11 "2015-10-13T14:09:27Z")

</div>

(no I don’t remember the discussion here.)

---

<div class="post-metadata">

### Author: ![jld](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jld/32/634_2.png) [@jld](https://internals.rust-lang.org/u/jld)
#### Post date: [November 23, 2017, 4:07am UTC](https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744/12 "2017-11-23T04:07:23Z")

</div>

As I recall, it was meant to match the target’s C ABI, and git agrees with my memory: the exact words I used were “match the target’s C ABI for the equivalent C enum”. I remember I spent some time looking at ABI specs for the targets that were supported at the time, but I missed that Windows has a _maximum_ size. So that’s a bug, in my opinion.

Also, there’s probably at least one target that’s been added in the past 4 years where the `i32` default is wrong. (Originally there was no default, but the target architecture was changed from an `enum` to a string at some point.)

But trying to match C exactly is… difficult, as the examples in the above-linked GitHub issue demonstrate; this raises the question of whether “the equivalent C enum” is a well-defined concept.

And there’s the problem where C allows values that are impossible for the Rust `enum`. This raises the possibility that `#[repr(C)]` was never the right way to do C `enum` FFI. I don’t remember whether we just completely overlooked it, maybe because FFI usage was mostly Rust→C rather than C→Rust and being unable to generate those values wasn’t as bad as not being able to handle them, or if we noticed it and meant to do something about it at some point and nobody ever did, or what.

As for what to do about it: it would be nice to have something in the base language, or even a crate like `libc`, rather than requiring bindgen and/or winding up with code assuming `i32` and breaking on uncommon platforms.

---

<div class="post-metadata">

### Author: ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)
#### Post date: [March 25, 2019, 8:25am UTC](https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744/13 "2019-03-25T08:25:17Z")

</div>

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