Skip to content

fix: use deterministic keys in element! macro for reproducible builds#187

Merged
ccbrown merged 1 commit into
ccbrown:mainfrom
domenkozar:fix/deterministic-element-keys
Apr 10, 2026
Merged

fix: use deterministic keys in element! macro for reproducible builds#187
ccbrown merged 1 commit into
ccbrown:mainfrom
domenkozar:fix/deterministic-element-keys

Conversation

@domenkozar
Copy link
Copy Markdown
Contributor

Summary

  • Replace Uuid::new_v4() with a deterministic atomic counter for element declaration keys in the element! macro
  • Remove the uuid dependency from iocraft-macros
  • Add test verifying key determinism across invocations

Problem

The element! macro uses Uuid::new_v4() to generate unique keys for each element declaration. Since Uuid::new_v4() reads from /dev/urandom, it produces different output on every compilation. This makes any crate using element! non-reproducible: two compilations of the same source with the same flags produce different .rmeta and .o files.

This breaks build systems that rely on reproducible builds, such as Nix with crate2nix/buildRustCrate. In that setup, crates are compiled independently and can be cached. When a cached crate has different metadata (SVH) than what a locally-built dependent expects, rustc produces E0463 "can't find crate" link errors.

Fix

Proc macro expansion order is deterministic within a compilation, so a simple atomic counter produces stable, unique keys. The counter resets to 0 at the start of each compilation (since proc macro state is not persisted), ensuring identical output for identical input.

Test plan

  • All existing tests pass
  • New deterministic_keys test verifies same call site produces same key and different call sites produce different keys

🤖 Generated with Claude Code

@domenkozar domenkozar force-pushed the fix/deterministic-element-keys branch 2 times, most recently from 2745041 to dba0150 Compare April 8, 2026 02:54
@ccbrown
Copy link
Copy Markdown
Owner

ccbrown commented Apr 8, 2026

Thanks for the PR!

I think there are situations where this could cause conflicts. For example, a library which provides a component like so might encounter conflicts since the static variable is scoped only to each compilation unit (i.e. there can be more than one component with the 0 key):

element! {
    Foo {}

    #(children)
}

Maybe there's a way to guarantee uniqueness across compilation units by using proc_macro::Span::call_site() or something similar?

Include module_path!() in generated element keys so that keys are
unique across compilation units, not just within a single crate.
This prevents key conflicts when library and user elements become
siblings via #(children) interpolation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@domenkozar domenkozar force-pushed the fix/deterministic-element-keys branch from dba0150 to c2d4a82 Compare April 8, 2026 08:47
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.71%. Comparing base (d614ffa) to head (c2d4a82).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #187   +/-   ##
=======================================
  Coverage   87.71%   87.71%           
=======================================
  Files          35       35           
  Lines        5525     5526    +1     
  Branches     5525     5526    +1     
=======================================
+ Hits         4846     4847    +1     
  Misses        570      570           
  Partials      109      109           
Files with missing lines Coverage Δ
packages/iocraft-macros/src/lib.rs 94.24% <100.00%> (+0.01%) ⬆️

Impacted file tree graph

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@domenkozar
Copy link
Copy Markdown
Contributor Author

Thanks for the review! Good catch on the cross-crate collision issue.

I've updated the implementation to include module_path!() in the generated keys, so the key is now (module_path!(), counter) (or (module_path!(), counter, user_key) when a user provides a key). Since module_path!() always starts with the crate name and Cargo enforces unique crate names in a dependency graph, this guarantees uniqueness across compilation units. The counter continues to handle within-crate uniqueness.

@ccbrown
Copy link
Copy Markdown
Owner

ccbrown commented Apr 10, 2026

Lgtm!

@ccbrown ccbrown merged commit 090d2bb into ccbrown:main Apr 10, 2026
5 checks passed
@github-actions github-actions Bot mentioned this pull request Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants