PyO3/pyo3 · error
this object is already borrowed
Error message
this object is already borrowed
What it means
`PyClassGuard::into_super` converts a guard of a subclass `T` into a guard of its pyo3 base class `U`. To keep borrow counts consistent it first adds a shared borrow on the base (`try_borrow`, panicking with 'this object is already borrowed' if the base is mutably borrowed) and then releases the borrow of `T`. The panic means the base-class object's borrow state conflicts with the conversion.
Source
Thrown at src/pycell.rs:393
let t_not_frozen = !<T::Frozen as crate::pyclass::boolean_struct::private::Boolean>::VALUE;
let u_frozen = <<T::BaseType as PyClass>::Frozen as crate::pyclass::boolean_struct::private::Boolean>::VALUE;
if t_not_frozen && u_frozen {
// If `T` is mutable subclass of `U` differ, then it is possible that we need to
// release the borrow count now. (e.g. `U` may have a noop borrow checker so
// dropping the `PyRef<U>` later would noop and leak the borrow we currently hold.)
//
// However it's nontrivial, if `U` itself has a mutable base class `V`,
// then the borrow checker of both `T` and `U` is the shared borrow checker of `V`.
//
// But it's really hard to prove that in the type system, the soundest thing we
// can do is just add a borrow to `U` now and then release the borrow of `T`.
self.inner
.as_super()
.get_class_object()
.borrow_checker()
.try_borrow()
.expect("this object is already borrowed");
self.inner
.get_class_object()
.borrow_checker()
.release_borrow()
};
PyRef {
inner: unsafe {
ManuallyDrop::new(self)
.as_ptr()
.assume_owned_unchecked(py)
.cast_into_unchecked()
},
}
}
/// Borrows a shared reference to `PyRef<T::BaseType>`.
///View on GitHub (pinned to ac9b6899d3)
Solutions
- Ensure no `PyRefMut` of the base class is alive when calling `into_super`
- Convert guards to the base only from an immutable (`PyRef`) state
- Use `try_borrow`-based explicit checks before converting
- Restructure to avoid calling `into_super` while a base method is executing
Example fix
// before
fn method(mut this: PyRefMut<'_, Self>) -> PyResult<()> {
let base = this.into_super(); // base already mutably borrowed
base.base_method()
}
// after
fn method(this: PyRef<'_, Self>) -> PyResult<()> {
let base = this.into_super();
base.base_method()
} Defensive patterns
Strategy: type-guard
Validate before calling
// Rust: ensure base is not mutably borrowed before into_super // (call into_super only from PyRef, not PyRefMut)
Type guard
fn can_take_super<'py, T: PyClass>(obj: &Bound<'py, T>) -> bool {
obj.try_borrow().is_ok()
} Prevention
- Call into_super from immutable guards only
- Avoid overlapping base and subclass guards
- Restructure methods so base mutation is not concurrent
- Prefer try_borrow checks in hierarchy navigation
When it happens
Trigger: Calling `into_super()` while the base class portion is mutably borrowed (`PyRefMut` of the base alive); overlapping guards for base and derived instances of the same underlying object; nested `into_super` calls inside methods that already hold mutable borrows.
Common situations: Multi-level `#[pyclass(extends=...)]` hierarchies with methods on both base and subclass calling each other; Python subclass method calling `super()`-like Rust conversions while the base is mutably accessed.
Related errors
- this object is already borrowed
- Already mutably borrowed
- Already borrowed
- Neither abi3 or abi3t features are enabled
- Cannot target an abi3t version below {MINIMUM_SUPPORTED_VERS
AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05).
Data as JSON: /api/errors/e080c7c5d4587d21.
Report an issue: GitHub.