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.
Summary
The
Intoderive emits its generatedimpl Into<…>blocks instd::collections::HashMapiteration 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.rsstores the target types inpub(crate) types: HashMap<HashType, Bound>.src/trait_handlers/into/into_struct.rsiterates them directly into output:std::HashMapis seeded per process viagetrandom(), so the order of the emittedimplblocks 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
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)
HashTypealready implementsOrd, so switchingtypesfromHashMaptoBTreeMapininto/models/type_attribute.rsmakes the iteration — and therefore the emittedimplorder — deterministic, with no other change:(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.0line has the same pattern. Happy to open a PR if you'd like.