PyO3/pyo3 · critical
PyObject pointer is null
Error message
PyObject pointer is null
What it means
panic_on_null handles FFI results that return a NULL PyObject. It first checks whether Python's error indicator is set and writes it as unraisable (so the real cause isn't lost), then panics because a null pointer cannot be safely wrapped in a Py/Bound.
Source
Thrown at src/instance.rs:2429
/// Casts the `Py<T>` to a concrete Python object type without checking validity.
///
/// # Safety
///
/// Callers must ensure that the type is valid or risk type confusion.
#[inline]
pub unsafe fn cast_bound_unchecked<'py, U>(&self, py: Python<'py>) -> &Bound<'py, U> {
// Safety: caller has upheld the safety contract
unsafe { self.bind(py).cast_unchecked() }
}
}
#[track_caller]
#[cold]
fn panic_on_null(py: Python<'_>) -> ! {
if let Some(err) = PyErr::take(py) {
err.write_unraisable(py, None);
}
panic!("PyObject pointer is null");
}
#[cfg(test)]
mod tests {
use super::{Bound, IntoPyObject, Py};
#[cfg(all(feature = "macros", panic = "unwind"))]
use crate::exceptions::PyValueError;
#[allow(unused_imports, reason = "conditionally used")]
use crate::platform::prelude::*;
use crate::test_utils::generate_unique_module_name;
#[cfg(all(feature = "macros", panic = "unwind"))]
use crate::test_utils::UnraisableCapture;
use crate::types::{dict::IntoPyDict, PyAnyMethods, PyCapsule, PyDict, PyString};
use crate::{ffi, Borrowed, IntoPyObjectExt, PyAny, PyResult, Python};
use core::ffi::CStr;
#[test]
fn test_call() {View on GitHub (pinned to ac9b6899d3)
Solutions
- Look at the unraisable exception printed before the panic — it contains the actual Python error
- Wrap raw FFI calls to check for NULL yourself before converting (Py::from_owned_ptr_or_err) and return a proper PyResult
- Ensure calls happen while the interpreter is fully initialized and errors are checked between FFI calls
Example fix
// before
let obj = unsafe { Py::from_owned_ptr(py, ffi::PyImport_Import(...)) };
// after
let obj = unsafe { Py::from_owned_ptr_or_err(py, ffi::PyImport_Import(...))? }; Defensive patterns
Strategy: try-catch
Validate before calling
// check raw FFI results before converting
let ptr = unsafe { ffi_call() };
if ptr.is_null() {
return Err(PyErr::take(py).unwrap_or_else(|| PySystemError::new_err("null result")));
} Type guard
fn valid_pyobject(ptr: *mut ffi::PyObject) -> bool { !ptr.is_null() } Try / catch
// prefer fallible constructors
let obj: PyResult<Py<PyAny>> = unsafe { Py::from_owned_ptr_or_err(py, ptr) }; Prevention
- Use *_or_err FFI wrappers instead of infallible *_ptr variants
- Check the Python error indicator after every fallible C API call
- Never call Python C APIs during interpreter shutdown
When it happens
Trigger: Calling unsafe/PyO3 APIs that wrap a raw FFI result (e.g. Py::from_owned_ptr-style paths, constructor calls, attribute lookups) when the underlying C API returned NULL.
Common situations: Python exceptions during object construction or method calls surfaced through raw pointers; calling APIs during interpreter shutdown where allocation fails; misuse of unsafe Py APIs with pointers from foreign code.
Related errors
- attempted to fetch exception but none was set
- Converting PyErr arguments failed: {}
- Unknown Py_GIL_DISABLED value
- invalid hex encoding
- `pyo3_build_config::get()` requires a direct dependency on `
AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05).
Data as JSON: /api/errors/7e78f9273846fd94.
Report an issue: GitHub.