FyroxEngine/Fyrox · error

Attempt to replace object in pool using dangling handle!…

Error message

Attempt to replace object in pool using dangling handle! Handle is {:?}, but pool record has {} generation

What it means

Pool's replace-by-handle checks that the handle's generation matches the record's generation before overwriting. A generation mismatch means the handle is dangling — the object at that index was freed and the slot possibly reused — so replacing would silently write into someone else's object. The pool panics instead of corrupting data.

Solutions

  1. Validate the handle with pool.is_valid_handle(handle) (or try_contains) before replacing.
  2. Re-fetch a fresh handle after any free/spawn sequence touching the same pool.
  3. Subscribe to destruction events and clear stored handles when their target is freed.

Example fix

// before
pool.replace(stale_handle, new_obj); // panics on generation mismatch
// after
if pool.is_valid_handle(stale_handle) {
    pool.replace(stale_handle, new_obj);
} else {
    let h = pool.spawn(new_obj);
}
Defensive patterns

Strategy: validation

Validate before calling

// validate before replace
if !pool.is_valid_handle(handle) {
    // handle is dangling: re-fetch or respawn
}

Type guard

fn is_live<T>(pool: &Pool<T>, h: Handle<T>) -> bool {
    pool.try_borrow(h).is_some()
}

Prevention

When it happens

Trigger: Calling Pool::replace (or the method at fyrox-core/src/pool/mod.rs:1203) with a Handle obtained before the target was freed and its slot recycled; also after wrapping/free of the original object.

Common situations: Storing handles long-term (in components, UI, scripts) while the referenced pool object is freed and the index reused; event callbacks firing after the target was destroyed.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of FyroxEngine/Fyrox@76c91aad8e (2026-09-10). Data as JSON: /api/errors/738eb80e8d82a728. Report an issue: GitHub.

Appendix: source

Thrown at fyrox-core/src/pool/mod.rs:1203

    ///
    /// [`take_reserve`]: Pool::take_reserve
    /// [`alive_count`]: Pool::alive_count
    #[inline]
    pub fn total_count(&self) -> u32 {
        let free = u32::try_from(self.free_stack.len()).expect("free stack length overflowed u32");
        self.records_len() - free
    }

    #[inline]
    pub fn replace(&mut self, handle: Handle<T>, payload: T) -> Option<T> {
        let index_usize = usize::try_from(handle.index).expect("index overflowed usize");
        if let Some(record) = self.records.get_mut(index_usize) {
            if record.generation == handle.generation {
                self.free_stack.retain(|i| *i != handle.index);

                record.payload.replace(payload)
            } else {
                panic!("Attempt to replace object in pool using dangling handle! Handle is {:?}, but pool record has {} generation", handle, record.generation);
            }
        } else {
            None
        }
    }

    /// Returns a reference to the first element in the pool (if any).
    pub fn first_ref(&self) -> Option<&T> {
        self.iter().next()
    }

    /// Returns a reference to the first element in the pool (if any).
    pub fn first_mut(&mut self) -> Option<&mut T> {
        self.iter_mut().next()
    }

    /// Checks if given handle "points" to some object.
    ///

View on GitHub (pinned to 76c91aad8e)