Skip to content

Instantly share code, notes, and snippets.

@packrat386
Created April 24, 2020 05:46
Show Gist options
  • Save packrat386/90227e996a7aae7f0f4db7fc51afd60b to your computer and use it in GitHub Desktop.
Save packrat386/90227e996a7aae7f0f4db7fc51afd60b to your computer and use it in GitHub Desktop.
Cuteness intensifies
package cloj
import (
"sync"
)
// Map is an example of a type I might need that has some
// inner bits that need to be in harmony for the whole thing
// to function. In this case, what we're imagining is a kv store
// of string -> string that is safe to concurrently access.
type Map interface {
Get(key string) (string, bool)
Set(key, value string)
}
// simpleMap is how I would basically always make something like this. You've
// got the backing data structure (map[string]string) and a sync.Mutex to guard
// against concurrent accesses. The problem is that if someone has this data
// structure, they can go poking around in `inner` without actually using the
// methods, thus bypassing the lock. We can prevent that somewhat by not exporting
// and by returning an interface, but casts exist and sometimes the abuse is
// coming from inside our own package.
type simpleMap struct {
inner map[string]string
lock *sync.Mutex
}
func NewSimpleMap() Map {
return &simpleMap{
inner: map[string]string{},
lock: new(sync.Mutex),
}
}
func (s *simpleMap) Get(key string) (string, bool) {
s.lock.Lock()
defer s.lock.Unlock()
value, ok := s.inner[key]
return value, ok
}
func (s *simpleMap) Set(key, value string) {
s.lock.Lock()
defer s.lock.Unlock()
s.inner[key] = value
}
// clojMap is a silly idea to use a closure to store the data instead. Once
// the constructor exits, the only thing that has access to `inner` is the
// closures we declared in the same scope, and the only way to get those is
// to call clojMap. We can do some cuteness with functions as types to make
// this satisfy our interfaces, but even if someone was in our package and
// willing to crack open the interface and cast to the concrete type, there's
// still no way for them to get unguarded access to `inner`. The only way they
// can possibly mess with `inner` now is to change the constructor to somehow
// give them a reference to it (or do some funsafe pointer math).
type clojMap func() (getFunc, setFunc)
type getFunc func(string) (string, bool)
type setFunc func(string, string)
func (c clojMap) Get(key string) (string, bool) {
get, _ := c()
return get(key)
}
func (c clojMap) Set(key, value string) {
_, set := c()
set(key, value)
}
func NewClojMap() Map {
inner := map[string]string{}
lock := new(sync.Mutex)
get := func(key string) (string, bool) {
lock.Lock()
defer lock.Unlock()
value, ok := inner[key]
return value, ok
}
set := func(key string, value string) {
lock.Lock()
defer lock.Unlock()
inner[key] = value
}
return clojMap(func() (getFunc, setFunc) {
return getFunc(get), setFunc(set)
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment