# Stable ID generation

> **CocoIndex v1, Rust SDK (experimental).** This page documents the CocoIndex **v1** Rust SDK (the `cocoindex` crate). The Rust SDK is experimental: any release may change or remove its API, and it is not yet published on crates.io. The Python SDK pages are at https://cocoindex.io/docs/.
>
> Source: https://cocoindex.io/docs/rust/common_resources/id_generation/ · Rust docs index: https://cocoindex.io/docs/rust/llms.txt · Rust SDK tour: https://github.com/cocoindex-io/cocoindex/blob/main/rust/sdk/SHOWCASE.md · Rust examples: https://github.com/cocoindex-io/cocoindex/tree/main/examples/rust

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

| API | Same `dep` produces... | Use when... |
|-----|------------------------|-------------|
| `generate_id(&ctx, &dep)` | **Same** ID every time | Each unique input maps to exactly one ID |
| `id_gen.next_id(&ctx, &dep)` | **Distinct** ID each call | You need multiple IDs for potentially non-distinct inputs |

The same distinction applies to `generate_uuid` vs `UuidGenerator`.

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.

```rust
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.

```rust
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](/docs/rust/programming_guide/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.
