PyO3/pyo3 · error

Unknown return value from PyDict_SetDefaultRef: {x}

Error message

Unknown return value from PyDict_SetDefaultRef: {x}

What it means

setdefault_result_from_nonerror_return_code maps PyDict_SetDefaultRef's documented return codes (0 = inserted, 1 = already present) to bool, and panics on any other value. Since SetDefaultRef should never return anything else when no exception is set, this panic signals an unexpected C-API contract violation (interpreter bug, FFI mismatch, or wrong Python version).

Source

Thrown at src/types/dict.rs:478

        inner(
            self,
            key.into_pyobject_or_pyerr(py)?.into_any().as_borrowed(),
            default_value
                .into_pyobject_or_pyerr(py)?
                .into_any()
                .as_borrowed(),
            py,
        )
    }
}

fn setdefault_result_from_nonerror_return_code(code: PyResult<core::ffi::c_int>) -> PyResult<bool> {
    match code? {
        // inserted
        0 => Ok(true),
        // not inserted
        1 => Ok(false),
        x => panic!("Unknown return value from PyDict_SetDefaultRef: {x}"),
    }
}

impl<'a, 'py> Borrowed<'a, 'py, PyDict> {
    /// Iterates over the contents of this dictionary without incrementing reference counts.
    ///
    /// # Safety
    /// It must be known that this dictionary will not be modified during iteration,
    /// for example, when parsing arguments in a keyword arguments dictionary.
    pub(crate) unsafe fn iter_borrowed(self) -> BorrowedDictIter<'a, 'py> {
        BorrowedDictIter::new(self)
    }
}

fn dict_len(dict: &Bound<'_, PyDict>) -> Py_ssize_t {
    #[cfg(any(PyPy, GraalPy, Py_LIMITED_API, Py_GIL_DISABLED))]
    unsafe {
        ffi::PyDict_Size(dict.as_ptr())

View on GitHub (pinned to ac9b6899d3)

Solutions

  1. Verify the Python headers/ABI in use match the running interpreter version
  2. Update PyO3 to the latest patch release for 3.13+ SetDefaultRef fixes
  3. Check that PyDict_SetDefaultRef returns PySet_Contains-style codes and no exception was set; inspect via PyErr_Occurred before panicking
  4. As a workaround, use a non-SetDefaultRef code path (older setdefault implementation) or pin a supported Python version

Example fix

// before
let existed = dict.setdefault(key, value)?; // panics on unexpected code
// after
if unsafe { ffi::PyErr_Occurred().is_null() } {
    let existed = dict.setdefault(key, value)?;
} else { /* handle PyErr */ }
Defensive patterns

Strategy: validation

Validate before calling

if unsafe { pyo3::ffi::PyErr_Occurred() }.is_null() {
    // SetDefaultRef path is safe to attempt
} else { /* clear/handle existing exception first */ }

Try / catch

let r = std::panic::catch_unwind(|| dict.setdefault(key, value)); match r { Ok(res) => res, Err(_) => { /* fallback to contains_key + get_item */ } }

Prevention

When it happens

Trigger: Calling PyDict::setdefault (the PyDict_SetDefaultRef-backed path, Python 3.13+) where the C API returns a code other than 0/1 — e.g. an ffi signature mismatch or a non-conforming interpreter.

Common situations: Using a Python build whose PyDict_SetDefaultRef behaves differently than declared; mixed-version headers/ABI; custom CPython forks. Rare in practice.

Related errors


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