Deriving ReprC for custom enums
C enums
A C enum is a field-less enum, i.e., an enum that only has unit variants.
Examples
enum Ordering {
Less /* = 0 */,
Equal /* = 1 */,
Greater /* = 2 */,
}
/// The following discriminants are all guaranteed to be > 0.
enum ErrorKind {
NotFound = 1,
PermissionDenied /* = 2 */,
TimedOut /* = 3 */,
Interrupted /* = 4 */,
Other /* = 5 */,
// ...
}
See the reference for more info about them.
Usage
use ::safer_ffi::prelude:*;
#[derive_ReprC] // <- `::safer_ffi`'s attribute
#[repr(u8)] // <- explicit integer `repr` is mandatory!
pub
enum LogLevel {
Off = 0, // <- explicit discriminants are supported
Error,
Warning,
Info,
Debug,
}
Generated C header
typedef uint8_t LogLevel_t; enum {
LOGLEVEL_OFF = 0,
LOGLEVEL_ERROR,
LOGLEVEL_WARNING,
LOGLEVEL_INFO,
LOGLEVEL_DEBUG,
};
Layout of C enums
These enums are generally used to define a closed set of distinct integral constants in a type-safe fashion.
But when used from C, the type safety is kind of lost, given how loosely C
converts back and forth between enums and integers.
This leads to a very important point:
What is the integer type of the enum discriminants?
With no #[repr(...)] annotation whatsoever, Rust reserves the right to
choose whatever it wants: no defined C layout, so not FFI-safe.
With #[repr(Int)] (where Int can be u8, i8, u32,
etc.) Rust is forced to use that very Int type.
With #[repr(C)], Rust will pick what C would pick if it were given an
equivalent definition.
#[repr(C)] enums can cause UB when used across FFI ⚠️
Click for more info
It turns out C itself does not really define a concrete integer layout for its enums. Indeed, the C standard only states that:
-
the discriminants are
ints. -
the enum itself represents an integer type that must fit in an
int.-
Very often this is an
inttoo. -
but since there is no explicit guarantee that it must be exactly an
inttoo, compiler flags such as-fshort-enumscan lead to smaller integer types.This means that when you link against a library that was compiled with a different set of flags, such as a system-wide shared library or a Rust generated
staticlib/cdylib, then such mismatch is very likely to cause Undefined Behavior!
-
In practice, when C defines an enum to be used by Rust, there is no other
choice but to use #[repr(C)] and pray / ensure that the C library is compiled
with the same semantics that Rust expects (e.g., no -fshort-enums flag).
But when doing FFI in the other direction, there is no reason whatsoever to use
#[repr(C)]: picking a fixed-size integer is then the most sensible thing to
do for a well-defined and thus robust FFI interface.
That's why #[derive_ReprC] makes the opinionated choice of
refusing to handle an enum definition that does not provide an
explicit fixed-size integer representation.
More complex enums
Are not supported yet.