1. Symptoms
When attempting to compile Rust code that uses the #[derive] attribute on an enum with zero variants, the compiler produces the E0732 error:
error[E0732]: #[derive] can't be used with enum that has no data
--> src/main.rs:3:1
|
3 | #[derive(Debug, Clone)]
| ^^^^^^^^^^^^^^^^^^^^^^
4 | enum Void {}
| ---------- has no variants
The error manifests with any trait in the derive macro, whether it’s Debug, Clone, Default, PartialEq, or any other derive-able trait. The compiler will also highlight the enum definition itself as having no variants.
You may encounter this error in several scenarios:
- Creating a marker enum for type-level programming
- Defining a never-type variant (similar to
!in Rust) - Accidentally creating an empty enum during refactoring
- Writing placeholder code that was never completed
The error message is explicit: enums with zero variants cannot support the derive mechanism because there is nothing to derive implementations for.
2. Root Cause
The #[derive] attribute in Rust generates implementations of traits based on the structure of types. For enums, the derive macro examines each variant and generates code that handles construction, comparison, formatting, and other operations.
Zero-variant enums (also called “empty enums” or “void enums”) represent a type that can never be instantiated. They have no constructors, no possible values, and therefore no actual data to process. When the compiler attempts to generate derive implementations, it finds nothing to work with.
The fundamental problem is logical: if a type can never have a value, then:
Clonehas nothing to cloneDebughas nothing to formatDefaulthas no value to returnPartialEqhas no comparisons to perform
This isn’t a limitation of the compilerβit’s a semantic impossibility. The derive macros are correctly rejecting the request because the derived code would never be usable.
Zero-variant enums do exist in Rust for specific purposes (such as marker types or compile-time impossible states), but they cannot use #[derive] for trait implementations.
3. Step-by-Step Fix
There are several approaches to resolve E0732 depending on your use case.
Option 1: Remove the #[derive] Attribute
If the enum genuinely has no variants and no derive macro is needed, simply remove the #[derive] attribute:
Before:
#[derive(Debug, Clone, PartialEq)]
enum Void {}
After:
enum Void {}
The enum still exists and can be used as a marker type, but without deriving any traits.
Option 2: Convert to a Struct with Private Fields
If you need the type to exist but cannot be instantiated, consider a zero-variant struct with a private field instead. However, this requires manual trait implementations:
Before:
#[derive(Debug)]
enum Never {}
impl Never {
fn new() -> Option<Never> {
None
}
}
After:
enum Never {}
impl Never {
fn new() -> Option<Never> {
None
}
}
Option 3: Use a Phantom Marker Type
For type-level programming, use a marker struct instead of an empty enum:
Before:
#[derive(Clone, Copy)]
enum MarkerType {}
struct PhantomMarker;
impl PhantomMarker {
// marker type methods
}
After:
// Marker struct with no fields - can still derive traits
#[derive(Clone, Copy, Default)]
struct PhantomMarker(u8);
impl PhantomMarker {
const fn new() -> Self {
PhantomMarker(0)
}
}
Option 4: Manually Implement Required Traits
If you must keep the zero-variant enum but need specific trait implementations, implement them manually:
Before:
#[derive(Debug)]
enum Impossible {}
impl std::fmt::Debug for Impossible {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Impossible")
}
}
After:
// Manual implementation for zero-variant enum
enum Impossible {}
impl std::fmt::Debug for Impossible {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
Note that the self parameter is never used since no instances can exist. The compiler allows this pattern.
Option 5: Convert to a Zero-Variant Tuple Struct
Before:
#[derive(Debug)]
enum EmptyEnum {}
After:
// Zero-variant tuple struct - still cannot be instantiated
struct EmptyEnum();
This preserves the type name while avoiding the enum-specific derive restriction.
4. Verification
After applying the fix, verify the code compiles correctly:
cargo build
For the basic fix, verify there are no warnings or errors:
Compiling my_project v0.1.0 (file:///path/to/project)
Finished dev [unoptimized + debuginfo] target(s) in 0.32s
Test that the type behaves as expected in your codebase:
// Verify the enum works in your context
fn main() {
// If using as a marker type
let _marker: MarkerType = unsafe { std::mem::transmute(0u8) };
println!("Zero-variant enum compiled successfully");
}
Run your test suite to ensure the change doesn’t break existing functionality:
cargo test
If you have a workspace with multiple crates, test each affected crate individually:
cargo test --all
5. Common Pitfalls
Assuming zero-variant enums are useful as-is: Many developers encounter E0732 when trying to use zero-variant enums as placeholder types. Remember that these types can never be constructed, so they have limited direct utility beyond compile-time type constraints.
Forgetting to check all derive occurrences: If the enum appears in multiple files or modules, ensure you remove #[derive] from every location:
// main.rs
#[derive(Debug)] // Error here
enum Void {}
mod inner {
#[derive(Clone)] // Error here too
enum Void {}
}
Confusing zero-variant enums with unit variants: A unit variant (like enum E { A }) has one variant and can be derived. Zero variants (like enum E {}) cannot be derived:
// This works - one unit variant
#[derive(Debug, Clone)]
enum Working {
UnitVariant,
}
// This fails - zero variants
#[derive(Debug)]
enum Broken {}
Attempting derive on uninhabited types for pattern matching: Even when using zero-variant enums for exhaustiveness checking in match expressions, you cannot derive traits on them:
fn process(value: SomeEnum) -> bool {
match value {
// This works without derive on the never type itself
}
}
Not understanding the intent of zero-variant enums: These exist primarily for compile-time guarantees that a code path is unreachable. If you need runtime functionality, the type design may need reconsideration.
6. Related Errors
E0080: “evaluation of constant value failed” - Can occur if a zero-variant enum is used in constant evaluation contexts where the compiler attempts to create a value.
E0433: “failed to resolve” - May appear if derive macros for zero-variant enums attempt to reference code that doesn’t exist due to the empty enum having no variants.
E0369: “binary operation cannot be applied” - Sometimes occurs when attempting operations on zero-variant enum values in generic contexts where trait bounds are satisfied by the enum but no actual values exist.
E0507: “cannot move out of” - Can appear if code tries to move a zero-variant enum value, though in practice this rarely occurs since such values can never exist.
E0599: “no method named” - May occur in generic code where a trait bound includes the zero-variant enum but the derive implementation was never generated.
For zero-variant enums specifically, the most common path to resolution is either removing the derive attribute or redesigning the type to have at least one variant. The compiler’s error E0732 correctly identifies that the derive operation is fundamentally impossible for this type structure.