Skip to content

Instantly share code, notes, and snippets.

@autarch
Created May 11, 2023 17:44
Show Gist options
  • Save autarch/6e4e879d19e5239f454f08ef90e568cb to your computer and use it in GitHub Desktop.
Save autarch/6e4e879d19e5239f454f08ef90e568cb to your computer and use it in GitHub Desktop.
package main
import (
"log"
"sync"
)
type LockedData struct {
mtx sync.RWMutex
data *TheData
}
type TheData struct {
field1 int
field2 string
}
// It would be nice if we could make `TheData` read-only when we call the
// callback. Unfortunately, AFAIK the only way to guarantee this would be to
// deep-copy the entire data structure, since if `TheData` contains any fields
// that are pointers (including a slice or map), then any changes to those
// fields will be seen by the caller. Given that, there's no reason _not_ to
// pass `TheData` as a pointer.
func (ld *LockedData) WithReadLockedData(cb func(*TheData) error) error {
ld.mtx.RLock()
defer ld.mtx.Unlock()
return cb(ld.data)
}
func (ld *LockedData) WithWriteLockedData(cb func(*TheData) error) error {
ld.mtx.RLock()
defer ld.mtx.Unlock()
return cb(ld.data)
}
type SomethingElse struct {
ld *LockedData
}
func (se *SomethingElse) doNotDoThis() {
// 20 lines of set up code
callback := func(td *TheData) error {
// 30 lines of logic here
return nil
}
err := se.ld.WithReadLockedData(callback)
if err != nil {
// 10 lines of error handling
}
}
func (se *SomethingElse) doThisInstead() {
// 20 lines of set up code (or maybe split this off in its own method!)
err := se.ld.WithReadLockedData(func(td *TheData) error {
return se.doSomethingWithTheData(td)
})
if err != nil {
// 10 lines of error handling
}
}
func (se *SomethingElse) callbackReturnsSomething() {
// 20 lines of set up code (or maybe split this off in its own method!)
// It'd be nice if we could have a generic WithReadLockedDataReturning
// method, but Go does not allow for generic methods. That said, this
// pattern is not _that_ gross if the body of the callback is very short,
// as we see below.
var ret int
err := se.ld.WithReadLockedData(func(td *TheData) error {
var cbErr error
ret, cbErr = se.doSomethingElseWithTheData(td)
return cbErr
})
if err != nil {
// 10 lines of error handling
}
log.Println("ret = %d", ret)
}
func (se *SomethingElse) doSomethingWithTheData(td *TheData) error {
// The 30 lines from the callback now live in this method.
return nil
}
func (se *SomethingElse) doSomethingElseWithTheData(td *TheData) (int, error) {
// Do something with `TheData`
return 42, nil
}
func main() {
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment