Skip to content

Take anything that implements Hash for keys - #5687

Open
DominicBurkart wants to merge 1 commit into
DioxusLabs:mainfrom
DominicBurkart:hashable
Open

Take anything that implements Hash for keys#5687
DominicBurkart wants to merge 1 commit into
DioxusLabs:mainfrom
DominicBurkart:hashable

Conversation

@DominicBurkart

Copy link
Copy Markdown

Closes #2188

Keys in rsx were restricted to formatted strings, so a hashable id like a Uuid had to take the wasteful Uuid -> String -> diff path. This PR lets key accept any expression whose value implements Hash:

rsx! {
    for user in users {
        UserRow { key: user.uuid, user }
    }
}

How it works

  • dioxus_core::Key is a new enum with two variants: Key::Str(String) for formatted-string keys (unchanged behavior, still rewritable by hot reloading from the literal pool) and Key::Hashed(u64) for keys built by hashing any Hash value with a deterministic DefaultHasher. VNodeInner::key is now Option<Key>, and keyed diffing compares/hashes Key values directly instead of &str.
  • rsx macro: key: "{value}" keeps its existing path. Any other expression (key: uuid, key: item.id, shorthand key) is now accepted by validate_key (which previously carried a todo pointing at exactly this issue) and is hashed at the call site via Key::hashed(&(expr)). Static keys are still rejected. Event handlers / if-chains as keys are still rejected.
  • Hot reloading: HotReloadedTemplate::key becomes Option<HotReloadKey> where HotReloadKey::Fmted re-renders from the literal pool as before, and HotReloadKey::Dynamic marks an expression key computed at the rsx call site (the codegen now passes the call-site key into DynamicValuePool::render_with). The hot-reload differ remembers the last build's key expression: templates with an unchanged expression key stay hot-reloadable; changing the key expression falls back to a full rebuild.

Notes

  • Keys only compare equal when created the same way: Key::from("a") != Key::hashed("a"). Since every instance of a template site generates its keys the same way, this doesn't affect diffing in practice.
  • This also fixes an inconsistency where a non-string literal key like key: 1 silently compiled to Some("1") in release but None in debug: literals now go through the hashed path consistently in both modes.
  • Key::Hashed compares 64-bit hashes rather than values, so a collision between two sibling keys is theoretically possible (~2⁻⁶⁴, SipHash). Happy to switch to storing/comparing something stronger if that tradeoff isn't acceptable.
  • Key is exported from dioxus_core but intentionally not added to dioxus::prelude, which already re-exports the keyboard Key type from dioxus_html.

Testing

  • packages/core/tests/diff_hashed_keys.rs: keyed-diff scenarios mirrored from diff_keyed_list.rs using integer keys, a custom #[derive(Hash)] type, component keys, and a stability check — asserting the same mutations as their string-key equivalents.
  • packages/rsx-hotreload/tests/hotreload_pattern.rs::expr_keys: expression keys hot reload when unchanged, require rebuild when changed/added/switched from a formatted key, and can be removed.
  • packages/rsx/tests/parsing.rs: expression keys parse without diagnostics; event-handler keys still error. The old key_must_be_formatted contract test is replaced since this PR intentionally changes that contract.
  • cargo test for dioxus-core (incl. doctests), dioxus-rsx, dioxus-rsx-hotreload all pass; cargo clippy -D warnings clean on the three touched crates.

https://claude.ai/code/session_01PWiGWxtiybnBJYQBHYYcmQ

Keys in rsx were restricted to formatted strings, forcing hashable
types like Uuid through a wasteful value -> String -> diff path.

Introduce dioxus_core::Key with two variants: Key::Str for formatted
string keys (still rewritable by hot reloading from the literal pool)
and Key::Hashed for keys built from the hash of any value implementing
Hash. VNodeInner::key is now Option<Key> and keyed diffing compares
Key values directly instead of &str.

In the rsx macro, key: "{value}" keeps its existing string path,
while any other expression (key: uuid, key: item.id) is accepted and
hashed at the call site via Key::hashed. HotReloadedTemplate::key
becomes Option<HotReloadKey>, where HotReloadKey::Dynamic marks an
expression key computed at the call site: hot reloading keeps working
for templates with expression keys as long as the key expression
itself is unchanged, and falls back to a full rebuild when it changes.

This also fixes non-string literal keys like key: 1 silently producing
Some("1") in release but None in debug: they now go through the
hashed path consistently in both modes.

Closes DioxusLabs#2188
@DominicBurkart
DominicBurkart marked this pull request as ready for review July 20, 2026 20:46
@DominicBurkart
DominicBurkart requested a review from a team as a code owner July 20, 2026 20:46
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.

Take anything that's Hash for keys

1 participant