{"record":{"id":"fe7a96137cc232dc","repo":"PyO3/pyo3","slug":"already-mutably-borrowed","errorCode":null,"errorMessage":"Already mutably borrowed","messagePattern":"Already mutably borrowed","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/pycell.rs","lineNumber":311,"sourceCode":"    #[inline]\n    pub fn as_ptr(&self) -> *mut ffi::PyObject {\n        self.inner.as_ptr()\n    }\n\n    /// Returns an owned raw FFI pointer represented by self.\n    ///\n    /// # Safety\n    ///\n    /// The reference is owned; when finished the caller should either transfer ownership\n    /// of the pointer or decrease the reference count (e.g. with [`pyo3::ffi::Py_DecRef`](crate::ffi::Py_DecRef)).\n    #[inline]\n    pub fn into_ptr(self) -> *mut ffi::PyObject {\n        self.inner.clone().into_ptr()\n    }\n\n    #[track_caller]\n    pub(crate) fn borrow(obj: &Bound<'py, T>) -> Self {\n        Self::try_borrow(obj).expect(\"Already mutably borrowed\")\n    }\n\n    pub(crate) fn try_borrow(obj: &Bound<'py, T>) -> Result<Self, PyBorrowError> {\n        let cell = obj.get_class_object();\n        cell.ensure_threadsafe();\n        cell.borrow_checker()\n            .try_borrow()\n            .map(|_| Self { inner: obj.clone() })\n    }\n}\n\nimpl<'p, T> PyRef<'p, T>\nwhere\n    T: PyClass,\n    T::BaseType: PyClass,\n{\n    /// Gets a `PyRef<T::BaseType>`.\n    ///","sourceCodeStart":293,"sourceCodeEnd":329,"githubUrl":"https://github.com/PyO3/pyo3/blob/ac9b6899d348be4d54614d060dea53a645a12e36/src/pycell.rs#L293-L329","documentation":"PyO3's borrow checker for `#[pyclass]` objects enforces Rust-style runtime borrowing: an object can be immutably (shared) borrowed many times or mutably borrowed once. `PyRef::borrow` (immutable borrow) panics with 'Already mutably borrowed' when the object is currently held as a `PyRefMut`. This replaces an `Err(PyBorrowError)` with a panic at the call site (`#[track_caller]`).","triggerScenarios":"Calling a Python method on a `#[pyclass]` object while another method already holds `&mut self` (PyRefMut) — e.g. a method that calls back into Python which re-enters the same object; holding `PyRefMut` across a `py.allow_threads` boundary is safe, but re-entering while holding it is not; storing a `PyRef`/`PyRefMut` and calling `borrow` again on the same object nested.","commonSituations":"Recursive callback patterns where a Rust method invokes Python code that touches the same object; iterator methods borrowing while `__next__` also borrows mutably; accidental long-lived `PyRefMut` stored in a struct field.","solutions":["Shorten the `PyRefMut` scope: drop the mutable borrow before invoking Python code that may re-enter the object","Use `try_borrow`/`try_borrow_mut` and handle the `Err` instead of panicking","Wrap re-entrant work in `py.allow_threads(|| ...)` after extracting needed data, releasing the borrow first","Restructure the class to split interior state into a `Py<RefCell<T>>`/lock so re-entrancy is handled gracefully"],"exampleFix":"// before\nfn do_work(&self, py: Python<'_>) {\n    let mut this = self.into_ref_mut(py);\n    this.callback.call0()?; // re-enters object -> panic\n}\n// after\nfn do_work(&self, py: Python<'_>) -> PyResult<()> {\n    let data = self.into_ref(py).data.clone(); // use immutable borrow / clone out\n    let cb = self.callback.clone();\n    py.allow_threads(move || cb.call0())?;\n    Ok(())\n}","handlingStrategy":"try-catch","validationCode":"# Rust: check borrow state before re-entering\nobj.try_borrow(py).map_err(|_| PyRuntimeError::new_err('object busy'))?;","typeGuard":null,"tryCatchPattern":"// Rust\nlet borrowed = match obj.try_borrow(py) {\n    Ok(r) => r,\n    Err(_) => return Err(PyRuntimeError::new_err('already mutably borrowed')),\n};","preventionTips":["Never hold PyRefMut across calls back into Python that may re-enter the object","Use py.allow_threads after cloning needed data out","Prefer try_borrow/try_borrow_mut in re-entrant code paths","Split class state to reduce borrow conflicts"],"tags":["rust","pyo3","borrow-checker","reentrancy"],"backgroundTag":"already-mutably-borrowed","analyzedSha":"ac9b6899d348be4d54614d060dea53a645a12e36","analyzedAt":"2026-09-05T09:20:35.319Z","contentChangedAt":"2026-09-05T09:20:35.319Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}