PyO3/pyo3 · info

frozenset should always be iterable

Error message

frozenset should always be iterable

What it means

PyO3 wraps a `frozenset` iterator as `BoundFrozenSetIterator`, constructing a `PyIterator` from the set. The `.expect("frozenset should always be iterable")` asserts a CPython invariant: frozensets are always iterable, so `PyIterator::from_object` should never fail. Reaching this panic means an object masquerading as, or corrupting, a `frozenset` was passed, or the type object's `tp_iter` was patched.

Source

Thrown at src/types/frozenset.rs:227

    }
}

impl<'py> IntoIterator for &Bound<'py, PyFrozenSet> {
    type Item = Bound<'py, PyAny>;
    type IntoIter = BoundFrozenSetIterator<'py>;

    /// Returns an iterator of values in this set.
    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

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

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

impl<'py> Iterator for BoundFrozenSetIterator<'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("frozenset 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. Verify with `isinstance(x, frozenset)` (not just a type-pointer match) before passing objects where the panic occurs
  2. Remove any monkeypatching of builtin frozenset types/slots
  3. Upgrade pyo3 and reproduce with a minimal snippet; report to PyO3 if hit in vanilla CPython
  4. Check for C extension corruption or mixed Python runtimes in the process

Example fix

# before
fake = Mock(spec=frozenset)  # spec is not a real frozenset
rust_iter_fn(fake)
# after
real = frozenset(fake)
rust_iter_fn(real)
Defensive patterns

Strategy: type-guard

Validate before calling

# Python: pass real frozensets only
isinstance(value, frozenset) or raise TypeError('expected frozenset')

Type guard

def is_real_frozenset(value) -> bool:
    return type(value) is frozenset

Prevention

When it happens

Trigger: Passing an object whose type claims to be a frozenset (subclass or mocked `Py_TYPE` swap) but whose iterator cannot be created; monkeypatching `frozenset` type slots; memory corruption at interpreter teardown.

Common situations: Test doubles / mocks that impersonate frozensets; C-API-level tampering with builtin types (some exotic runtime patches); essentially unreachable in normal code — mostly encountered in corrupted environments.

Related errors


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