Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.