cocoindex-io/cocoindex · critical

Environment::provide: type `{}` has already been provided

Error message

Environment::provide: type `{}` has already been provided

What it means

Environment::provide() enforces that each Rust type acts as a unique key in the environment's typed state map. Panicking means you called .provide::<T>() twice for the same type T on the same Environment builder. This is a programmer invariant violation: the second value would silently shadow the first, so the library aborts instead.

Source

Thrown at rust/sdk/cocoindex/src/app.rs:154

    pub fn lmdb_map_size(mut self, value: usize) -> Self {
        self.lmdb_map_size = value;
        self
    }

    /// Limit the number of concurrently processing components (per app).
    pub fn max_inflight_components(mut self, value: usize) -> Self {
        self.max_inflight_components = Some(value);
        self
    }

    /// Inject a shared resource. Retrieved later via `ctx.get::<T>()`.
    /// The type IS the key — each type can only be provided once.
    ///
    /// # Panics
    /// Panics if a value of type `T` has already been provided.
    pub fn provide<T: Send + Sync + 'static>(mut self, value: T) -> Self {
        if self.state.contains::<T>() {
            panic!(
                "Environment::provide: type `{}` has already been provided",
                std::any::type_name::<T>()
            );
        }
        self.state.insert(value);
        self
    }

    /// Inject a shared resource by named [`ContextKey`].
    ///
    /// Named keys are useful when multiple resources share the same Rust type
    /// and carry change-tracking.
    ///
    /// # Panics
    /// Panics if a change-tracked key cannot fingerprint the provided value.
    pub fn provide_key<T: Send + Sync + 'static>(mut self, key: &ContextKey<T>, value: T) -> Self {
        self.context
            .provide(key, value)

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Remove the duplicate .provide() call for that type.
  2. Wrap one of the values in a distinct newtype so each type is unique (struct SecondaryPool(Pool)).
  3. If the value must be replaced, rebuild the Environment instead of re-providing on the same builder.

Example fix

// before
env.provide(pool).provide(pool2) // both asyncpg::Pool

// after
struct ReplicaPool(asyncpg::Pool);
env.provide(pool).provide(ReplicaPool(pool2))
Defensive patterns

Strategy: type-guard

Type guard

// guard before providing
fn can_provide<T: Send + Sync + 'static>(env: &EnvironmentBuilder, _v: &T) -> bool {
    !env.contains::<T>()
}

Prevention

When it happens

Trigger: Chaining .provide::<Pool>(p1).provide::<Pool>(p2), or providing the same concrete type from two code paths that both build the environment (e.g. a helper called twice).

Common situations: Refactoring two distinct resource types into the same struct/type; calling provide for a type already provided inside a shared lifespan helper; copy-pasted builder chains in tests.

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.

Related errors


AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08). Data as JSON: /api/errors/18476d581c129588. Report an issue: GitHub.