PyO3/pyo3 · error

0 < N <= 12

Error message

0 < N <= 12

What it means

`array_into_tuple` converts a fixed-size Rust array of `Bound<PyAny>` into a `PyTuple`, asserting `0 < N <= 12` when converting `N` to `Py_ssize_t` for `PyTuple_New`. The bound exists because pyo3's `IntoPyTuple` impls are only generated for arrays of size 1–12; `N == 0` would create a null-item tuple path and larger arrays aren't supported by the trait impls.

Source

Thrown at src/types/tuple.rs:954

                    // SAFETY: index guaranteed in bounds by the length check
                    unsafe { t.get_borrowed_item_unchecked($n) }
                        .extract::<$T>()
                        .map_err(Into::into)?,
                )+))
            } else {
                Err(wrong_tuple_length(t, $length))
            }
        }
    }
});

fn array_into_tuple<'py, const N: usize>(
    py: Python<'py>,
    array: [Bound<'py, PyAny>; N],
) -> Bound<'py, PyTuple> {
    #[cfg(not(RustPython))]
    unsafe {
        let ptr = ffi::PyTuple_New(N.try_into().expect("0 < N <= 12"));
        let tup = ptr.assume_owned(py).cast_into_unchecked();
        for (index, obj) in array.into_iter().enumerate() {
            #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
            ffi::PyTuple_SET_ITEM(ptr, index as ffi::Py_ssize_t, obj.into_ptr());
            #[cfg(any(Py_LIMITED_API, PyPy, GraalPy))]
            ffi::PyTuple_SetItem(ptr, index as ffi::Py_ssize_t, obj.into_ptr());
        }
        tup
    }

    // SAFETY: array is layout compatible with *const *mut crate::PyObject
    // and does not steal the bound reference.
    #[cfg(RustPython)]
    unsafe {
        ffi::PyTuple_FromArray(array.as_ptr().cast(), N.try_into().expect("0 < N <= 12"))
            .assume_owned(py)
            .cast_into_unchecked()
    }

View on GitHub (pinned to ac9b6899d3)

Solutions

  1. Use a `Vec<Bound<PyAny>>` or `&[Bound<PyAny>]` instead of a >12-element array
  2. For empty arrays, use `PyTuple::empty(py)` explicitly
  3. Ensure the array element type is correct so `try_into` infers N within 1..=12

Example fix

// before: 13-element array
let tup = array13.into_pytuple(py);
// after
let tup = PyTuple::new(py, array13.as_slice())?;
Defensive patterns

Strategy: validation

Validate before calling

const fn array_size_ok<const N: usize>() -> bool { N > 0 && N <= 12 }
// compile-time guard
const _: () = assert!(array_size_ok::<N>());

Type guard

fn convertible_array<const N: usize>(a: [Bound<'_, PyAny>; N]) -> Option<[Bound<'_, PyAny>; N]> {
    (1..=12).contains(&N).then_some(a)
}

Try / catch

// runtime guard before conversion
let tup = if N == 0 { PyTuple::empty(py) } else { array.into_pytuple(py) };

Prevention

When it happens

Trigger: Calling `.into_py_tuple()`/`PyTuple::new` paths on a `[Bound<PyAny>; N]` array with `N == 0` (e.g. `[]` with inferred element type `Bound<PyAny>`) or `N > 12`.

Common situations: Passing an empty array literal to tuple conversion after a `Vec::try_into()`, or upgrading pyo3 code that previously used a Vec and now uses a >12-element array.

Related errors


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