PyO3/pyo3 · error
this object is already borrowed
Error message
this object is already borrowed
What it means
The same 'into_super adds a borrow to the base then releases T' logic in `src/pyclass/guard.rs`: `as_super()` performs `try_borrow` on the base class object and panics with 'this object is already borrowed' if the base is already mutably borrowed. This is the guard (non-Bound) variant of error 85, enforcing that base and derived guards never hold conflicting borrow states.
Source
Thrown at src/pyclass/guard.rs:276
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 a mutable subclass of a frozen `U` base, 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` is frozen but 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.as_super()
.as_class_object()
.borrow_checker()
.try_borrow()
.expect("this object is already borrowed");
self.as_class_object().borrow_checker().release_borrow()
};
PyClassGuard {
ptr: core::mem::ManuallyDrop::new(self).ptr,
marker: PhantomData,
}
}
}
impl<T: PyClass> Deref for PyClassGuard<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
// SAFETY: `PyClassObject<T>` contains a valid `T`, by construction no
// mutable alias is enforced
unsafe { &*self.as_class_object().get_ptr().cast_const() }
View on GitHub (pinned to ac9b6899d3)
Solutions
- Take the mutable borrow on the base only once and pass it down instead of converting guards repeatedly
- Call `into_super` from immutable guards (`PyRef`) only
- Check borrow state with `try_borrow` before conversion
- Refactor the hierarchy so base mutation happens in a separate, non-overlapping call
Example fix
// before let base_mut = base.borrow_mut(); let guard = sub_guard.into_super(); // panic: base mutably borrowed // after drop(base_mut); let guard = sub_guard.into_super();
Defensive patterns
Strategy: type-guard
Validate before calling
// Rust: verify borrowability before as_super let checker_ok = obj.as_class_object().borrow_checker().try_borrow().is_ok();
Prevention
- Take base mutability once and pass it down instead of repeated conversions
- Keep guard lifetimes short and non-overlapping
- Document hierarchy methods that mutate base state
- Test extends= hierarchies under re-entrancy
When it happens
Trigger: `PyClassGuard::into_super`/`as_super` called while a mutable guard of the base class exists; a `&mut self` base method calling into subclass code that converts the guard back to the base; dropping guards out of order such that the base remains mutably borrowed.
Common situations: `#[pyclass(extends=Base)]` subclasses whose subclass methods invoke base `&mut self` methods and then call `into_super`; wrapper/guard-heavy APIs managing nested class hierarchies.
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/caea33d0d0a085b0.
Report an issue: GitHub.