transact-rs/sqlx · error
PgBindIter is only used once
Error message
PgBindIter is only used once
What it means
`PgBindIter` wraps an iterator for one-time binding as a SQL parameter, backed by an `Option`/`Take`-like inner field. Because encoding consumes the iterator, encoding the same value twice is invalid; the library panics via `.expect("PgBindIter is only used once")` in `encode_by_ref` when the inner value was already taken. This guards against silently re-using a half-consumed iterator, which would encode wrong data.
Source
Thrown at sqlx-postgres/src/bind_iter.rs:140
if iter.next().is_some() {
let iter_size = std::cmp::max(lower_size_hint, OVERFLOW);
return Err(format!("encoded iterator is too large for Postgres: {iter_size}").into());
}
// set the length now that we know what it is.
buf[len_start..(len_start + 4)].copy_from_slice(&count.to_be_bytes());
Ok(IsNull::No)
}
}
impl<'q, I> Encode<'q, Postgres> for PgBindIter<I>
where
I: Iterator,
<I as Iterator>::Item: Type<Postgres> + Encode<'q, Postgres>,
{
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
Self::encode_inner(self.0.take().expect("PgBindIter is only used once"), buf)
}
fn encode(self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError>
where
Self: Sized,
{
Self::encode_inner(
self.0.into_inner().expect("PgBindIter is only used once"),
buf,
)
}
}
View on GitHub (pinned to 03af8bcc57)
Solutions
- Create a fresh `PgBindIter` (from a new/cloned iterator or factory closure) for each query execution
- If the source collection is reusable, wrap a `Vec`/slice rather than a one-shot iterator, or clone the underlying collection per execution
- Avoid passing the same PgBindIter by reference into multiple `.bind()` calls
- If retrying queries, rebuild all bound arguments inside the retry loop
Example fix
// before let iter = PgBindIter::new(rows.into_iter()); let q1 = query!().bind(&iter); // ok let q2 = query!().bind(&iter); // panics: only used once // after let q1 = query!().bind(PgBindIter::new(rows.iter().cloned())); let q2 = query!().bind(PgBindIter::new(rows.iter().cloned()));
Defensive patterns
Strategy: validation
Validate before calling
fn fresh_bind<'q, I>(src: &[I::Item]) -> PgBindIter<std::vec::IntoIter<I::Item>>
where I: Iterator, I::Item: Clone {
PgBindIter::new(src.to_vec().into_iter())
} Try / catch
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| value.encode_by_ref(&mut buf)))
.unwrap_or_else(|_| panic!("PgBindIter already consumed; create a new one")); Prevention
- Treat PgBindIter as single-use: never store it in structs that outlive one query
- Rebuild bound arguments inside retry loops
- Prefer binding collections (Vec/slice) when the data must be reused
- If you must reuse, keep a source collection and clone-into a new PgBindIter each bind
When it happens
Trigger: Encoding a `PgBindIter` value by reference more than once — e.g. passing the same `&PgBindIter` to two query executions, or a driver code path that calls `encode_by_ref` twice on the same argument (such as in prepared-statement re-execution or retry logic that re-encodes arguments).
Common situations: Re-running a query in a loop with the same bound value; caching an encoded argument and re-encoding; sqlx internal paths that encode arguments once at prepare time and again at execute time.
Related errors
- failed to parse DATABASE_URL
- unimplemented!()
- VARBIT length mismatch.
- cleanup_test() invoked outside `#[sqlx::test]`
- DATABASE_URL must be set
AI-assisted analysis of transact-rs/sqlx@03af8bcc57 (2026-09-03).
Data as JSON: /api/errors/bad698bdb5b6c206.
Report an issue: GitHub.