{"record":{"id":"9592da3fb3770e87","repo":"FyroxEngine/Fyrox","slug":"an-object-at-index-must-be-returned-to-a-pool-i","errorCode":null,"errorMessage":"An object at index {} must be returned to a pool it was taken from! Call Pool::forget_ticket if you don't need the object anymore.","messagePattern":"An object at index (.+?) must be returned to a pool it was taken from! Call Pool::forget_ticket if you don't need the object anymore\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"fyrox-core/src/pool/mod.rs","lineNumber":367,"sourceCode":"where\n    T: 'static,\n    P: PayloadContainer<Element = T> + 'static,\n{\n    #[inline]\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\n#[derive(Debug)]\npub struct Ticket<T> {\n    index: u32,\n    marker: PhantomData<T>,\n}\n\nimpl<T> Drop for Ticket<T> {\n    fn drop(&mut self) {\n        panic!(\n            \"An object at index {} must be returned to a pool it was taken from! \\\n            Call Pool::forget_ticket if you don't need the object anymore.\",\n            self.index\n        )\n    }\n}\n\nimpl<T, P> Clone for PoolRecord<T, P>\nwhere\n    T: Clone,\n    P: PayloadContainer<Element = T> + Clone + 'static,\n{\n    #[inline]\n    fn clone(&self) -> Self {\n        Self {\n            ref_counter: Default::default(),\n            generation: self.generation,\n            payload: self.payload.clone(),","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/FyroxEngine/Fyrox/blob/76c91aad8eca488ce527b1af707be8b3b24ad72d/fyrox-core/src/pool/mod.rs#L349-L385","documentation":"Pool's Ticket<T> represents a checked-out object; its Drop impl panics unconditionally because the only correct way to release the ticket is to hand the object back via Pool (free/return), which consumes the ticket without running Drop. Dropping a ticket means an object was never returned to the pool, breaking the pool's free-list invariant.","triggerScenarios":"Letting a Ticket value go out of scope (or explicitly drop(ticket)) instead of returning the borrowed object to the pool with the consuming free/return method; early-returns or ? inside a scope holding a ticket.","commonSituations":"Error paths in pooled-resource code (connections, sound sources, temporary buffers) that forget to return the object; misuse after Pool::try_take/ take where the ticket is stored and later discarded.","solutions":["Return the object to the pool by calling the pool's consuming free method with the ticket before it drops.","If you intentionally never return it, call Pool::forget_ticket to detach the ticket safely.","Restructure error paths (early returns, ?) so the ticket is returned on all branches."],"exampleFix":"// before\nlet (ticket, obj) = pool.take_and_get(handle);\nif obj.is_bad() { return; } // ticket dropped -> panic\n// after\nlet (ticket, obj) = pool.take_and_get(handle);\nif obj.is_bad() {\n    pool.free(ticket);\n    return;\n}","handlingStrategy":"try-catch","validationCode":"// ensure every take has a matching free on all paths\nlet (ticket, obj) = pool.take_and_get(h);\nlet result = (|| { /* work with obj */ })();\npool.free(ticket); // always runs before any return","typeGuard":null,"tryCatchPattern":"// Rust cannot catch panics safely; instead wrap the work so the ticket is consumed:\nfn with_pooled<T, R>(pool: &mut Pool<T>, h: Handle<T>, f: impl FnOnce(&mut T) -> R) -> R {\n    let (ticket, mut obj) = pool.take_and_get(h);\n    let r = f(&mut obj);\n    pool.free(ticket);\n    r\n}","preventionTips":["Use a closure/RAII wrapper that frees the ticket before any early return.","Call Pool::forget_ticket when intentionally abandoning the object.","Never store Ticket values in long-lived structs without a free path."],"tags":["pool","raii","resource-leak","panic"],"backgroundTag":"invalid-state-transition","analyzedSha":"76c91aad8eca488ce527b1af707be8b3b24ad72d","analyzedAt":"2026-09-10T16:04:01.633Z","contentChangedAt":"2026-09-10T16:04:01.633Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}