PyO3/pyo3 · error

set should always be iterable

Error message

set should always be iterable

What it means

`BoundSetIterator::new` converts a `PySet` into a `PyIterator` and asserts this always succeeds because Python sets are contractually iterable. The panic fires only if `PyIterator::from_object` fails, i.e. the object reaching this code is not actually iterable — an internal invariant breach, usually meaning a non-set object was downcast into `PySet` unsafely.

Source

Thrown at src/types/set.rs:262

    type Item = Bound<'py, PyAny>;
    type IntoIter = BoundSetIterator<'py>;

    /// Returns an iterator of values in this set.
    ///
    /// # Panics
    ///
    /// If PyO3 detects that the set is mutated during iteration, it will panic.
    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

/// PyO3 implementation of an iterator for a Python `set` object.
pub struct BoundSetIterator<'py>(Bound<'py, PyIterator>);

impl<'py> BoundSetIterator<'py> {
    pub(super) fn new(set: Bound<'py, PySet>) -> Self {
        Self(PyIterator::from_object(&set).expect("set should always be iterable"))
    }
}

impl<'py> Iterator for BoundSetIterator<'py> {
    type Item = Bound<'py, super::PyAny>;

    /// Advances the iterator and returns the next value.
    fn next(&mut self) -> Option<Self::Item> {
        self.0
            .next()
            .map(|result| result.expect("set iteration should be infallible"))
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let len = ExactSizeIterator::len(self);
        (len, Some(len))
    }

View on GitHub (pinned to ac9b6899d3)

Solutions

  1. Avoid unchecked casts; use `PySet::extract`/`downcast` (checked) before iterating
  2. If you cannot guarantee the type, iterate via `PyIterator::from_object` and handle the error yourself
  3. Verify the interpreter build if using RustPython/GraalPy

Example fix

// before
let set: &Bound<PySet> = obj.cast_into_unchecked();
for x in set.iter() { ... }
// after
let set: &Bound<PySet> = obj.downcast()?;
for x in set.iter() { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

if not isinstance(obj, set):
    raise TypeError("expected a set")

Type guard

fn as_set<'py>(obj: &Bound<'py, PyAny>) -> PyResult<&Bound<'py, PySet>> {
    obj.downcast::<PySet>()
}

Try / catch

// checked conversion before the panicking iterator
let set = obj.downcast::<PySet>()?;
for item in set.iter() { /* ... */ }

Prevention

When it happens

Trigger: Constructing a set iterator from a `Bound<'py, PySet>` that was produced by an unsafe/incorrect cast (e.g. `cast_into_unchecked` on an object that is not a set), typically under RustPython/GraalPy or misbehaving FFI code.

Common situations: Unsafe downcasts of arbitrary Python objects to `PySet`, embedding alternate interpreters where type checks differ, or corrupted objects from bad FFI refcount handling.

Related errors


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