Documentation
¶
Overview ¶
Package cacheobj is the in-process, zero-serialization companion to github.com/ubgo/cache. It holds live Go objects by reference: what you Set is the exact value you Get back, with no codec, no copy, and no boxing.
When to use it ¶
Reach for cacheobj only when the value's usefulness depends on staying live — a *regexp.Regexp, an *http.Client, an open connection, a func, a chan, or any struct with unexported state that would not survive a codec round-trip (for example an ORM entity whose unexported client handle is nil after decoding). For serializable values (DTOs, configs, scalars, []Result) use github.com/ubgo/cache + cache-mem instead — it is strictly more capable there (single-flight, negative caching, redis/pg/tiered backends).
NOT a cache.Cache backend ¶
cacheobj is a DIFFERENT abstraction from github.com/ubgo/cache. The byte cache is []byte-in/[]byte-out so it can work uniformly over the network; cacheobj is typed (Cache[T]) and in-process only. It deliberately does not implement cache.Cache and cannot be swapped into a redis/pg slot. It reuses cache.Stats and cache.EvictionCause solely so observability reads identically across the family.
Mutation footgun ¶
Get returns the SAME reference that was Set — no defensive copy (that is the whole point, and impossible for non-copyable types anyway). Treat cached objects as immutable, or synchronize mutation yourself: a caller mutating a returned pointer mutates what every other caller sees.
Expiry ¶
Expiry is lazy by default: an entry past its TTL is detected and evicted on the Get that touches it. An LRU capacity bound (see WithCapacity) reclaims memory for keys that are never read again; alternatively WithSweepInterval starts a background goroutine that periodically evicts expired entries (call Close to stop it).
Example ¶
The headline use case: cache a live, non-serializable object — here a compiled *regexp.Regexp — and get the exact same instance back, with no recompilation and no serialization.
package main
import (
"fmt"
"regexp"
cacheobj "github.com/ubgo/cache-obj"
)
func main() {
re := cacheobj.New[*regexp.Regexp](cacheobj.WithCapacity(128))
compile := func(pattern string) *regexp.Regexp {
if r, ok := re.Get(pattern); ok {
return r // same compiled program, zero cost
}
r := regexp.MustCompile(pattern)
re.Set(pattern, r)
return r
}
a := compile(`\d+`)
b := compile(`\d+`) // served from cache — identical instance
fmt.Println(a == b)
fmt.Println(a.MatchString("abc123"))
}
Output: true true
Index ¶
- type Cache
- type Option
- type Store
- func (s *Store[T]) Close()
- func (s *Store[T]) Del(key string)
- func (s *Store[T]) Get(key string) (T, bool)
- func (s *Store[T]) Len() int
- func (s *Store[T]) Purge()
- func (s *Store[T]) Remember(key string, ttl time.Duration, fn func() (T, error)) (T, error)
- func (s *Store[T]) Set(key string, v T)
- func (s *Store[T]) SetTTL(key string, v T, ttl time.Duration)
- func (s *Store[T]) Stats() cache.Stats
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Cache ¶
type Cache[T any] interface { // Get returns the live value and true, or the zero value and false on a // miss or expiry. The returned value is the SAME reference that was Set // (no copy). A TTL'd entry found expired is evicted as a side effect. Get(key string) (T, bool) // Set stores v under key using the store's default TTL (see // WithDefaultTTL). An existing entry is replaced. Set(key string, v T) // SetTTL stores v under key with an explicit TTL. A ttl <= 0 means the // entry never expires (it lives until evicted by capacity or deleted). SetTTL(key string, v T, ttl time.Duration) // Del removes key. Deleting an absent key is a no-op. Del does not fire // the OnEvict hook — eviction notifications are for involuntary drops // (capacity, expiry), not explicit removal. Del(key string) // Len reports the current entry count, including any expired entries not // yet swept by a Get. Len() int // Purge removes every entry. Like Del, it does not fire OnEvict. Purge() // Stats returns a point-in-time snapshot using the shared // github.com/ubgo/cache.Stats shape. Counters are cumulative since // construction; Entries is an instantaneous gauge. Stats() cache.Stats }
Cache is the live-object cache contract: values are stored and returned by reference, with no serialization. It is generic over the value type T and is NOT github.com/ubgo/cache.Cache (see the package doc).
All methods are safe for concurrent use.
type Option ¶
type Option func(*config)
Option configures a Store at construction. The option type is non-generic: every knob (capacity, TTL, clock, OnEvict) is independent of the value type T, so options compose cleanly with New[T].
func WithCapacity ¶
WithCapacity bounds the cache to at most n entries using LRU eviction. A non-positive n (the default) means unbounded — entries are removed only by TTL expiry, Del, or Purge.
func WithClock ¶
WithClock overrides the time source, for deterministic TTL tests. Defaults to time.Now.
func WithDefaultTTL ¶
WithDefaultTTL sets the TTL applied by Set. A non-positive d (the default) means Set-stored entries never expire. SetTTL always overrides this.
func WithOnEvict ¶
func WithOnEvict[T any](fn func(key string, v T, cause cache.EvictionCause)) Option
WithOnEvict registers a callback invoked when an entry is dropped involuntarily: by capacity (cause cache.EvictSize) or TTL expiry (cause cache.EvictExpired). It receives the evicted key AND value, so the callback can release resources the value owns (for example, close a *sql.DB). It is NOT called for Del or Purge — those are deliberate, so clean up at the call site. The callback runs while the cache lock is held: keep it fast and do not call back into the cache from it.
T is inferred from the callback's value parameter and must match the cache's value type; a mismatch yields the zero value.
Example ¶
WithOnEvict observes involuntary drops (capacity or expiry) and receives the evicted key AND value — so it can release whatever the value owns. Here capacity is 1, so the second Set evicts the first.
package main
import (
"fmt"
"github.com/ubgo/cache"
cacheobj "github.com/ubgo/cache-obj"
)
func main() {
c := cacheobj.New[string](
cacheobj.WithCapacity(1),
cacheobj.WithOnEvict(func(key, value string, cause cache.EvictionCause) {
fmt.Printf("evicted %q=%q (%s)\n", key, value, cause)
}),
)
c.Set("a", "first")
c.Set("b", "second") // evicts "a" by capacity
}
Output: evicted "a"="first" (size)
func WithSweepInterval ¶
WithSweepInterval starts a background goroutine that, every d, evicts entries whose TTL has elapsed (firing OnEvict with cache.EvictExpired) — reclaiming memory for expired keys that are never read again. A non-positive d (the default) means no sweeper: expiry stays lazy (checked on Get). When you use a sweeper, call Close to stop the goroutine.
type Store ¶
type Store[T any] struct { // contains filtered or unexported fields }
Store is the in-memory Cache[T] implementation returned by New.
func New ¶
New builds an in-memory live-object cache. With no options it is unbounded with no default TTL. See the WithX options for capacity, TTL, eviction hook, and clock.
func (*Store[T]) Close ¶
func (s *Store[T]) Close()
Close stops the background sweeper started by WithSweepInterval. It is idempotent and safe to call even when no sweeper was started (a no-op). The cache remains usable after Close — only the background goroutine stops; expiry reverts to lazy (on Get).
func (*Store[T]) Remember ¶
Remember returns the cached value for key, or loads it via fn and stores it with the given ttl (ttl <= 0 means no expiry). Under concurrent misses for the same key, fn runs exactly once (single-flight) and every caller shares the result.
Loader errors are returned to all waiting callers and are NOT cached — the next call retries. Loaded values are stored by reference, like Set.
fn must not call Remember for the same key (it would deadlock waiting on itself), and should not panic (a panic propagates to the leader; waiters are released but observe the zero value).
Example ¶
Remember is get-or-load with single-flight: the loader runs once on a miss (and once across concurrent misses for the same key), and the result is cached. A second call is served from cache without re-running the loader.
package main
import (
"fmt"
"time"
cacheobj "github.com/ubgo/cache-obj"
)
func main() {
c := cacheobj.New[int]()
calls := 0
load := func() (int, error) { calls++; return 42, nil }
v1, _ := c.Remember("answer", time.Minute, load)
v2, _ := c.Remember("answer", time.Minute, load) // cached — loader skipped
fmt.Println(v1, v2, calls)
}
Output: 42 42 1
func (*Store[T]) SetTTL ¶
SetTTL implements Cache.
Example ¶
SetTTL stores a value with an explicit lifetime; a non-positive TTL means the entry never expires.
package main
import (
"fmt"
"time"
cacheobj "github.com/ubgo/cache-obj"
)
func main() {
c := cacheobj.New[string]()
c.SetTTL("token", "secret", 5*time.Minute)
c.SetTTL("config", "immutable", 0) // 0 => never expires
v, ok := c.Get("token")
fmt.Println(v, ok)
}
Output: secret true
Directories
¶
| Path | Synopsis |
|---|---|
|
Command examples is a runnable tour of cache-obj: caching live objects (compiled regexes, an *http.Client), single-flight Remember, per-entry TTL + Stats, and resource cleanup via the value-bearing OnEvict hook.
|
Command examples is a runnable tour of cache-obj: caching live objects (compiled regexes, an *http.Client), single-flight Remember, per-entry TTL + Stats, and resource cleanup via the value-bearing OnEvict hook. |
|
Package objtest is the conformance suite for the cacheobj contract.
|
Package objtest is the conformance suite for the cacheobj contract. |