Composable, observable, functional memory cache simulation.
A top-level Memory trait requires read/write methods returning the requested data (if read) alongside auxiliary information of type specified by the implementor.
A cache, like SetAssociativeCache (a standard write-back write-allocating flush-on-evict set-associative cache) is constructed with a backend implementing Memory, an implementor of EvictionPolicy, and its set/way geometry. Caches also satisfy the read/write API and therefore implement Memory as well. This trait-basd hierarchy makes it ergonomic to layer caches of different kind.
Defining a new cache is simple: create a struct holding its relevant data structures (e.g., a tag array or data array) + an implementor of Memory to use as a backing store and define the auxiliary information that reads/writes to this cache are to return. These data structures may additionally be wrapped in performance model-implementing traits, which may be completely decoupled with the cache implementation(!) if one desires.