Skip to content

repr(C) breaks sqlx::Type on enums #4366

Closed

Description

@Erik1000

I have found these related issues/pull requests

When an enum has repr(C) the sqlx macro uses C in its _ as C casting.

Description

Given a enum like this:

#[derive(Debug, sqlx::Type)]
#[repr(C)]
enum MyEnum {
    VariantA,
    VariantB,
}

the macro expands to:

// Recursive expansion of sqlx::Type macro
// ========================================

#[automatically_derived]
impl<'q, DB: ::sqlx::Database> ::sqlx::encode::Encode<'q, DB> for MyEnum
where
    C: ::sqlx::encode::Encode<'q, DB>,
{
    fn encode_by_ref(
        &self,
        buf: &mut <DB as ::sqlx::database::Database>::ArgumentBuffer,
    ) -> ::std::result::Result<::sqlx::encode::IsNull, ::sqlx::error::BoxDynError> {
        let value = match self {
            MyEnum::VariantA => (MyEnum::VariantA as C),
            MyEnum::VariantB => (MyEnum::VariantB as C),
        };
        <C as ::sqlx::encode::Encode<DB>>::encode_by_ref(&value, buf)
    }
    fn size_hint(&self) -> usize {
        <C as ::sqlx::encode::Encode<DB>>::size_hint(&Default::default())
    }
}
#[automatically_derived]
impl<'r, DB: ::sqlx::Database> ::sqlx::decode::Decode<'r, DB> for MyEnum
where
    C: ::sqlx::decode::Decode<'r, DB>,
{
    fn decode(
        value: <DB as ::sqlx::database::Database>::ValueRef<'r>,
    ) -> ::std::result::Result<
        Self,
        ::std::boxed::Box<
            dyn ::std::error::Error + 'static + ::std::marker::Send + ::std::marker::Sync,
        >,
    > {
        let value = <C as ::sqlx::decode::Decode<'r, DB>>::decode(value)?;
        match value {
            _ if (MyEnum::VariantA as C) == value => ::std::result::Result::Ok(MyEnum::VariantA),
            _ if (MyEnum::VariantB as C) == value => ::std::result::Result::Ok(MyEnum::VariantB),
            _ => ::std::result::Result::Err(::std::boxed::Box::new(::sqlx::Error::Decode(
                ::std::format!("invalid value {:?} for enum {}", value, "MyEnum").into(),
            ))),
        }
    }
}
#[automatically_derived]
impl<DB: ::sqlx::Database> ::sqlx::Type<DB> for MyEnum
where
    C: ::sqlx::Type<DB>,
{
    fn type_info() -> DB::TypeInfo {
        <C as ::sqlx::Type<DB>>::type_info()
    }
    fn compatible(ty: &DB::TypeInfo) -> bool {
        <C as ::sqlx::Type<DB>>::compatible(ty)
    }
}
#[automatically_derived]
impl ::sqlx::postgres::PgHasArrayType for MyEnum {
    fn array_type_info() -> ::sqlx::postgres::PgTypeInfo {
        <C as ::sqlx::postgres::PgHasArrayType>::array_type_info()
    }
}

For repr(usize) and friends this behaviour makes sense but not for repr(C)

Reproduction steps

#[derive(Debug, sqlx::Type)]
#[repr(C)]
enum MyEnum {
    VariantA,
    VariantB,
}

SQLx version

0.9

Enabled SQLx features

"runtime-tokio","tls-rustls-ring-native-roots","postgres","uuid","chrono","json","derive","migrate",

Database server and version

PostgresQL 18

Operating system

Windows

Rust version

rustc 1.98.0-nightly (f20a92ec0 2026-06-07)

Activity

  1. Erik1000 commented on Aug 7, 2026

    @Erik1000
    ContributorAuthor

    Discovered because facet requires enum to have an explict repr

  2. thelastbackspace commented on Nov 5, 2026

    @thelastbackspace
    Contributor

    I'd like to pick this up. The derive takes the first bare word in #[repr(..)] as the integer type, so repr(C) becomes as C. My fix only accepts integer reprs and treats anything else like an enum without a repr (a string enum). Opening a PR with a sqlite test, happy to change the approach if you'd prefer something else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions