Skip to content

Into derive is non-deterministic: impl blocks emitted in HashMap order (changes consuming crate SVH) #48

@pchikku

Description

@pchikku

Summary

The Into derive emits its generated impl Into<…> blocks in std::collections::HashMap iteration order, which is randomized per process. As a result the macro expands to a different token order on every compile, which changes the crate-metadata hash (SVH) of the crate that uses the derive.

Mechanism

  • src/trait_handlers/into/models/type_attribute.rs stores the target types in
    pub(crate) types: HashMap<HashType, Bound>.

  • src/trait_handlers/into/into_struct.rs iterates them directly into output:

    for (target_ty, bound) in type_attribute.types {
        // … emits one `impl ::core::convert::Into<#target_ty> for … { … }` per entry
    }

std::HashMap is seeded per process via getrandom(), so the order of the emitted impl blocks differs between compilations of identical source.

Why it matters

Under a content-addressed build system with a shared binary cache (e.g. Nix/crate2nix), the same input then produces different output bytes across builds/hosts. When a freshly-built crate is linked against a cached dependency whose recorded SVH differs, the compiler fails with error[E0463]: can't find crate. This is the proc-macro variant of rust-lang/rust#89904.

Reproduction

use educe::Educe;

#[derive(Educe)]
#[educe(Into(u16), Into(u32), Into(u64), Into(u128), Into(i16), Into(i32), Into(i64), Into(i128))]
pub struct W(pub u8);

Build this crate three times with a clean rebuild each time and hash the produced .rmeta: the three hashes differ (the contiguous differing region is the crate SVH; the compiled code is otherwise identical).

Fix (verified)

HashType already implements Ord, so switching types from HashMap to BTreeMap in into/models/type_attribute.rs makes the iteration — and therefore the emitted impl order — deterministic, with no other change:

-use std::collections::HashMap;
+use std::collections::BTreeMap;
...
-    pub(crate) types: HashMap<HashType, Bound>,
+    pub(crate) types: BTreeMap<HashType, Bound>,

(plus the two HashMap::new() call sites in the same file)

With this change the same crate rebuilds byte-identically across repeated clean builds. The current 0.6.0 line has the same pattern. Happy to open a PR if you'd like.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions