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
- Add at least one vector or run calibrate() before syncing
- Check that ingestion succeeded (no silent empty batch) before syncing
- Skip the sync when the index is empty
- 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
- Sync only after ingestion confirms rows added
- Assert non-empty index in save paths
- Order pipelines: build -> add/calibrate -> sync
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.