{"record":{"id":"eb438c3eaa29c600","repo":"tokio-rs/tokio","slug":"joinerror-reason-is-not-a-panic","errorCode":null,"errorMessage":"`JoinError` reason is not a panic.","messagePattern":"`JoinError` reason is not a panic\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tokio/src/runtime/task/error.rs","lineNumber":95,"sourceCode":"    /// ```should_panic,ignore-wasm\n    /// use std::panic;\n    ///\n    /// #[tokio::main]\n    /// async fn main() {\n    ///     let err = tokio::spawn(async {\n    ///         panic!(\"boom\");\n    ///     }).await.unwrap_err();\n    ///\n    ///     if err.is_panic() {\n    ///         // Resume the panic on the main task\n    ///         panic::resume_unwind(err.into_panic());\n    ///     }\n    /// }\n    /// ```\n    #[track_caller]\n    pub fn into_panic(self) -> Box<dyn Any + Send + 'static> {\n        self.try_into_panic()\n            .expect(\"`JoinError` reason is not a panic.\")\n    }\n\n    /// Consumes the join error, returning the object with which the task\n    /// panicked if the task terminated due to a panic. Otherwise, `self` is\n    /// returned.\n    ///\n    /// # Examples\n    ///\n    /// ```should_panic,ignore-wasm\n    /// use std::panic;\n    ///\n    /// #[tokio::main]\n    /// async fn main() {\n    ///     let err = tokio::spawn(async {\n    ///         panic!(\"boom\");\n    ///     }).await.unwrap_err();\n    ///\n    ///     if let Ok(reason) = err.try_into_panic() {","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/tokio-rs/tokio/blob/625954f365727668cb02d04172b34f1149637728/tokio/src/runtime/task/error.rs#L77-L113","documentation":"JoinError::into_panic calls try_into_panic().expect('JoinError reason is not a panic.'). It panics (double panic risk) when called on a JoinError that is NOT a panic — i.e. it represents a cancelled task. into_panic assumes the underlying task panicked.","triggerScenarios":"Calling err.into_panic() on a JoinError that is actually Repr::Cancelled (task was aborted), not Repr::Panic.","commonSituations":"Unconditionally calling into_panic() after handle.await.err() without checking is_panic(); confusing cancellation with panic in error handling.","solutions":["Check e.is_panic() before calling e.into_panic().","Prefer try_into_panic() which returns Result and won't panic.","Match on the error kind (cancelled vs panic) explicitly.","Treat cancellation separately from panic recovery in your error path."],"exampleFix":"// before\nlet panic_payload = err.into_panic(); // panics if cancelled\n// after\nif err.is_panic() {\n    std::panic::resume_unwind(err.into_panic());\n} else {\n    // cancelled\n    return;\n}","handlingStrategy":"type-guard","validationCode":"// Guard with is_panic before calling into_panic:\nif err.is_panic() { /* safe to into_panic */ }","typeGuard":"fn into_panic_safe(e: tokio::task::JoinError) -> Result<Box<dyn std::any::Any + Send>, tokio::task::JoinError> {\n    e.try_into_panic().map_err(|e| e)\n}","tryCatchPattern":"match err.try_into_panic() {\n    Ok(payload) => std::panic::resume_unwind(payload),\n    Err(e) => { /* cancelled */ return; }\n}","preventionTips":["Prefer try_into_panic() over into_panic().","Always check is_panic() first.","Distinguish cancellation from panic in error handling."],"tags":["task","join-error","panic","cancellation"],"backgroundTag":null,"analyzedSha":"625954f365727668cb02d04172b34f1149637728","analyzedAt":"2026-08-11T17:46:45.378Z","contentChangedAt":"2026-08-11T17:46:45.378Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}