linera-io/linera-protocol · error · syn::Error
Keccak-256 tag collision between variants `{other_name}` and
Error message
Keccak-256 tag collision between variants `{other_name}` and `{name}` (tag = {tag:#010x}). Rename one of the variants. What it means
`#[derive(StableEnum)]` assigns each enum variant a deterministic u32 tag: Keccak-256 of the variant name, first 4 bytes big-endian, masked into a 27-bit range so the ULEB128 encoding is always 4 bytes. At macro-expansion time `variant_tags` rejects duplicate tags, failing compilation with this error. With 2^27 possible tags the birthday bound makes collisions realistic only beyond roughly ten thousand variants — for hand-written enums this is essentially unreachable.
Source
Thrown at linera-sdk-derive/src/stable_enum.rs:74
/// Computes the stable tag for a variant name.
fn compute_tag(variant_name: &str) -> u32 {
let hash = Keccak256::digest(variant_name.as_bytes());
let val = u32::from_be_bytes([hash[0], hash[1], hash[2], hash[3]]);
(val & 0x07FF_FFFF) | 0x0800_0000
}
/// Computes all variant tags, returning an error on a (vanishingly unlikely) collision.
fn variant_tags(input: &ItemEnum) -> Result<Vec<(String, u32, &Variant)>> {
let mut out = Vec::with_capacity(input.variants.len());
for variant in &input.variants {
let name = variant.ident.to_string();
let tag = compute_tag(&name);
if let Some((other_name, _, _)) = out
.iter()
.find(|(_, t, _): &&(String, u32, &Variant)| *t == tag)
{
return Err(Error::new(
variant.span(),
format!(
"Keccak-256 tag collision between variants `{other_name}` and `{name}` \
(tag = {tag:#010x}). Rename one of the variants."
),
));
}
out.push((name, tag, variant));
}
Ok(out)
}
fn reject_generics(input: &ItemEnum) -> Result<()> {
if !input.generics.params.is_empty() || input.generics.where_clause.is_some() {
return Err(Error::new(
input.generics.span(),
"#[derive(StableEnum)] does not yet support generic enums",
));View on GitHub (pinned to 6c226ddcb3)
Solutions
- Rename one of the two variants named in the error — the message lists both names and the shared tag value.
- If names are generated, make the generator produce diverse, semantic names instead of sequential indices.
- Re-run `cargo check` after renaming; tags are deterministic, so the same pair always collides until renamed.
Example fix
// before
#[derive(StableEnum)]
enum Action { Transfer, Transferr } // hypothetical Keccak-256 tag collision
// after
#[derive(StableEnum)]
enum Action { Transfer, TransferV2 } // renamed variant gets a fresh tag Defensive patterns
Strategy: validation
Validate before calling
// compile-time: a CI `cargo check` on every generated enum catches this before merge // `cargo check -p my-app` fails with the collision message listing both variant names
Prevention
- Prefer semantic variant names over sequential generated ones.
- Keep generated enums small; split large schemas into multiple enums.
- Treat this as a deterministic compile error — the same pair always collides until renamed.
When it happens
Trigger: Deriving `StableEnum` on an enum where two variant names hash to the same 27-bit tag; procedurally generated enums with low name diversity (Variant00001-style names) at scales of ~10^4 variants.
Common situations: Code-generated enums from external schemas with auto-numbered variant names; tests that deliberately exercise the collision path of the derive macro.
Related errors
- #[derive(StableEnum)] does not yet support generic enums
- Failed to deserialize instantiation argument {argument:?}
- Query {argument:?} is invalid and could not be deserialized
- Application parameters must be deserializable
- Failed to deserialize service response
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/62d227917a04c026.
Report an issue: GitHub.