# Using Go Generics for Function Caching

I’m currently engaged in a side project where I’m exploring web application development using Golang. As part of this endeavor, I’m incorporating elements from other web frameworks like Ruby on Rails to handle server-side HTML rendering, database connection management, form handling, and more.

During the course of my work, I decided to dive into caching and wanted to experiment with different strategies. One of the advantages of Golang over other languages is its compilation and support for multi-threading through go routines. I wanted to implement executable caching without relying on external tools like Redis or Memcached.

There are various patterns for caching, often revolving around the concept of a key-value store. To summarize it in haiku form:

Here is my data, It is named this way, give it back, I don’t care how.

In Golang, there are several in-process key-value stores available. One popular choice is BoltDB due to its reliability and battle-tested nature. However, upon careful consideration, it still felt too similar to Redis. I was inclined to approach this challenge more programmatically, perhaps even writing the caching mechanism myself.

Caching the return value of a function call requires knowledge of the types in advance, so that the value can be appropriately cached.

This is a short example of the ergonomics I was aiming for.

package main

func  SomeFunc() int {
  // complex operation
  return  1
}

func  CacheWrapper(fun func() int) func() int {
  var  cached *int
  return  func() int {
    if cached == nil {
      value := fun()
      cached = &value
    }
    return *cached
  }
}

func  main() {
  invoke := CacheWrapper(SomeFunc)
  invoke() // initial call
  invoke() // using cached value
}

As you can see CachedWrapper is ensuring that the expensive function is only called once. This Just Works™ with int return types, however. With early Golang libraries, I’d have to create a CachedWrapper for each return type I’d ever want to use.

With more recent versions of Golang, however, generics were introduces to allow types to be abstracted away. The type could be inferred at compiled time based on the return type.

his is short example of the ergonomics I was aiming for. You’ll see the return type referenced as R in CacheWrapper the function declaration.

package main

func  SomeFunc() int {
  // complex operation
  return  1
}

func CacheWrapper[R any](fun func() R) func() R {
  var  cached *R // R represent the return function value
  return  func() R {
    if cached == nil {
      value := fun()
      cached = &value
    }
    return *cached
  }
}

func  main() {
  invoke := CacheWrapper(SomeFunc)
  invoke() // initial call
  invoke() // using cached value
}

NOTE: This is caching of a function call, not memoization. If we were memoizing, we’d be taking arguments into account. The caching would be more complex. Here we are just considering a function is called, and the returned value is essential.

With R as a placeholder, we can use any type as the function’s return value. It is inferred from the fun return type, which allows us never to have to know about it ahead of time explicitly. The compiler does the work for us.

I can use this in many places with minimal intrusion into my current codebase. I have to import the CacheWrapper function and use it. Generics in Go are fantastic and save me so much code!

Some next steps for ensuring caching functionality would be expiration and thread safeness. I’ll follow up with a continuation or a library.