PyO3/pyo3 · error
Empty enum
Error message
Empty enum
What it means
The #[pyclass] macro computes the output type of an enum by unioning the output types of all its variants with Iterator::reduce, which returns None for an empty iterator; the expect then panics. It guards the assumption that a pyclass enum has at least one variant. An empty enum (enum E {}) has no representable value, so PyO3 refuses to generate code for it.
Source
Thrown at pyo3-macros-backend/src/intopyobject.rs:475
target: quote!(#pyo3_path::types::PyAny),
output: quote!(#pyo3_path::Bound<'py, <Self as #pyo3_path::conversion::IntoPyObject<'py>>::Target>),
error: quote!(#pyo3_path::PyErr),
},
body: quote! {
match self {
#variants
}
},
}
}
#[cfg(feature = "experimental-inspect")]
fn output_type(&self) -> PyExpr {
self.variants
.iter()
.map(|var| var.output_type())
.reduce(PyExpr::union)
.expect("Empty enum")
}
}
// if there is a `'py` lifetime, we treat it as the `Python<'py>` lifetime
fn verify_and_get_lifetime(generics: &syn::Generics) -> Option<&syn::LifetimeParam> {
let mut lifetimes = generics.lifetimes();
lifetimes.find(|l| l.lifetime.ident == "py")
}
pub fn build_derive_into_pyobject<const REF: bool>(tokens: &DeriveInput) -> Result<TokenStream> {
let options = ContainerAttributes::from_attrs(&tokens.attrs)?;
let ctx = &Ctx::new(&options.krate, None);
let Ctx { pyo3_path, .. } = &ctx;
let (_, ty_generics, _) = tokens.generics.split_for_impl();
let mut trait_generics = tokens.generics.clone();
if REF {
trait_generics.params.push(parse_quote!('_a));View on GitHub (pinned to ac9b6899d3)
Solutions
- Add at least one variant to the enum.
- If the enum is a never type, do not expose it to Python; keep it out of #[pyclass].
- Check feature flags: ensure at least one variant is not compiled out by cfg when the crate builds.
- Wrap the type differently (e.g. expose a unit variant placeholder) if a placeholder value is acceptable.
Example fix
// before
#[pyclass]
enum Never {}
// after
#[pyclass]
enum Never {
Unreachable,
} Defensive patterns
Strategy: validation
Validate before calling
// Before adding #[pyclass] to an enum, ensure it has variants assert!(variants > 0, "pyclass enum must have at least one variant");
Type guard
fn has_variants(e: &syn::ItemEnum) -> bool { !e.variants.is_empty() } Prevention
- Never declare pyclass enums with zero variants.
- Audit cfg-gated variants: keep at least one variant enabled in every feature combination.
- Treat empty (never-type) enums as non-Python types.
When it happens
Trigger: Applying #[pyclass] (or the IntoPyObject/derive machinery) to an enum with zero variants, e.g. `#[pyclass] enum Never {}`.
Common situations: Users porting never-type-like empty enums from Rust-only code into pyo3-exposed types, or code-generated enums that ended up with no variants after cfg-stripping (e.g. all variants behind #[cfg] features that are off).
Related errors
- complex enum has a non-unit variant
- named field has an identifier
- Empty enum
- Named fields should have identifiers
- no class given for Fn with a "self" receiver
AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05).
Data as JSON: /api/errors/75c45ddd77909be1.
Report an issue: GitHub.