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
- Avoid unchecked casts; use `PySet::extract`/`downcast` (checked) before iterating
- If you cannot guarantee the type, iterate via `PyIterator::from_object` and handle the error yourself
- 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
- Avoid cast_into_unchecked on untrusted objects
- Only obtain Bound<PySet> via extract/downcast
- Validate interpreter behavior if embedding RustPython/GraalPy
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
- set iteration should be infallible
- frozenset iteration should be infallible
- 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/28499aa8041af166.
Report an issue: GitHub.