RyanCodrai/turbovec · error · io::Error

cannot sync a lazy index that has never seen an add or calib

Error message

cannot sync a lazy index that has never seen an add or calibrate

What it means

sync/save on a lazy index fails with InvalidInput when self.dim is None — the lazily-built index has never had a vector added nor been calibrated, so there is no dimension to persist. The library refuses to write a meaningless empty file instead of producing an unloadable artifact.

Source

Thrown at turbovec/src/lib.rs:1822

    /// One full v7 image of this index, in memory.
    pub(crate) fn v7_image(&self, kind: u8, ids_full: Option<&[u64]>) -> std::io::Result<Vec<u8>> {
        self.with_sync_source(kind, ids_full, io_v7::image_bytes)
    }

    /// Bytes [`Self::v7_image`] would produce, without building it.
    pub(crate) fn v7_image_len(&self, kind: u8, ids_full: Option<&[u64]>) -> std::io::Result<usize> {
        self.with_sync_source(kind, ids_full, io_v7::image_len)
    }

    pub(crate) fn sync_v7_impl(
        &mut self,
        path: &Path,
        kind: u8,
        ids_full: Option<&[u64]>,
    ) -> std::io::Result<()> {
        let Some(dim) = self.dim else {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                "cannot sync a lazy index that has never seen an add or calibrate",
            ));
        };
        if self.blocked.get().is_none() {
            self.packed();
        }
        if self.boundaries.get().is_none() || self.centroids.get().is_none() {
            let (b, c) = codebook::codebook(self.bit_width, dim);
            let _ = self.boundaries.set(b);
            let _ = self.centroids.set(c);
        }
        let geo = io_v7::Geo {
            kind,
            dim,
            bit_width: self.bit_width,
            n_calib: self.tqplus_shift.len(),
        };

View on GitHub (pinned to ccab9f325e)

Solutions

  1. Add at least one vector or run calibrate() before syncing
  2. Check that ingestion succeeded (no silent empty batch) before syncing
  3. Skip the sync when the index is empty
  4. Guard the sync behind a check that data was actually loaded

Example fix

// before
let mut idx = Index::lazy();
idx.sync(path, KIND, None)?; // Err
// after
let mut idx = Index::lazy();
for v in vectors { idx.add(v)?; }
idx.sync(path, KIND, None)?;
Defensive patterns

Strategy: validation

Validate before calling

if idx.dim().is_none() {
    return Err(anyhow!("index is empty; add vectors or calibrate before sync"));
}
idx.sync(path, KIND, None)?;

Type guard

fn syncable(idx: &Index) -> bool { idx.dim().is_some() }

Try / catch

match idx.sync(path, KIND, None) {
    Err(e) if e.to_string().contains("never seen an add or calibrate") => {
        eprintln!("skipping sync: index is empty");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling sync (or the save path that takes kind/ids_full) on a freshly constructed lazy Index before any add() or calibrate() call.

Common situations: Error-handling code that syncs unconditionally after a failed build; scaffolding that wires up persistence before data ingestion; tests instantiating an index and immediately persisting it.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.


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