Golang泛型实践:将缓存库改造为泛型版本的实验
Golang泛型实践:将缓存库改造为泛型版本的实验 我想了解Go语言新的泛型实现,所以我拿了一个我使用过的库(GitHub - patrickmn/go-cache: 一个用于Go的、适用于单机应用的内存键值存储/缓存库(类似于Memcached)。)并尝试将其泛型化:由lukemassa首次尝试实现泛型的拉取请求 #147 · patrickmn/go-cache · GitHub。我非常希望在那里或这里得到反馈,同时需要说明的是,这对我来说主要是一个泛型实验,我并不期望这段代码被合并,至少在没有维护者进行认真的工作/审查之前不会。
一些想法(与拉取请求中的评论有些重叠):
- 转换比预期的要容易;我基本上只是让主结构体变成泛型,然后修复了所有的编译错误。
- 能够对参数类型进行类型切换(提案:规范:泛型:对参数化类型进行类型切换 · Issue #45380 · golang/go · GitHub)本可以使这个过程稍微容易一些,然而,所讨论的函数本身类型安全性就不高(理想情况下,
Increment函数应该能在正确的约束下“直接工作”),所以这并不是什么大问题。 - 类似地,直观上,能够为方法添加参数会很好(类型参数提案),但深入研究后,我明白了支持这个功能会有多困难。
- 有没有办法不用到处写
[T comparable]?感觉我应该能够依赖结构体的约束,而不是让自己容易打错字,或者需要修改约束时涉及很多地方,但这可能只是必要的。
更多关于Golang泛型实践:将缓存库改造为泛型版本的实验的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于Golang泛型实践:将缓存库改造为泛型版本的实验的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
// 泛型版本的Cache结构体定义
package cache
import (
"sync"
"time"
)
type Cache[K comparable, V any] struct {
defaultExpiration time.Duration
items map[K]Item[V]
mu sync.RWMutex
onEvicted func(K, V)
janitor *janitor[K, V]
}
type Item[V any] struct {
Object V
Expiration int64
}
// 创建泛型缓存实例
func New[K comparable, V any](defaultExpiration, cleanupInterval time.Duration) *Cache[K, V] {
items := make(map[K]Item[V])
return newCache[K, V](defaultExpiration, items, cleanupInterval)
}
// 泛型Get方法
func (c *Cache[K, V]) Get(k K) (V, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
item, found := c.items[k]
if !found || item.Expired() {
var zero V
return zero, false
}
return item.Object, true
}
// 泛型Set方法
func (c *Cache[K, V]) Set(k K, v V, d time.Duration) {
var e int64
if d == DefaultExpiration {
d = c.defaultExpiration
}
if d > 0 {
e = time.Now().Add(d).UnixNano()
}
c.mu.Lock()
defer c.mu.Unlock()
c.items[k] = Item[V]{
Object: v,
Expiration: e,
}
}
// 泛型Increment方法 - 需要类型约束
func (c *Cache[K, V]) Increment(k K, n V) error {
c.mu.Lock()
defer c.mu.Unlock()
item, found := c.items[k]
if !found || item.Expired() {
return ErrKeyNotFound
}
// 这里需要类型断言,因为V可能是多种数值类型
switch val := any(item.Object).(type) {
case int:
newVal := val + any(n).(int)
c.items[k] = Item[V]{
Object: any(newVal).(V),
Expiration: item.Expiration,
}
case int32:
newVal := val + any(n).(int32)
c.items[k] = Item[V]{
Object: any(newVal).(V),
Expiration: item.Expiration,
}
// 其他数值类型的处理...
default:
return ErrIncrementTypeNotSupported
}
return nil
}
// 使用示例
func main() {
// 创建字符串键、整数值的缓存
cache1 := New[string, int](5*time.Minute, 10*time.Minute)
cache1.Set("counter", 42, time.Minute)
val, found := cache1.Get("counter")
// 创建字符串键、自定义结构体值的缓存
type User struct {
ID int
Name string
}
cache2 := New[string, User](5*time.Minute, 10*time.Minute)
cache2.Set("user1", User{ID: 1, Name: "Alice"}, time.Hour)
// 创建整数键、切片值的缓存
cache3 := New[int, []string](5*time.Minute, 10*time.Minute)
cache3.Set(1, []string{"a", "b", "c"}, time.Minute)
}
关于约束重复的问题,可以使用类型别名简化:
type CacheKey comparable
type CacheValue any
type Cache[K CacheKey, V CacheValue] struct {
// ... 字段定义
}
// 这样在多个地方使用时更简洁
func NewCache[K CacheKey, V CacheValue](defaultExpiration, cleanupInterval time.Duration) *Cache[K, V] {
// ... 实现
}
对于Increment的类型安全问题,可以定义数值类型约束:
type Numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
~float32 | ~float64
}
type Cache[K comparable, V Numeric] struct {
// ... 字段定义
}
// 这样Increment可以更类型安全
func (c *Cache[K, V]) Increment(k K, n V) error {
c.mu.Lock()
defer c.mu.Unlock()
item, found := c.items[k]
if !found || item.Expired() {
return ErrKeyNotFound
}
// 现在可以直接进行数值运算
// 注意:这里需要运行时类型转换,因为V是类型参数
// 实际实现需要更复杂的处理
}

