Stable ID generation

Generate IDs and UUIDs that stay stable across incremental runs — generate_id and generate_uuid for pure derivation, and IdGenerator / UuidGenerator for stateful sequences that still reproduce across runs.

Version
v 1.0.14
Last reviewed
Nov 4, 2026
ExperimentalThe Rust SDK is experimental. It is outside CocoIndex's compatibility promise: any release, including a minor one, may change or remove its API. It is not yet published on crates.io.

The ID module (cocoindex::resources::id, re-exported from cocoindex::prelude) provides utilities for generating stable unique IDs and UUIDs that persist across incremental updates.

In an incremental pipeline, using random IDs (like Uuid::new_v4()) means every reprocessing run generates different IDs for the same data — causing unnecessary churn in your targets (deleting old rows, inserting identical ones with new IDs). CocoIndex’s ID utilities produce stable IDs: the same inputs produce the same IDs across runs, so unchanged data keeps its identity and targets only see real changes.

Choosing the right API

APISame dep produces…Use when…
generate_id(&ctx, &dep)Same ID every timeEach unique input maps to exactly one ID
id_gen.next_id(&ctx, &dep)Distinct ID each callYou need multiple IDs for potentially non-distinct inputs

The same distinction applies to generate_uuid vs UuidGenerator.

Different from Python

Every ID function takes the component’s Ctx first: the memo that keeps an ID stable belongs to the processing component, and in Rust the context is explicit.

generate_id / generate_uuid

Async functions that return the same ID/UUID for the same dep value. These are idempotent: calling multiple times with identical dep yields identical results.

use cocoindex::resources::id::{generate_id, generate_uuid};

// Same dep always gets the same ID; "A" twice is one ID, "B" is another.
let ids = vec![
    generate_id(&ctx, &"A").await?,
    generate_id(&ctx, &"B").await?,
    generate_id(&ctx, &"A").await?,
];

// Same for UUIDs.
let uuids = vec![
    generate_uuid(&ctx, &"A").await?,
    generate_uuid(&ctx, &"B").await?,
    generate_uuid(&ctx, &"A").await?,
];

Parameters:

  • ctx — The component’s Ctx.
  • dep — Dependency value that determines the ID/UUID: any Serialize value, passed by reference. The same dep always produces the same result within a component. generate_id_default(&ctx) and generate_uuid_default(&ctx) are the forms with no dependency value.

Returns:

  • generate_id returns a Result<u64> (IDs start from 1; 0 is reserved)
  • generate_uuid returns a Result<uuid::Uuid>

IdGenerator / UuidGenerator

Generators that return a distinct ID/UUID on each call, even when called with the same dep value. The sequence is stable across runs.

Use these when you need multiple IDs for potentially non-distinct inputs, such as splitting text into chunks where chunks may have identical content but still need unique IDs.

use cocoindex::prelude::*;

/// Embed one chunk and declare its row. The shared generator gives each
/// chunk a distinct ID, even when two chunks have the same text.
#[cocoindex::function]
async fn process_chunk(
    ctx: &Ctx,
    chunk: Chunk,
    text: &str,
    filename: &str,
    id_gen: &IdGenerator,
    table: &postgres::TableTarget,
) -> Result<()> {
    let chunk_text = chunk.text(text);
    let id = i64::try_from(id_gen.next_id(ctx, chunk_text).await?)
        .map_err(|_| Error::engine("generated id does not fit in BIGINT"))?;
    table.declare_row(
        ctx,
        &DocEmbedding {
            id,
            filename: filename.to_string(),
            chunk_start: chunk.start.char_offset as i32,
            chunk_end: chunk.end.char_offset as i32,
            text: chunk_text.to_string(),
            embedding: ctx.get_key(&EMBEDDER)?.embed(ctx, chunk_text).await?,
        },
    )
}

// One generator per file, shared by the concurrent calls of `ctx.map`.
let id_gen = IdGenerator::new();
ctx.map(chunks, async |chunk| {
    process_chunk(ctx, chunk, &text, &filename, &id_gen, &table).await
})
.await?;

// Constructor dependencies split the sequences of two generators in one component.
let id_gen = IdGenerator::with_deps(&dep)?;
let id = id_gen.next_id_default(&ctx).await?;

let uuid_gen = UuidGenerator::new();
let uuid = uuid_gen.next_uuid(&ctx, &"same").await?;

next_id and next_uuid take &self: the generator counts how often it has seen each dep behind a mutex, and that ordinal is part of the memo key. One generator can therefore be shared by the concurrent closures of ctx.map, as in examples/rust/text_embedding. Concurrent calls take their ordinals in the order they reach the generator, so give each call a dep that identifies its item, as the chunk text does above. A generator is not Clone: a copy would replay the same ordinals and hand out the same IDs again.

Constructor:

  • IdGenerator::new() / UuidGenerator::new() — Create a generator with no dependency.
  • IdGenerator::with_deps(&deps)? / UuidGenerator::with_deps(&deps)? — Create a generator whose deps (any Serialize value) distinguishes it from other generators within the same processing component. Use distinct deps values for different generator instances.

Methods:

  • async fn next_id(&self, ctx: &Ctx, dep: &D) -> Result<u64> — Generate the next unique integer ID (distinct on each call)
  • async fn next_uuid(&self, ctx: &Ctx, dep: &D) -> Result<Uuid> — Generate the next unique UUID (distinct on each call)
  • next_id_default(&ctx) / next_uuid_default(&ctx) — The same with no per-call dependency.