1 unstable release

new 0.0.0-2025-01-19 Jan 19, 2025

#78 in FFI

Apache-2.0 OR MIT

30KB
280 lines

abienum - underlying types for C enums

GitHub crates.io docs.rs License Build Status

Attempts to define the implicit underlying types of C and C++ enums, when compiled via the cc crate using default settings. That is, enums that follow any of these styles:

enum Test { Hello = 1 };
typedef enum { Hello = 1 } Test;
typedef enum Test { Hello = 1 } Test;
typedef enum _Test { Hello = 1 } Test;

Would presumably be FFI-compatible with the following Rust type:

#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)] pub struct Test(abienum::c_enum_u7);
impl Test { pub const Hello : Test = Test(1 as _); }

You are expected to use the c_enum_* with the smallest compatible range — e.g. c_enum_u7 over c_enum_u8 or c_enum_i8.

Caveats

The underlying type of enums is implementation specific, to the point of being potentially finicky in practice.

  1. Compilers may disagree on enum size. For example, ARM delegates type selection to the platform ABI, which leads to Clang and GCC disagreeing on enum size for unknown/none, where no platform ABI is specified or agreed upon. If using the cc crate, prefer a global CC=... over .compiler("..."). If linking against prebuilt libraries, specify the same compiler as was used for said libraries via CC=...

  2. Compilers provide flags controlling enum size, such as -f[no-]short-enums [clang, gcc]. If you need these, prefer a global CFLAGS=... over .flag("...").

  3. Compilers may provide extensions controlling enum size, such as __attribute__((packed)) or #pragmas. You're completely on your own for those. I recommend a lot of static_asserts on the C++ side and const _ : () = assert!(...);s on the Rust side. Good luck.

  4. Compilers may or may not actually respect the flags, attributes, and pragmas specified. E.g. LLVM seems to ignore them on Win32 despite parsing them (llvm/llvm-project #70607)

  5. Type selection beyond the basics (e.g. involving potentially typed expressions that depend on previous enumerands) is enough of a potential mess that I haven't tackled it. See also these C23 proposals:

Alternatives

C++11 (and perhaps C23?) provides syntax to explicitly control the underlying type of enums, which — assuming you can sanely modify the C/C++ — I strongly recommend over resorting to this crate's nonsense:

enum Test : int { Hello = 1 };
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)] pub struct Test(core::ffi::c_int);
impl Test { pub const Hello : Test = Test(1 as _); }

If you're stuck in earlier versions of C or C++, the old "force dword" trick will at least get the size right (signedness may still vary):

typedef enum _D3DLIGHTTYPE {
    D3DLIGHT_POINT          = 1,
    D3DLIGHT_SPOT           = 2,
    D3DLIGHT_DIRECTIONAL    = 3,
    D3DLIGHT_FORCE_DWORD    = 0x7fffffff, /* force 32-bit size enum */
} D3DLIGHTTYPE;
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)] pub struct D3DLIGHTTYPE(i32);
pub const D3DLIGHT_POINT        : D3DLIGHTTYPE = D3DLIGHTTYPE(1);
pub const D3DLIGHT_SPOT         : D3DLIGHTTYPE = D3DLIGHTTYPE(2);
pub const D3DLIGHT_DIRECTIONAL  : D3DLIGHTTYPE = D3DLIGHTTYPE(3);
//  const D3DLIGHT_FORCE_DWORD  : D3DLIGHTTYPE = D3DLIGHTTYPE(0x7fffffff); // impl detail

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

No runtime deps

~0–305KB