PyO3/pyo3 · error
Already mutably borrowed
Error message
Already mutably borrowed
What it means
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]`).
Source
Thrown at src/pycell.rs:311
#[inline]
pub fn as_ptr(&self) -> *mut ffi::PyObject {
self.inner.as_ptr()
}
/// Returns an owned raw FFI pointer represented by self.
///
/// # Safety
///
/// The reference is owned; when finished the caller should either transfer ownership
/// of the pointer or decrease the reference count (e.g. with [`pyo3::ffi::Py_DecRef`](crate::ffi::Py_DecRef)).
#[inline]
pub fn into_ptr(self) -> *mut ffi::PyObject {
self.inner.clone().into_ptr()
}
#[track_caller]
pub(crate) fn borrow(obj: &Bound<'py, T>) -> Self {
Self::try_borrow(obj).expect("Already mutably borrowed")
}
pub(crate) fn try_borrow(obj: &Bound<'py, T>) -> Result<Self, PyBorrowError> {
let cell = obj.get_class_object();
cell.ensure_threadsafe();
cell.borrow_checker()
.try_borrow()
.map(|_| Self { inner: obj.clone() })
}
}
impl<'p, T> PyRef<'p, T>
where
T: PyClass,
T::BaseType: PyClass,
{
/// Gets a `PyRef<T::BaseType>`.
///View on GitHub (pinned to ac9b6899d3)
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
Example fix
// before
fn do_work(&self, py: Python<'_>) {
let mut this = self.into_ref_mut(py);
this.callback.call0()?; // re-enters object -> panic
}
// after
fn do_work(&self, py: Python<'_>) -> PyResult<()> {
let data = self.into_ref(py).data.clone(); // use immutable borrow / clone out
let cb = self.callback.clone();
py.allow_threads(move || cb.call0())?;
Ok(())
} Defensive patterns
Strategy: try-catch
Validate before calling
# Rust: check borrow state before re-entering
obj.try_borrow(py).map_err(|_| PyRuntimeError::new_err('object busy'))?; Try / catch
// Rust
let borrowed = match obj.try_borrow(py) {
Ok(r) => r,
Err(_) => return Err(PyRuntimeError::new_err('already mutably borrowed')),
}; Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Already borrowed
- this object is already borrowed
- this object is already borrowed
- Attaching a thread to the interpreter is currently prohibite
- Neither abi3 or abi3t features are enabled
AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05).
Data as JSON: /api/errors/fe7a96137cc232dc.
Report an issue: GitHub.