RyanCodrai/turbovec · error · io::Error

{} ids for {} rows

Error message

{} ids for {} rows

What it means

IdMap::from_index_and_ids requires the external-id vector to have exactly one id per row of the inner TurboQuantIndex. A length mismatch means the id metadata and the index rows disagree, so an InvalidData io::Error with the counts is returned instead of building a misaligned map.

Source

Thrown at turbovec/src/id_map.rs:904

        let l = crate::io_v7::load(path, 0, 1)?;
        // See TurboQuantIndex::load_v7 — an unclaimed snapshot loads
        // unbound so the first sync claims it.
        let bind = (l.cursor.nonce != crate::io_v7::UNCLAIMED_NONCE).then_some(path);
        Self::from_v7_load(l, bind)
    }

    /// Wrap an already-built index with an id table.
    ///
    /// Used by [`crate::convert`], which decodes a file into codes plus
    /// ids and needs to re-emit it: the ids are validated for duplicates
    /// exactly as a load does, since a table with a repeat cannot answer
    /// `remove` or `contains` unambiguously.
    pub(crate) fn from_index_and_ids(
        inner: TurboQuantIndex,
        slot_to_id: Vec<u64>,
    ) -> std::io::Result<Self> {
        if slot_to_id.len() != inner.len() {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("{} ids for {} rows", slot_to_id.len(), inner.len()),
            ));
        }
        let mut sorted = slot_to_id.clone();
        sorted.sort_unstable();
        if sorted.windows(2).any(|w| w[0] == w[1]) {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                "duplicate ids",
            ));
        }
        Ok(Self {
            inner,
            slot_to_id,
            id_to_slot: std::sync::OnceLock::new(),
            sorted_ids: std::sync::Mutex::new(sorted),
            deferred_added: std::sync::Mutex::new(Default::default()),

View on GitHub (pinned to ccab9f325e)

Solutions

  1. Regenerate the id vector so it has exactly one entry per index row in the same order.
  2. Ensure the index and ids come from the same build/checkpoint — rebuild both together.
  3. If ids were filtered, apply the same filtering to the index (or vice versa) before calling from_index_and_ids.

Example fix

// before
let map = IdMap::from_index_and_ids(index, stale_ids)?; // 1000 ids for 900 rows
// after
assert_eq!(stale_ids.len(), index.len(), "ids/index length mismatch");
let map = IdMap::from_index_and_ids(index, stale_ids)?;
Defensive patterns

Strategy: validation

Validate before calling

if ids.len() != index.len() {
    return Err(format!("{} ids for {} rows", ids.len(), index.len()));
}

Try / catch

let map = IdMap::from_index_and_ids(index, ids)
    .map_err(|e| { log::error!("id map build failed: {e}"); BuildError::IdMismatch })?;

Prevention

When it happens

Trigger: Constructing an IdMap (or wrapping API) with slot_to_id.len() != inner.len() — e.g. ids truncated, duplicated rows filtered from the index without updating ids, or loading an ids file from a different index build.

Common situations: Rebuilding an index with remove/rebuild and reusing a stale id vector; loading mismatched artifacts (index from one checkpoint, ids from another); off-by-one in a serialization step that dropped ids.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of RyanCodrai/turbovec@ccab9f325e (2026-09-06). Data as JSON: /api/errors/e84aac4e50b5ae9d. Report an issue: GitHub.