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
- Verify the Python headers/ABI in use match the running interpreter version
- Update PyO3 to the latest patch release for 3.13+ SetDefaultRef fixes
- Check that PyDict_SetDefaultRef returns PySet_Contains-style codes and no exception was set; inspect via PyErr_Occurred before panicking
- 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
- Keep Python header/ABI versions in sync with the running interpreter
- Pin supported Python versions for PyDict_SetDefaultRef paths (3.13+)
- Update PyO3 when C-API contract fixes ship
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
- dictionary changed size during iteration
- dictionary keys changed during iteration
- Attaching a thread to the interpreter is prohibited while a
- Cannot attach to the Python interpreter while it is finalizi
- Attaching a thread to the interpreter is currently prohibite
AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05).
Data as JSON: /api/errors/9078bb8084c627a2.
Report an issue: GitHub.