Created
April 24, 2020 05:46
-
-
Save packrat386/90227e996a7aae7f0f4db7fc51afd60b to your computer and use it in GitHub Desktop.
Cuteness intensifies
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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