The Problem with Positional Types

If you've used tuples as R for a while, you've probably already hit the wall. Let's make it explicit.

The Tuple Explosion

Two dependencies: perfectly readable.

#![allow(unused)]
fn main() {
Effect<A, E, (Database, Logger)>
}

Five dependencies: which is which?

#![allow(unused)]
fn main() {
Effect<A, E, (Pool, Pool, Logger, Config, HttpClient)>
//            ^^^^ two Pools — which is the main DB and which is the cache?
}

Tuples are positional. (Pool, Pool, ...) is ambiguous — both fields have the same type. There's no way to distinguish them except by index, and index-based access is error-prone and breaks silently when you reorder the tuple.

The Fragility Problem

Positional types are fragile under change. Say your function started with:

#![allow(unused)]
fn main() {
fn foo() -> Effect<A, E, (Database, Logger)>
//                        0         1
}

Now a teammate adds Config between them:

#![allow(unused)]
fn main() {
fn foo() -> Effect<A, E, (Database, Config, Logger)>
//                        0         1       2
}

Every caller that was providing a tuple (db, log) must be updated to (db, config, log). The change in position is invisible to the type system — the compiler won't tell you where the old index references are. It's a silent bomb.

The Same-Type Collision

The deeper problem: Rust can't distinguish Pool for the main database from Pool for the cache. They're the same type. Positional tuples just accept both:

#![allow(unused)]
fn main() {
// V1: provide (main_pool, cache_pool)
// V2: accidentally swap them
effect.provide((cache_pool, main_pool))  // compiles, wrong at runtime
}

No compile error. Wrong behaviour. Possibly wrong for months before you notice.

What We Actually Need

We need a way to give each dependency a name — a compile-time identifier that's independent of its type and its position in any list.

What if:

  • Database meant "the tagged Pool known as DatabaseTag"
  • Cache meant "the tagged Pool known as CacheTag"

Then you couldn't accidentally swap them — they'd be different types even though both are Pool underneath.

That's exactly what Tags provide.