# Thread-Safe Architecture Using Arc<T> for Infrastructure Dependencies in Forge Services

> Learn how Forge services use Arc<T> to create thread-safe architecture for infrastructure dependencies. Achieve cheap cloning across async tasks avoiding mutable refs or complex synchronization.

- Repository: [Forge Code/forgecode](https://github.com/antinomyhq/forgecode)
- Tags: architecture
- Published: 2026-04-08

---

**Forge services achieve thread-safe sharing by wrapping every infrastructure dependency in an `Arc<T>`, enabling cheap cloning across async tasks without requiring mutable references or complex synchronization primitives.**

The `antinomyhq/forgecode` codebase implements a rigorous **thread-safe architecture using `Arc<T>` for infrastructure dependencies** that allows services to be duplicated for each request while maintaining safe concurrent access. By atomically reference-counting external resources like repositories and configuration providers, Forge eliminates copy overhead and avoids borrow checker constraints. This design follows clean-architecture guidelines documented in [`AGENTS.md`](https://github.com/antinomyhq/forgecode/blob/main/AGENTS.md) and appears throughout the service layer in `crates/forge_services`.

## Why Arc<T> for Shared Ownership

**`Arc<T>`** (atomically reference-counted pointer) provides **shared ownership** across threads without needing mutable references. Cloning an `Arc` only increments a reference counter, making it cheap to duplicate services for concurrent tasks.

When **interior mutability** is required, `Arc<T>` composes cleanly with `RwLock<T>` or `Mutex<T>`, keeping the public API free of `&mut` constraints. This pattern ensures that any number of concurrent tasks can hold a clone of the same service and safely call its async methods.

## The Service Pattern: Arc<T> Storage

Each Forge service stores its infrastructure as a single `Arc<T>` field and exposes a constructor that takes ownership of the arc. Notably, these constructors apply **no trait bounds**, following the clean-architecture guideline that constraints belong only on methods that need them.

In [`crates/forge_services/src/provider_service.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_services/src/provider_service.rs), the `ForgeProviderService` wraps a repository:

```rust
/// Store the repository in an `Arc` for cheap cloning.
pub struct ForgeProviderService<R> {
    repository: Arc<R>,
}

impl<R> ForgeProviderService<R> {
    /// Constructor takes ownership of an `Arc<R>`.
    pub fn new(repository: Arc<R>) -> Self {
        Self { repository }
    }
}

```

*Source:* [`provider_service.rs`](https://github.com/antinomyhq/forgecode/blob/main/provider_service.rs) lines 16–18

## Tuple-Struct Pattern for Single Dependencies

When a service has exactly one dependency, Forge adopts the **tuple-struct** pattern to eliminate boilerplate while keeping the dependency explicit and clone-friendly.

In [`crates/forge_services/src/tool_services/plan_create.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_services/src/tool_services/plan_create.rs), `ForgePlanCreate` uses this approach:

```rust
/// Single-dependency service using the tuple-struct pattern.
pub struct ForgePlanCreate<F>(Arc<F>);

impl<F> ForgePlanCreate<F> {
    pub fn new(infra: Arc<F>) -> Self { Self(infra) }
}

```

*Source:* [`plan_create.rs`](https://github.com/antinomyhq/forgecode/blob/main/plan_create.rs) lines 15–18

This shorthand stores the `Arc<F>` directly in the struct tuple, requiring no named field accessors while maintaining the same thread-safe semantics.

## Composing High-Level Services with Arc<T>

Higher-level services compose lower-level ones simply by storing `Arc<…>` for each component. The top-level `ForgeServices` struct in [`crates/forge_services/src/forge_services.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_services/src/forge_services.rs) demonstrates this aggregation:

```rust
pub struct ForgeServices<F> {
    // … many other services omitted for brevity …
    plan_create_service: Arc<ForgePlanCreate<F>>,
    file_read_service:   Arc<ForgeFsRead<F>>,
    // … more services …
}

impl<F> ForgeServices<F> {
    pub fn new(infra: Arc<F>) -> Self {
        Self {
            plan_create_service: Arc::new(ForgePlanCreate::new(infra.clone())),
            file_read_service:   Arc::new(ForgeFsRead::new(infra)),
            // … initialise others …
        }
    }
}

```

*Source:* [`forge_services.rs`](https://github.com/antinomyhq/forgecode/blob/main/forge_services.rs) lines 58–77

Each sub-service receives a cloned `Arc`, ensuring that the entire service tree shares the same infrastructure reference safely across threads.

## Concurrent Usage in Async Tasks

Because every service is cloneable via `Arc`, spawning concurrent tasks requires no additional synchronization code:

```rust
let infra = Arc::new(MyInfrastructure::new());
let provider_service = Arc::new(ForgeProviderService::new(infra.clone()));

tokio::join!(
    async { provider_service.clone().some_async_op().await },
    async { provider_service.clone().another_async_op().await },
);

```

Each async task holds its own `Arc` clone, guaranteeing that the underlying infrastructure is dropped only when the last task completes.

## Summary

- **Arc<T> enables shared ownership** of infrastructure across threads without copying data or requiring `&mut` references.
- **Service constructors** accept `Arc<T>` directly with no trait bounds, delaying constraints to method implementations.
- **Tuple-struct pattern** (`struct Service<T>(Arc<T>)`) provides minimal boilerplate for single-dependency services.
- **High-level composition** aggregates multiple `Arc`-wrapped services, as shown in `ForgeServices`.
- **Testability** improves because mock infrastructure can be wrapped in `Arc::new(MockImpl{})` and injected identically to production code.

## Frequently Asked Questions

### What is the benefit of using Arc<T> over other synchronization primitives?

**`Arc<T>` provides reference counting without locking**, making it ideal for immutable data or cases where interior mutability is handled separately. Unlike `Mutex<T>` or `RwLock<T>`, `Arc` itself does not synchronize access to the data—it only tracks ownership. This allows Forge services to be cloned cheaply (just an atomic increment) while still being **Send + Sync** when the underlying type permits.

### How does Forge handle interior mutability when using Arc<T>?

When a service requires mutation, Forge wraps the data inside **interior mutability primitives** like `RwLock<T>` or `Mutex<T>` *inside* the `Arc`. For example, `Arc<RwLock<Config>>` allows thread-safe mutation while the `Arc` handles ownership. This keeps the service struct itself free of lifetime or mutability constraints, simplifying the public API.

### Why does Forge use tuple-structs for some services?

The **tuple-struct pattern** (`struct Service(Arc<T>)`) eliminates field-naming boilerplate when a service has exactly one dependency. According to [`AGENTS.md`](https://github.com/antinomyhq/forgecode/blob/main/AGENTS.md), this convention maintains explicit dependency injection while reducing code size. It is used in files like [`plan_create.rs`](https://github.com/antinomyhq/forgecode/blob/main/plan_create.rs) for services that act as thin wrappers around a single infrastructure trait.

### How does this architecture improve testability?

Because services depend on `Arc<T>` rather than concrete types, unit tests can inject **mock implementations** wrapped in `Arc::new(MockImpl{})`. The service under test receives the same runtime behavior—cheap cloning and thread-safe access—as it would with production infrastructure, allowing tests to run concurrently without race conditions or complex setup.