Created
May 11, 2023 17:44
-
-
Save autarch/6e4e879d19e5239f454f08ef90e568cb to your computer and use it in GitHub Desktop.
This file contains hidden or 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 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