Generics, Traits, and Erased Workers
Garage uses generics where one algorithm should be specialized for many table schemas, and trait objects where a runtime collection must hold unlike worker types.
One table algorithm, many schemas
The central type is conceptually:
Table<F: TableSchema, R: TableReplication>
TableSchema supplies associated types for partition key, sort key, entry,
and filter. TableReplication supplies placement and read/write quorum policy.
The generic table can then implement insert, get, range reads, RPC encoding,
Merkle synchronization, repair, and garbage collection once.
Associated types express a family invariant: an object table has one coherent combination of key, entry, and filter types. Callers cannot accidentally query it with a bucket-table key.
Behavioral bounds on entries
An Entry<P, S> must be cloneable, serializable, migratable, sendable, and a
Crdt. Those bounds are not decoration. Distributed table code needs to move
entries into async work, encode them for RPC, migrate stored versions, obtain
keys, and merge divergent replicas.
Generic streaming handlers
save_stream<S> and read_and_put_blocks<S> accept any stream of byte chunks
with the required item and pinning behavior. Tests, HTTP bodies, copies, and
POST uploads can reuse the same storage pipeline without sharing one concrete
body type.
Generic request policy owns a resource
RequestStrategy<T> is generic over a value dropped on completion. Most calls
use T = (); block writes use an owned semaphore permit. This avoids a boxed
callback and makes “release this exact resource when the RPC set ends” a
compile-time ownership relationship.
Where dynamic dispatch wins
BackgroundRunner receives Box<dyn Worker>. Merkle, sync, GC, resync, scrub,
and lifecycle workers have different concrete types but must coexist in one
runtime collection. Their hot domain algorithms remain generic; only the
heterogeneous supervision boundary is erased.
That division is a useful rule:
Use generics to preserve relationships inside reusable algorithms. Use a trait object at a collection or plugin boundary whose members are chosen at runtime.