Rust C FFI - declare constants from enums

I'm working on a Rust library that is used from C. I have an enum that is used to represent errors. I would like to automatically generate a header file with constants for each of the enum variants. That way the Rust code can return an integer (by converting the enum variant to an int), and the C program can use that to determine the error message.

I was wondering if this is possible, or do I need to manually generate the constants in the header file?

Furthermore, I'm not sure how to convert the enum into an integer. This only seems possible for simple enums.

For generating C headers based on your Rust code, you probably want cbindgen.

By "simple enum", I suppose you mean enums with only unit variants (i.e. no associated data). Of course, enums with associated data can't be expressed as primitive integers – how would you plan to expose the associated data in those cases?

I only care about the "number" the enum variant has. I will have a function that turns an enum variant into a string (to handle enums with associated data), but I still want to get an integer code that the C code can check.

I checked cbindgen, but I don't think it can declare constants.

Here's an example of what I'm trying to do:

if (error < 0) {
		if (error == GIT_ENOTFOUND)
			fprintf(stderr, "Could not find repository at path '%s'\n", path);
		else
			fprintf(stderr, "Unable to open repository: %s\n",
				git_error_last()->message);
		exit(1);
	}

For non-simple enums, you're waiting on the stabilization of RFC 2363. In the meanwhile, you could use something like strum::EnumDiscriminants to maintain a simple enum that mirrors your main enum.

1 Like

Thanks for the response. This ended up working. I was initially using the enum_kinds crate, but switched to strum.

Unfortunately, it looks like there is no way for cbindgen to generate #defines or variables for a set of enum variants, but that's ok.

asciicast

1 Like

Woah! This is very impressive.

Alas, the enum I have is autogenerated using proc macros, so I don't think it's straightforward to apply the proc macro to it.

// Use `EnumDiscriminants` to get an integer code for each enum variant
// https://users.rust-lang.org/t/rust-c-ffi-declare-constants-from-enums/60082/3
#[derive(Error, Debug, EnumDiscriminants)]
pub enum FooError {
    #[error("yadda yadda....")]

Oh, I see, that's indeed quite restrictive. Luckily, safer-ffi, contrary to cbindgen, is robust to macros, provided derive_ReprC annotation is emitted by the macro. Since EnumDiscriminants won't do that for us, we can polyfill it:

derive_CEnumDiscriminants! {
    #[derive(Error, Debug)]
    pub enum FooError {
        #[error("yadda yadda....")]
        …
    }
}

where here is a possible implementation of the polyfill:

macro_rules! derive_CEnumDiscriminants {(
    $( #[$attr:meta] )*
    $pub:vis
    enum $EnumName:ident {
        $(
            $( #[$field_attr:meta] )*
            $Variant:ident
                $(( $($parens:tt)* ))?
                $({ $($braces:tt)* })?
        ),* $(,)?
    }
) => (
    $( #[$attr] )*
    $pub
    enum $EnumName {
        $(
            $( #[$field_attr] )*
            $Variant
                $(( $($parens)* ))?
                $({ $($braces)* })?
        ),*
    }

    ::paste::paste! {
        #[::safer_ffi::derive_ReprC]
        #[repr(i32)]
        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
        $pub
        enum [< $EnumName Discriminants >] {
            $($Variant),*
        }
    }
)}
  • Playground

  • This won't work for generic enums, that would require a bit more effort but not that much :slightly_smiling_face:

1 Like