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

  1. Look at the unraisable exception printed before the panic — it contains the actual Python error
  2. Wrap raw FFI calls to check for NULL yourself before converting (Py::from_owned_ptr_or_err) and return a proper PyResult
  3. 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

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


AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05). Data as JSON: /api/errors/7e78f9273846fd94. Report an issue: GitHub.