bevyengine/bevy · critical

can only hash u64 using PassHasher

Error message

can only hash u64 using PassHasher

What it means

`PassHasher` (crates/bevy_platform/src/hash.rs) is a pass-through hasher for keys that are already well-distributed u64 values (bevy `Entity` ids). It only implements `write_u64` (stores the value) and `finish`; hashing anything that emits raw bytes hits `write`, which panics with "can only hash u64 using PassHasher". Hashers built on it (e.g. `NoOpHash`/the Entity-hash state used by `EntityHashMap`/`EntityHashSet`) are only valid for keys whose `Hash` impl is exactly one `write_u64` call.

Source

Thrown at crates/bevy_platform/src/hash.rs:154

        PassHasher::default()
    }
}

/// A no-op hash that only works on `u64`s. Will panic if attempting to
/// hash a type containing non-u64 fields.
#[derive(Debug, Default)]
pub struct PassHasher {
    hash: u64,
}

impl Hasher for PassHasher {
    #[inline]
    fn finish(&self) -> u64 {
        self.hash
    }

    fn write(&mut self, _bytes: &[u8]) {
        panic!("can only hash u64 using PassHasher");
    }

    #[inline]
    fn write_u64(&mut self, i: u64) {
        self.hash = i;
    }
}

/// [`BuildHasher`] for types that already contain a high-quality hash.
#[derive(Clone, Default)]
pub struct NoOpHash;

impl BuildHasher for NoOpHash {
    type Hasher = NoOpHasher;

    fn build_hasher(&self) -> Self::Hasher {
        NoOpHasher(0)
    }

View on GitHub (pinned to 396ca72708)

Solutions

  1. Keep pass-through hashers for `Entity`/`u64` keys only
  2. For arbitrary keys, use a real hasher: std's `RandomState`, or bevy_platform's ahash-based `RandomState`/`FixedState`
  3. If you must keep the map, pre-hash your key to a u64 yourself and use the hash as the key

Example fix

// before: &str keys in a pass-through-hashed map -> panic on first insert
let mut map: EntityHashMap<String, u32> = EntityHashMap::default();
map.insert("player".to_string(), 1);

// after: regular hasher for string keys
let mut map: HashMap<String, u32> = HashMap::default();
// or keep EntityHashMap only for Entity/u64 keys
Defensive patterns

Strategy: type-guard

Validate before calling

null

Type guard

/// Only types whose Hash impl is exactly one write_u64 may live in
/// pass-through-hashed maps. Restrict usage via this newtype.
struct EntityMap<V> {
    inner: EntityHashMap<Entity, V>,
}

impl<V> EntityMap<V> {
    fn insert(&mut self, k: Entity, v: V) -> Option<V> {
        self.inner.insert(k, v) // Entity -> write_u64, safe
    }
    fn get(&self, k: Entity) -> Option<&V> { self.inner.get(&k) }
}

Prevention

When it happens

Trigger: Inserting/looking up with a key type that hashes via bytes or non-u64 integers in a map built on this hasher: `&str`, `String`, `u32` (default `write_u32` forwards to `write`), tuples like `(Entity, u32)`, floats, enums, or any composite type. Copying an `EntityHashMap` declaration and swapping the key type is the classic trigger.

Common situations: Refactoring an `EntityHashMap<Entity, T>` into a keyed cache with a different key; using the bevy_platform hash state in a `hashbrown`/`HashMap` generic over `S` and instantiating it with non-Entity keys; serialization code storing string keys in an Entity-keyed map.

Related errors


AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20). Data as JSON: /api/errors/c38b735f0f45899b. Report an issue: GitHub.