├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .golangci.yml ├── COPYING ├── README.md ├── bin ├── .go-1.24.3.pkg ├── .golangci-lint-1.64.7.pkg ├── README.hermit.md ├── activate-hermit ├── go ├── gofmt ├── golangci-lint ├── hermit └── hermit.hcl ├── doc.go ├── either ├── either.go └── either_test.go ├── eventsource ├── eventsource.go └── eventsource_test.go ├── go.mod ├── go.sum ├── must └── must.go ├── once ├── once.go └── once_test.go ├── optional ├── option.go └── option_test.go ├── pubsub ├── pubsub.go └── pubsub_test.go ├── renovate.json5 ├── result ├── result.go ├── result_test.go ├── work.go └── work_test.go └── tuple └── tuple.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [alecthomas] 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | name: CI 7 | jobs: 8 | test: 9 | name: Test 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | - name: Init Hermit 15 | run: ./bin/hermit env -r >> $GITHUB_ENV 16 | - name: Test 17 | run: go test ./... 18 | lint: 19 | name: Lint 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v3 24 | - name: Init Hermit 25 | run: ./bin/hermit env -r >> $GITHUB_ENV 26 | - name: golangci-lint 27 | run: golangci-lint run 28 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | tests: true 3 | 4 | output: 5 | print-issued-lines: false 6 | 7 | linters: 8 | enable-all: true 9 | disable: 10 | - lll 11 | - gocyclo 12 | - gochecknoglobals 13 | - wsl 14 | - whitespace 15 | - godox 16 | - funlen 17 | - gocognit 18 | - gomnd 19 | - godot 20 | - nestif 21 | - testpackage 22 | - nolintlint 23 | - wrapcheck 24 | - gci 25 | - gofumpt 26 | - gocritic 27 | - nlreturn 28 | - errorlint 29 | - nakedret 30 | - forbidigo 31 | - revive 32 | - cyclop 33 | - paralleltest 34 | - wastedassign 35 | - forcetypeassert 36 | - gomoddirectives 37 | - varnamelen 38 | - exhaustruct 39 | - ireturn 40 | - nonamedreturns 41 | - errname 42 | - nilnil 43 | - maintidx 44 | - unused # Does not work with type parameters 45 | - dupword 46 | - depguard 47 | - mnd 48 | - execinquery 49 | - exportloopref 50 | - err113 51 | 52 | linters-settings: 53 | gocyclo: 54 | min-complexity: 10 55 | dupl: 56 | threshold: 100 57 | goconst: 58 | min-len: 8 59 | min-occurrences: 3 60 | exhaustive: 61 | default-signifies-exhaustive: true 62 | 63 | issues: 64 | max-per-linter: 0 65 | exclude-dirs: 66 | - _examples 67 | max-same: 0 68 | exclude-use-default: false 69 | exclude: 70 | # Captured by errcheck. 71 | - '^(G104|G204|G307):' 72 | # Very commonly not checked. 73 | - 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked' 74 | - 'exported method `(.*\.MarshalJSON|.*\.UnmarshalJSON|.*\.EntityURN|.*\.GoString|.*\.Pos)` should have comment or be unexported' 75 | - 'uses unkeyed fields' 76 | - 'declaration of "err" shadows declaration' 77 | - 'bad syntax for struct tag key' 78 | - 'bad syntax for struct tag pair' 79 | - '^ST1012' 80 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (C) 2023 thanks.dev 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Useful generic types for Go 2 | 3 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/alecthomas/types)](https://pkg.go.dev/github.com/alecthomas/types) [![GHA Build](https://github.com/alecthomas/types/actions/workflows/ci.yml/badge.svg)](https://github.com/alecthomas/types/actions) 4 | [![Slack chat](https://img.shields.io/static/v1?logo=slack&style=flat&label=slack&color=green&message=gophers)](https://gophers.slack.com/messages/CN9DS8YF3) 5 | 6 | I've found myself reimplementing some small types and patterns in a lot of my 7 | recent Go projects, so I thought I'd distill them into a single package. 8 | 9 | Refer to the [package docs](https://pkg.go.dev/github.com/alecthomas/types) for details. 10 | -------------------------------------------------------------------------------- /bin/.go-1.24.3.pkg: -------------------------------------------------------------------------------- 1 | hermit -------------------------------------------------------------------------------- /bin/.golangci-lint-1.64.7.pkg: -------------------------------------------------------------------------------- 1 | hermit -------------------------------------------------------------------------------- /bin/README.hermit.md: -------------------------------------------------------------------------------- 1 | # Hermit environment 2 | 3 | This is a [Hermit](https://github.com/cashapp/hermit) bin directory. 4 | 5 | The symlinks in this directory are managed by Hermit and will automatically 6 | download and install Hermit itself as well as packages. These packages are 7 | local to this environment. 8 | -------------------------------------------------------------------------------- /bin/activate-hermit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file must be used with "source bin/activate-hermit" from bash or zsh. 3 | # You cannot run it directly 4 | # 5 | # THIS FILE IS GENERATED; DO NOT MODIFY 6 | 7 | if [ "${BASH_SOURCE-}" = "$0" ]; then 8 | echo "You must source this script: \$ source $0" >&2 9 | exit 33 10 | fi 11 | 12 | BIN_DIR="$(dirname "${BASH_SOURCE[0]:-${(%):-%x}}")" 13 | if "${BIN_DIR}/hermit" noop > /dev/null; then 14 | eval "$("${BIN_DIR}/hermit" activate "${BIN_DIR}/..")" 15 | 16 | if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ]; then 17 | hash -r 2>/dev/null 18 | fi 19 | 20 | echo "Hermit environment $("${HERMIT_ENV}"/bin/hermit env HERMIT_ENV) activated" 21 | fi 22 | -------------------------------------------------------------------------------- /bin/go: -------------------------------------------------------------------------------- 1 | .go-1.24.3.pkg -------------------------------------------------------------------------------- /bin/gofmt: -------------------------------------------------------------------------------- 1 | .go-1.24.3.pkg -------------------------------------------------------------------------------- /bin/golangci-lint: -------------------------------------------------------------------------------- 1 | .golangci-lint-1.64.7.pkg -------------------------------------------------------------------------------- /bin/hermit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # THIS FILE IS GENERATED; DO NOT MODIFY 4 | 5 | set -eo pipefail 6 | 7 | export HERMIT_USER_HOME=~ 8 | 9 | if [ -z "${HERMIT_STATE_DIR}" ]; then 10 | case "$(uname -s)" in 11 | Darwin) 12 | export HERMIT_STATE_DIR="${HERMIT_USER_HOME}/Library/Caches/hermit" 13 | ;; 14 | Linux) 15 | export HERMIT_STATE_DIR="${XDG_CACHE_HOME:-${HERMIT_USER_HOME}/.cache}/hermit" 16 | ;; 17 | esac 18 | fi 19 | 20 | export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://github.com/cashapp/hermit/releases/download/stable}" 21 | HERMIT_CHANNEL="$(basename "${HERMIT_DIST_URL}")" 22 | export HERMIT_CHANNEL 23 | export HERMIT_EXE=${HERMIT_EXE:-${HERMIT_STATE_DIR}/pkg/hermit@${HERMIT_CHANNEL}/hermit} 24 | 25 | if [ ! -x "${HERMIT_EXE}" ]; then 26 | echo "Bootstrapping ${HERMIT_EXE} from ${HERMIT_DIST_URL}" 1>&2 27 | INSTALL_SCRIPT="$(mktemp)" 28 | # This value must match that of the install script 29 | INSTALL_SCRIPT_SHA256="180e997dd837f839a3072a5e2f558619b6d12555cd5452d3ab19d87720704e38" 30 | if [ "${INSTALL_SCRIPT_SHA256}" = "BYPASS" ]; then 31 | curl -fsSL "${HERMIT_DIST_URL}/install.sh" -o "${INSTALL_SCRIPT}" 32 | else 33 | # Install script is versioned by its sha256sum value 34 | curl -fsSL "${HERMIT_DIST_URL}/install-${INSTALL_SCRIPT_SHA256}.sh" -o "${INSTALL_SCRIPT}" 35 | # Verify install script's sha256sum 36 | openssl dgst -sha256 "${INSTALL_SCRIPT}" | \ 37 | awk -v EXPECTED="$INSTALL_SCRIPT_SHA256" \ 38 | '$2!=EXPECTED {print "Install script sha256 " $2 " does not match " EXPECTED; exit 1}' 39 | fi 40 | /bin/bash "${INSTALL_SCRIPT}" 1>&2 41 | fi 42 | 43 | exec "${HERMIT_EXE}" --level=fatal exec "$0" -- "$@" 44 | -------------------------------------------------------------------------------- /bin/hermit.hcl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alecthomas/types/674731c8cd9cb4a78c45fa20b7ea429b98183a2c/bin/hermit.hcl -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package types contains a collection of types and functions that are useful in Go. 2 | package types 3 | -------------------------------------------------------------------------------- /either/either.go: -------------------------------------------------------------------------------- 1 | // Package either provides a simple implementation of a sum type, Either, that can be either a Left or a Right. 2 | // 3 | // Usage: 4 | // 5 | // left := LeftOf[string](42) 6 | // fmt.Println(left.Get()) // 42 7 | // 8 | // right := RightOf[int]("foo") 9 | // fmt.Println(right.Get()) // foo 10 | // 11 | // var either Either[int, string] = left 12 | // either = right 13 | package either 14 | 15 | import "fmt" 16 | 17 | // Either is a "sum type" that can be either Left or Right. 18 | type Either[L, R any] interface { 19 | String() string 20 | GoString() string 21 | either(L, R) //nolint:inamedparam 22 | } 23 | 24 | type Left[L, R any] struct{ value L } 25 | 26 | var _ Either[int, string] = Left[int, string]{} 27 | 28 | func (Left[L, R]) either(L, R) {} 29 | func (e Left[L, R]) Get() L { return e.value } 30 | func (e Left[L, R]) String() string { return fmt.Sprintf("%v", e.value) } 31 | func (e Left[L, R]) GoString() string { var r R; return fmt.Sprintf("LeftOf[%T](%#v)", r, e.value) } 32 | 33 | type Right[L, R any] struct{ value R } 34 | 35 | var _ Either[int, string] = Right[int, string]{} 36 | 37 | func (Right[L, R]) either(L, R) {} 38 | func (e Right[L, R]) Get() R { return e.value } 39 | func (e Right[L, R]) String() string { return fmt.Sprintf("%v", e.value) } 40 | func (e Right[L, R]) GoString() string { var l L; return fmt.Sprintf("RightOf[%T](%#v)", l, e.value) } 41 | 42 | // LeftOf creates an Either[L, R] with a left value. 43 | // 44 | // Note that the L and R type parameters are flipped so that we can use type 45 | // inference to avoid having to specify the L type. 46 | func LeftOf[R, L any](value L) Left[L, R] { return Left[L, R]{value} } 47 | 48 | // RightOf creates an Either[L, R] with a right value. 49 | func RightOf[L, R any](value R) Right[L, R] { return Right[L, R]{value} } 50 | -------------------------------------------------------------------------------- /either/either_test.go: -------------------------------------------------------------------------------- 1 | package either 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/alecthomas/assert/v2" 7 | ) 8 | 9 | func TestEither(t *testing.T) { 10 | left := LeftOf[string](42) 11 | assert.Equal(t, 42, left.Get()) 12 | right := RightOf[int]("foo") 13 | assert.Equal(t, "foo", right.Get()) 14 | 15 | var either Either[int, string] = left 16 | if either, ok := either.(Left[int, string]); ok { 17 | assert.Equal(t, 42, either.Get()) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /eventsource/eventsource.go: -------------------------------------------------------------------------------- 1 | // Package eventsource provides a pubsub.Topic that also stores the last published value in an atomic.Value. 2 | // 3 | // Updating the value will result in a publish event. 4 | package eventsource 5 | 6 | import ( 7 | "sync/atomic" 8 | 9 | "github.com/alecthomas/types/pubsub" 10 | ) 11 | 12 | // EventSource is a pubsub.Topic that also stores the last published value in an atomic.Value. 13 | // 14 | // Updating the value will result in a publish event. 15 | type EventSource[T any] struct { 16 | *pubsub.Topic[T] 17 | value atomic.Value 18 | } 19 | 20 | func New[T any]() *EventSource[T] { 21 | var t T 22 | e := &EventSource[T]{Topic: pubsub.New[T]()} 23 | e.value.Store(t) 24 | changes := make(chan pubsub.Message[T], 64) 25 | e.SubscribeSync(changes) 26 | go func() { 27 | for msg := range changes { 28 | e.value.Store(msg.Msg) 29 | msg.Ack() 30 | } 31 | }() 32 | return e 33 | } 34 | 35 | // Store will store a new value and synchronously publish it to all subscribers. 36 | // 37 | // It will return any errors from the publish. 38 | func (e *EventSource[T]) Store(value T) error { 39 | e.value.Store(value) 40 | return e.PublishSync(value) 41 | } 42 | 43 | func (e *EventSource[T]) Load() T { 44 | return e.value.Load().(T) 45 | } 46 | 47 | func (e *EventSource[T]) Swap(value T) T { 48 | rv := e.value.Swap(value) 49 | _ = e.PublishSync(value) 50 | return rv.(T) 51 | } 52 | 53 | func (e *EventSource[T]) CompareAndSwap(old, new T) bool { //nolint:predeclared 54 | if e.value.CompareAndSwap(old, new) { 55 | _ = e.PublishSync(new) 56 | return true 57 | } 58 | return false 59 | } 60 | -------------------------------------------------------------------------------- /eventsource/eventsource_test.go: -------------------------------------------------------------------------------- 1 | package eventsource 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | func Example() { 9 | // Create a new event source. 10 | e := New[int]() 11 | 12 | // Subscribe to changes. 13 | changes := e.SubscribeSync(nil) 14 | go func() { 15 | for change := range changes { 16 | fmt.Println("change:", change.Msg) 17 | change.Ack() 18 | } 19 | }() 20 | 21 | // Publish a set a value. 22 | err := e.PublishSync(1) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | // Set and publish a value. 28 | err = e.Store(2) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | fmt.Println(e.Load()) 34 | 35 | // Output: 36 | // change: 1 37 | // change: 2 38 | // 2 39 | } 40 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alecthomas/types 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.3 6 | 7 | require ( 8 | github.com/alecthomas/assert/v2 v2.11.0 9 | modernc.org/sqlite v1.37.1 10 | ) 11 | 12 | require ( 13 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 14 | github.com/ncruces/go-strftime v0.1.9 // indirect 15 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect 16 | modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect 17 | ) 18 | 19 | require ( 20 | github.com/alecthomas/atomic v0.1.0-alpha2 21 | github.com/alecthomas/repr v0.4.0 // indirect 22 | github.com/dustin/go-humanize v1.0.1 // indirect 23 | github.com/google/uuid v1.6.0 // indirect 24 | github.com/hexops/gotextdiff v1.0.3 // indirect 25 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 26 | github.com/mattn/go-isatty v0.0.20 // indirect 27 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 28 | golang.org/x/mod v0.24.0 // indirect 29 | golang.org/x/sys v0.33.0 // indirect 30 | golang.org/x/tools v0.33.0 // indirect 31 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 32 | lukechampine.com/uint128 v1.2.0 // indirect 33 | modernc.org/cc/v3 v3.41.0 // indirect 34 | modernc.org/ccgo/v3 v3.17.0 // indirect 35 | modernc.org/libc v1.65.7 // indirect 36 | modernc.org/mathutil v1.7.1 // indirect 37 | modernc.org/memory v1.11.0 // indirect 38 | modernc.org/opt v0.1.4 // indirect 39 | modernc.org/strutil v1.2.1 // indirect 40 | modernc.org/token v1.1.0 // indirect 41 | ) 42 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0= 2 | github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= 3 | github.com/alecthomas/assert/v2 v2.8.1 h1:YCxnYR6jjpfnEK5AK5SysALKdUEBPGH4Y7As6tBnDw0= 4 | github.com/alecthomas/assert/v2 v2.8.1/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= 5 | github.com/alecthomas/assert/v2 v2.9.0 h1:ZcLG8ccMEtlMLkLW4gwGpBWBb0N8MUCmsy1lYBVd1xQ= 6 | github.com/alecthomas/assert/v2 v2.9.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= 7 | github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY= 8 | github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= 9 | github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= 10 | github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= 11 | github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= 12 | github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI= 13 | github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= 14 | github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 15 | github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= 16 | github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 17 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 18 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 19 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 20 | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= 21 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 22 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 23 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 24 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 25 | github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 26 | github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 27 | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 28 | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 29 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 30 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 31 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= 32 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 33 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 34 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 35 | github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= 36 | github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= 37 | github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= 38 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 39 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 40 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 41 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 42 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 43 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 44 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 45 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 46 | golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w= 47 | golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= 48 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= 49 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= 50 | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 51 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 52 | golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 53 | golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 54 | golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 55 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 56 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 57 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 58 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 59 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 60 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 61 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 62 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 63 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 64 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= 65 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 66 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 67 | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= 68 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 69 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 70 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 71 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 72 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 73 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 74 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 75 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 76 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 77 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 78 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 79 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 80 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 81 | golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs= 82 | golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 83 | golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= 84 | golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= 85 | golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= 86 | golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= 87 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 88 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 89 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 90 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 91 | lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= 92 | lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= 93 | modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= 94 | modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= 95 | modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= 96 | modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= 97 | modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= 98 | modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= 99 | modernc.org/ccgo/v3 v3.17.0/go.mod h1:Sg3fwVpmLvCUTaqEUjiBDAvshIaKDB0RXaf+zgqFu8I= 100 | modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= 101 | modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= 102 | modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= 103 | modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= 104 | modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= 105 | modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= 106 | modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk= 107 | modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY= 108 | modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg= 109 | modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo= 110 | modernc.org/libc v1.50.9 h1:hIWf1uz55lorXQhfoEoezdUHjxzuO6ceshET/yWjSjk= 111 | modernc.org/libc v1.50.9/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE= 112 | modernc.org/libc v1.52.1 h1:uau0VoiT5hnR+SpoWekCKbLqm7v6dhRL3hI+NQhgN3M= 113 | modernc.org/libc v1.52.1/go.mod h1:HR4nVzFDSDizP620zcMCgjb1/8xk2lg5p/8yjfGv1IQ= 114 | modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= 115 | modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= 116 | modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8= 117 | modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E= 118 | modernc.org/libc v1.65.7 h1:Ia9Z4yzZtWNtUIuiPuQ7Qf7kxYrxP1/jeHZzG8bFu00= 119 | modernc.org/libc v1.65.7/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU= 120 | modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= 121 | modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 122 | modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= 123 | modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= 124 | modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= 125 | modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= 126 | modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= 127 | modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= 128 | modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= 129 | modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= 130 | modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= 131 | modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= 132 | modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI= 133 | modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU= 134 | modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= 135 | modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= 136 | modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= 137 | modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= 138 | modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= 139 | modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= 140 | modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= 141 | modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4= 142 | modernc.org/sqlite v1.29.6/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U= 143 | modernc.org/sqlite v1.29.8 h1:nGKglNx9K5v0As+zF0/Gcl1kMkmaU1XynYyq92PbsC8= 144 | modernc.org/sqlite v1.29.8/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk= 145 | modernc.org/sqlite v1.29.10 h1:3u93dz83myFnMilBGCOLbr+HjklS6+5rJLx4q86RDAg= 146 | modernc.org/sqlite v1.29.10/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA= 147 | modernc.org/sqlite v1.30.0 h1:8YhPUs/HTnlEgErn/jSYQTwHN/ex8CjHHjg+K9iG7LM= 148 | modernc.org/sqlite v1.30.0/go.mod h1:cgkTARJ9ugeXSNaLBPK3CqbOe7Ec7ZhWPoMFGldEYEw= 149 | modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk= 150 | modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU= 151 | modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= 152 | modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= 153 | modernc.org/sqlite v1.34.2 h1:J9n76TPsfYYkFkZ9Uy1QphILYifiVEwwOT7yP5b++2Y= 154 | modernc.org/sqlite v1.34.2/go.mod h1:dnR723UrTtjKpoHCAMN0Q/gZ9MT4r+iRvIBb9umWFkU= 155 | modernc.org/sqlite v1.36.1 h1:bDa8BJUH4lg6EGkLbahKe/8QqoF8p9gArSc6fTqYhyQ= 156 | modernc.org/sqlite v1.36.1/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU= 157 | modernc.org/sqlite v1.37.1 h1:EgHJK/FPoqC+q2YBXg7fUmES37pCHFc97sI7zSayBEs= 158 | modernc.org/sqlite v1.37.1/go.mod h1:XwdRtsE1MpiBcL54+MbKcaDvcuej+IYSMfLN6gSKV8g= 159 | modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= 160 | modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= 161 | modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= 162 | modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= 163 | modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= 164 | modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= 165 | modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= 166 | modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 167 | modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= 168 | modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 169 | modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= 170 | -------------------------------------------------------------------------------- /must/must.go: -------------------------------------------------------------------------------- 1 | // Package must provides a way to panic if the result of a multi-value function 2 | // call returns an error, or return just the values if there is no error. 3 | package must 4 | 5 | // Get panics if err is not nil, otherwise it returns value. 6 | func Get[T any](value T, err error) T { 7 | if err != nil { 8 | panic(err) 9 | } 10 | return value 11 | } 12 | 13 | // Get2 panics if err is not nil, otherwise it returns left and right. 14 | func Get2[T, U any](left T, right U, err error) (T, U) { 15 | if err != nil { 16 | panic(err) 17 | } 18 | return left, right 19 | } 20 | -------------------------------------------------------------------------------- /once/once.go: -------------------------------------------------------------------------------- 1 | // Package once provides a way to call a function exactly once (memoisation). 2 | package once 3 | 4 | import ( 5 | "context" 6 | "sync" 7 | ) 8 | 9 | type Handle[T any] struct { 10 | once sync.Once 11 | f func(context.Context) (T, error) 12 | value T 13 | err error 14 | } 15 | 16 | func (h *Handle[T]) Get(ctx context.Context) (T, error) { 17 | h.once.Do(func() { h.value, h.err = h.f(ctx) }) 18 | return h.value, h.err 19 | } 20 | 21 | // Once returns a new Handle[T] that calls f exactly once. 22 | // 23 | // The returned Handle[T] is safe for concurrent use. 24 | // If f returns an error, the error will be returned by Get on all subsequent calls. 25 | func Once[T any](f func(context.Context) (T, error)) *Handle[T] { 26 | return &Handle[T]{f: f} 27 | } 28 | -------------------------------------------------------------------------------- /once/once_test.go: -------------------------------------------------------------------------------- 1 | package once 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/alecthomas/assert/v2" 8 | "github.com/alecthomas/types/must" 9 | ) 10 | 11 | func TestOnce(t *testing.T) { 12 | ctx := context.Background() 13 | calls := 0 14 | h := Once(func(context.Context) (int, error) { calls++; return 42, nil }) 15 | assert.Equal(t, 42, must.Get(h.Get(ctx))) 16 | assert.Equal(t, 42, must.Get(h.Get(ctx))) 17 | assert.Equal(t, 42, must.Get(h.Get(ctx))) 18 | assert.Equal(t, 1, calls) 19 | } 20 | -------------------------------------------------------------------------------- /optional/option.go: -------------------------------------------------------------------------------- 1 | // Package optional provides an Option type that can contain a value or nothing. 2 | package optional 3 | 4 | import ( 5 | "database/sql" 6 | "database/sql/driver" 7 | "encoding" 8 | "encoding/json" 9 | "fmt" 10 | "reflect" 11 | ) 12 | 13 | // Stdlib interfaces Option implements. 14 | var _ interface { 15 | fmt.Stringer 16 | fmt.GoStringer 17 | json.Marshaler 18 | json.Unmarshaler 19 | driver.Valuer 20 | sql.Scanner 21 | } = (*Option[int])(nil) 22 | 23 | // An Option type is a type that can contain a value or nothing. 24 | type Option[T any] struct { 25 | value T 26 | ok bool 27 | } 28 | 29 | // From returns an Option that contains a value if ok is true, otherwise None. 30 | func From[T any](value T, ok bool) Option[T] { 31 | if ok { 32 | return Some(value) 33 | } 34 | return None[T]() 35 | } 36 | 37 | // Some returns an Option that contains a value. 38 | func Some[T any](value T) Option[T] { return Option[T]{value: value, ok: true} } 39 | 40 | // None returns an Option that contains nothing. 41 | func None[T any]() Option[T] { return Option[T]{} } 42 | 43 | // Ptr returns an Option that is invalid if the pointer is nil, otherwise the dereferenced pointer. 44 | func Ptr[T any](ptr *T) Option[T] { 45 | if ptr == nil { 46 | return None[T]() 47 | } 48 | return Some(*ptr) 49 | } 50 | 51 | // Nil returns an Option that is invalid if the value is nil, otherwise the value. 52 | // 53 | // If the type is not nillable (slice, map, chan, ptr, interface) this will panic. 54 | // 55 | // Unfortunately there's no way to do this without reflection. 56 | func Nil[T any](ptr T) Option[T] { 57 | rv := reflect.ValueOf(ptr) 58 | if !rv.IsValid() { 59 | return None[T]() 60 | } 61 | switch rv.Type().Kind() { 62 | case reflect.Slice, reflect.Map, reflect.Chan, reflect.Ptr, reflect.Interface: 63 | if rv.IsNil() { 64 | return None[T]() 65 | } 66 | return Some(ptr) 67 | 68 | default: 69 | panic("type is not nillable") 70 | } 71 | } 72 | 73 | // Zero returns an Option that is invalid if the value is the zero value, otherwise the value. 74 | func Zero[T any](value T) Option[T] { 75 | rv := reflect.ValueOf(value) 76 | if !rv.IsValid() || rv.IsZero() { 77 | return None[T]() 78 | } 79 | return Some(value) 80 | } 81 | 82 | func (o *Option[T]) Scan(src any) error { 83 | if src == nil { 84 | o.ok = false 85 | var zero T 86 | o.value = zero 87 | return nil 88 | } 89 | if value, ok := src.(T); ok { 90 | o.value = value 91 | o.ok = true 92 | return nil 93 | } 94 | var value T 95 | switch scan := any(&value).(type) { 96 | case sql.Scanner: 97 | if err := scan.Scan(src); err != nil { 98 | return fmt.Errorf("cannot scan %T into Option[%T]: %w", src, o.value, err) 99 | } 100 | o.value = value 101 | o.ok = true 102 | 103 | case encoding.TextUnmarshaler: 104 | switch src := src.(type) { 105 | case string: 106 | if err := scan.UnmarshalText([]byte(src)); err != nil { 107 | return fmt.Errorf("unmarshal from %T into Option[%T] failed: %w", src, o.value, err) 108 | } 109 | o.value = value 110 | o.ok = true 111 | 112 | case []byte: 113 | if err := scan.UnmarshalText(src); err != nil { 114 | return fmt.Errorf("cannot scan %T into Option[%T]: %w", src, o.value, err) 115 | } 116 | o.value = value 117 | o.ok = true 118 | 119 | default: 120 | return fmt.Errorf("cannot unmarshal %T into Option[%T]", src, o.value) 121 | } 122 | 123 | default: 124 | return fmt.Errorf("no decoding mechanism found for %T into Option[%T]", src, o.value) 125 | } 126 | return nil 127 | } 128 | 129 | func (o Option[T]) Value() (driver.Value, error) { 130 | if !o.ok { 131 | return nil, nil 132 | } 133 | switch value := any(o.value).(type) { 134 | case driver.Valuer: 135 | return value.Value() 136 | 137 | case encoding.TextMarshaler: 138 | return value.MarshalText() 139 | } 140 | return o.value, nil 141 | } 142 | 143 | // Ptr returns a pointer to the value if the Option contains a value, otherwise nil. 144 | func (o Option[T]) Ptr() *T { 145 | if o.ok { 146 | return &o.value 147 | } 148 | return nil 149 | } 150 | 151 | // Ok returns true if the Option contains a value. 152 | func (o Option[T]) Ok() bool { return o.ok } 153 | 154 | // MustGet returns the value. It panics if the Option contains nothing. 155 | func (o Option[T]) MustGet() T { 156 | if !o.ok { 157 | var t T 158 | panic(fmt.Sprintf("Option[%T] contains nothing", t)) 159 | } 160 | return o.value 161 | } 162 | 163 | // Get returns the value and a boolean indicating if the Option contains a value. 164 | func (o Option[T]) Get() (T, bool) { return o.value, o.ok } 165 | 166 | // Default returns the Option value if it is present, otherwise it returns the 167 | // value passed. 168 | func (o Option[T]) Default(value T) T { 169 | if o.ok { 170 | return o.value 171 | } 172 | return value 173 | } 174 | 175 | func (o Option[T]) MarshalJSON() ([]byte, error) { 176 | if o.ok { 177 | return json.Marshal(o.value) 178 | } 179 | return []byte("null"), nil 180 | } 181 | 182 | func (o *Option[T]) UnmarshalJSON(data []byte) error { 183 | if string(data) == "null" { 184 | o.ok = false 185 | return nil 186 | } 187 | if err := json.Unmarshal(data, &o.value); err != nil { 188 | return err 189 | } 190 | o.ok = true 191 | return nil 192 | } 193 | 194 | func (o Option[T]) String() string { 195 | if o.ok { 196 | return fmt.Sprintf("%v", o.value) 197 | } 198 | return "None" 199 | } 200 | 201 | func (o Option[T]) GoString() string { 202 | if o.ok { 203 | return fmt.Sprintf("Some[%T](%#v)", o.value, o.value) 204 | } 205 | return fmt.Sprintf("None[%T]()", o.value) 206 | } 207 | -------------------------------------------------------------------------------- /optional/option_test.go: -------------------------------------------------------------------------------- 1 | package optional_test 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/json" 6 | "testing" 7 | 8 | "github.com/alecthomas/assert/v2" 9 | . "github.com/alecthomas/types/optional" 10 | _ "modernc.org/sqlite" // Register SQLite driver. 11 | ) 12 | 13 | func TestOptionFrom(t *testing.T) { 14 | yes := func() (string, bool) { return "yes", true } 15 | no := func() (string, bool) { return "", false } 16 | 17 | o := From(yes()) 18 | assert.Equal(t, Some("yes"), o) 19 | o = From(no()) 20 | assert.Equal(t, None[string](), o) 21 | } 22 | 23 | func TestOptionGet(t *testing.T) { 24 | o := Some(1) 25 | v, ok := o.Get() 26 | assert.True(t, ok) 27 | assert.Equal(t, 1, v) 28 | 29 | o = None[int]() 30 | _, ok = o.Get() 31 | assert.False(t, ok) 32 | } 33 | 34 | func TestOptionMarshalJSON(t *testing.T) { 35 | o := Some(1) 36 | b, err := o.MarshalJSON() 37 | assert.NoError(t, err) 38 | assert.Equal(t, "1", string(b)) 39 | 40 | o = None[int]() 41 | b, err = o.MarshalJSON() 42 | assert.NoError(t, err) 43 | assert.Equal(t, "null", string(b)) 44 | } 45 | 46 | func TestOptionUnmarshalJSON(t *testing.T) { 47 | o := Option[int]{} 48 | err := json.Unmarshal([]byte("1"), &o) 49 | assert.NoError(t, err) 50 | b, ok := o.Get() 51 | assert.True(t, ok) 52 | assert.Equal(t, 1, b) 53 | } 54 | 55 | func TestOptionMarshalJSONOmitEmpty(t *testing.T) { 56 | s := struct { 57 | Value Option[int] `json:"value,omitempty"` 58 | }{} 59 | 60 | b, err := json.Marshal(s) 61 | assert.NoError(t, err) 62 | assert.Equal(t, `{"value":null}`, string(b)) 63 | } 64 | 65 | func TestOptionString(t *testing.T) { 66 | o := Some(1) 67 | assert.Equal(t, "1", o.String()) 68 | 69 | o = None[int]() 70 | assert.Equal(t, "None", o.String()) 71 | } 72 | 73 | func TestOptionGoString(t *testing.T) { 74 | o := Some(1) 75 | assert.Equal(t, "Some[int](1)", o.GoString()) 76 | 77 | o = None[int]() 78 | assert.Equal(t, "None[int]()", o.GoString()) 79 | } 80 | 81 | func TestOptionSQL(t *testing.T) { 82 | db, err := sql.Open("sqlite", ":memory:") 83 | assert.NoError(t, err) 84 | _, err = db.Exec(`CREATE TABLE test (id INTEGER PRIMARY KEY, value INTEGER);`) 85 | assert.NoError(t, err) 86 | _, err = db.Exec(`INSERT INTO test (id, value) VALUES (1, 1);`) 87 | assert.NoError(t, err) 88 | _, err = db.Exec(`INSERT INTO test (id, value) VALUES (2, NULL);`) 89 | assert.NoError(t, err) 90 | 91 | var option Option[int64] 92 | rows, err := db.Query("SELECT value FROM test WHERE id = 1;") 93 | assert.NoError(t, err) 94 | defer rows.Close() 95 | for rows.Next() { 96 | err = rows.Scan(&option) 97 | assert.NoError(t, err) 98 | } 99 | err = rows.Err() 100 | assert.NoError(t, err) 101 | assert.Equal(t, Some(int64(1)), option) 102 | 103 | rows, err = db.Query("SELECT value FROM test WHERE id = 2;") 104 | assert.NoError(t, err) 105 | defer rows.Close() 106 | for rows.Next() { 107 | err = rows.Scan(&option) 108 | assert.NoError(t, err) 109 | } 110 | err = rows.Err() 111 | assert.NoError(t, err) 112 | assert.Equal(t, None[int64](), option) 113 | } 114 | 115 | func TestOptionZero(t *testing.T) { 116 | assert.Equal(t, None[error](), Zero((error)(nil))) 117 | assert.Equal(t, None[string](), Zero("")) 118 | } 119 | 120 | func TestOptionNil(t *testing.T) { 121 | assert.Panics(t, func() { 122 | var str string 123 | assert.Equal(t, None[string](), Nil(str)) 124 | }) 125 | 126 | assert.Equal(t, None[error](), Nil((error)(nil))) 127 | } 128 | -------------------------------------------------------------------------------- /pubsub/pubsub.go: -------------------------------------------------------------------------------- 1 | // Package pubsub provides a simple publish/subscribe mechanism. 2 | // 3 | // It supports both synchronous and asynchronous subscriptions. 4 | package pubsub 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "runtime" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | // AckTimeout is the time to wait for an ack before panicking. 15 | // 16 | // This is a last-ditch effort to avoid deadlocks. 17 | const AckTimeout = time.Second * 30 18 | 19 | // PublishTimeout is the time to wait for a publish before panicking. 20 | const PublishTimeout = time.Second * 10 21 | 22 | // Message is a message that must be acknowledge by the receiver. 23 | type Message[T any] struct { 24 | Msg T 25 | ack chan error 26 | } 27 | 28 | func (a *Message[T]) Ack() { close(a.ack) } 29 | func (a *Message[T]) Nack(err error) { 30 | if err == nil { 31 | err = errors.New("nack") 32 | } 33 | a.ack <- err 34 | close(a.ack) 35 | } 36 | 37 | // Control messages for the topic. 38 | type control[T any] interface{ control() } 39 | 40 | type subscribe[T any] struct { 41 | subscriber string 42 | msg chan Message[T] 43 | } 44 | 45 | func (subscribe[T]) control() {} 46 | 47 | type unsubscribe[T any] chan Message[T] 48 | 49 | func (unsubscribe[T]) control() {} 50 | 51 | type stop struct{} 52 | 53 | func (stop) control() {} 54 | 55 | type Topic[T any] struct { 56 | // This map is used by Unsubscribe() because the non-ackable channel is not 57 | // the same as the ackable channel. 58 | // 59 | // If this were typed it would be map[chan T]chan Message[T] 60 | rawChannelMap sync.Map 61 | publish chan Message[T] 62 | control chan control[T] 63 | // Closed when the Topic is closed. 64 | close chan struct{} 65 | } 66 | 67 | // New creates a new topic that can be used to publish and subscribe to messages. 68 | func New[T any]() *Topic[T] { 69 | s := &Topic[T]{ 70 | publish: make(chan Message[T], 16384), 71 | control: make(chan control[T]), 72 | close: make(chan struct{}), 73 | } 74 | go s.run() 75 | return s 76 | } 77 | 78 | // Wait that returns a channel that will be closed when the Topic is closed. 79 | func (s *Topic[T]) Wait() chan struct{} { 80 | return s.close 81 | } 82 | 83 | // Publish a message to the topic. 84 | func (s *Topic[T]) Publish(t T) { 85 | s.publish <- Message[T]{Msg: t, ack: make(chan error, 1)} 86 | } 87 | 88 | // PublishSync publishes a message to the topic and blocks until all subscriber 89 | // channels have acked the message. 90 | func (s *Topic[T]) PublishSync(t T) error { 91 | ack := make(chan error, 1) 92 | s.publish <- Message[T]{Msg: t, ack: ack} 93 | timer := time.NewTimer(AckTimeout) 94 | defer timer.Stop() 95 | select { 96 | case err := <-ack: 97 | return err 98 | case <-timer.C: 99 | return nil 100 | } 101 | } 102 | 103 | func getSubscriber() string { 104 | pc, file, line, _ := runtime.Caller(2) 105 | return fmt.Sprintf("%s:%d: %s", file, line, runtime.FuncForPC(pc).Name()) 106 | } 107 | 108 | // Subscribe a channel to the topic. 109 | // 110 | // The channel will be closed when the topic is closed. 111 | // 112 | // If "c" is nil a new channel of size 16 will be created. 113 | func (s *Topic[T]) Subscribe(c chan T) chan T { 114 | if c == nil { 115 | c = make(chan T, 16) 116 | } 117 | forward := make(chan Message[T], cap(c)) 118 | go func() { 119 | for msg := range forward { 120 | c <- msg.Msg 121 | msg.Ack() 122 | } 123 | close(c) 124 | }() 125 | s.rawChannelMap.Store(c, forward) 126 | s.control <- subscribe[T]{msg: forward, subscriber: getSubscriber()} 127 | return c 128 | } 129 | 130 | // SubscribeSync creates a synchronous subscription to the topic. 131 | // 132 | // Each message must be acked by the subscriber. 133 | // 134 | // A synchronous publish will block until the message has been acked by 135 | // all subscribers. 136 | // 137 | // The channel will be closed when the topic is closed. 138 | // If "c" is nil a new channel of size 16 will be created. 139 | func (s *Topic[T]) SubscribeSync(c chan Message[T]) chan Message[T] { 140 | if c == nil { 141 | c = make(chan Message[T], 16) 142 | } 143 | s.control <- subscribe[T]{msg: c, subscriber: getSubscriber()} 144 | return c 145 | } 146 | 147 | // Unsubscribe a channel from the topic, closing the channel. 148 | func (s *Topic[T]) Unsubscribe(c chan T) { 149 | ackch, ok := s.rawChannelMap.Load(c) 150 | if !ok { // This should never happen in practice. 151 | panic("channel not subscribed") 152 | } 153 | s.rawChannelMap.Delete(c) 154 | // Drain the subscription channel 155 | go func() { 156 | for range c { 157 | } 158 | }() 159 | s.control <- unsubscribe[T](ackch.(chan Message[T])) 160 | } 161 | 162 | // UnsubscribeSync a synchronised subscription from the topic, closing the channel. 163 | func (s *Topic[T]) UnsubscribeSync(c chan Message[T]) { 164 | s.control <- unsubscribe[T](c) 165 | } 166 | 167 | // Close the topic, blocking until all subscribers have been closed. 168 | func (s *Topic[T]) Close() error { 169 | s.control <- stop{} 170 | <-s.close 171 | return nil 172 | } 173 | 174 | func (s *Topic[T]) run() { 175 | subscriptions := map[chan Message[T]]subscribe[T]{} 176 | for { 177 | select { 178 | case msg := <-s.control: 179 | switch msg := msg.(type) { 180 | case subscribe[T]: 181 | subscriptions[msg.msg] = msg 182 | 183 | case unsubscribe[T]: 184 | delete(subscriptions, msg) 185 | close(msg) 186 | 187 | case stop: 188 | for ch := range subscriptions { 189 | close(ch) 190 | } 191 | close(s.control) 192 | close(s.publish) 193 | s.rawChannelMap.Range(func(k, v any) bool { 194 | s.rawChannelMap.Delete(k) 195 | return true 196 | }) 197 | close(s.close) 198 | return 199 | 200 | default: 201 | panic(fmt.Sprintf("unknown control message: %T", msg)) 202 | } 203 | 204 | case msg := <-s.publish: 205 | errs := []error{} 206 | for ch, sub := range subscriptions { 207 | smsg := Message[T]{Msg: msg.Msg, ack: make(chan error, 1)} 208 | ch <- smsg 209 | timer := time.NewTimer(AckTimeout) 210 | select { 211 | case err := <-smsg.ack: 212 | errs = append(errs, err) 213 | case <-timer.C: 214 | // Print all goroutines 215 | panic("ack timeout for " + sub.subscriber) 216 | } 217 | timer.Stop() 218 | } 219 | msg.ack <- errors.Join(errs...) 220 | close(msg.ack) 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /pubsub/pubsub_test.go: -------------------------------------------------------------------------------- 1 | package pubsub_test 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "testing" 8 | "time" 9 | 10 | "github.com/alecthomas/assert/v2" 11 | . "github.com/alecthomas/types/pubsub" //nolint 12 | ) 13 | 14 | func Example() { 15 | // Create a new topic. 16 | t := New[int]() 17 | 18 | // Subscribe to changes. 19 | changes := t.Subscribe(nil) 20 | go func() { 21 | for change := range changes { 22 | fmt.Println("change:", change) 23 | } 24 | }() 25 | 26 | // Publish a value. 27 | t.Publish(1) 28 | 29 | // Publish a value and wait for it to be received. 30 | t.Publish(2) 31 | 32 | time.Sleep(time.Millisecond * 100) 33 | // Output: 34 | // change: 1 35 | // change: 2 36 | } 37 | 38 | func TestPubsub(t *testing.T) { 39 | pubsub := New[string]() 40 | ch := make(chan string, 64) 41 | pubsub.Subscribe(ch) 42 | pubsub.Publish("hello") 43 | select { 44 | case msg := <-ch: 45 | assert.Equal(t, "hello", msg) 46 | 47 | case <-time.After(time.Millisecond * 100): 48 | t.Fail() 49 | } 50 | _ = pubsub.Close() 51 | select { 52 | case _, ok := <-ch: 53 | assert.True(t, !ok, "channel should be closed") 54 | 55 | case <-time.After(time.Millisecond * 100): 56 | t.Fatal("channel should have been closed") 57 | } 58 | assert.Panics(t, func() { pubsub.Subscribe(ch) }) 59 | assert.Panics(t, func() { pubsub.Unsubscribe(ch) }) 60 | assert.Panics(t, func() { pubsub.Publish("hello") }) 61 | } 62 | 63 | func TestSyncPubSub(t *testing.T) { 64 | pubsub := New[string]() 65 | defer pubsub.Close() //nolint 66 | order := make(chan string, 64) 67 | finished := make(chan struct{}) 68 | ch := pubsub.SubscribeSync(nil) 69 | go func() { 70 | err := pubsub.PublishSync("hello") 71 | order <- "published" 72 | assert.NoError(t, err) 73 | 74 | err = pubsub.PublishSync("world") 75 | order <- "published" 76 | assert.EqualError(t, err, "nack") 77 | 78 | close(finished) 79 | }() 80 | select { 81 | case msg := <-ch: 82 | assert.Equal(t, "hello", msg.Msg) 83 | order <- "received" 84 | msg.Ack() 85 | 86 | // Test nack 87 | select { 88 | case msg := <-ch: 89 | assert.Equal(t, "world", msg.Msg) 90 | order <- "received" 91 | msg.Nack(errors.New("nack")) 92 | 93 | case <-time.After(time.Millisecond * 500): 94 | t.Fatal("timeout") 95 | } 96 | 97 | case <-time.After(time.Millisecond * 500): 98 | t.Fatal("timeout") 99 | } 100 | <-finished 101 | close(order) 102 | 103 | // Ensure that the message was received before it was published and thus 104 | // acked. 105 | actual := []string{} 106 | for o := range order { 107 | actual = append(actual, o) 108 | } 109 | assert.Equal(t, []string{"received", "published", "received", "published"}, actual) 110 | } 111 | 112 | func TestPubSubPanicAfterUnsubscribe(t *testing.T) { 113 | t.Skip("This test is slow") 114 | topic := New[string]() 115 | for range 100 { 116 | go func() { 117 | foo := topic.Subscribe(make(chan string, 1)) 118 | <-time.After(time.Second) 119 | topic.Unsubscribe(foo) 120 | }() 121 | } 122 | ctx, cancel := context.WithCancel(context.TODO()) 123 | defer cancel() 124 | go func() { 125 | for { 126 | select { 127 | case <-time.After(time.Millisecond * 100): 128 | topic.Publish("foo") 129 | 130 | case <-ctx.Done(): 131 | return 132 | } 133 | } 134 | }() 135 | <-time.After(time.Minute) 136 | } 137 | -------------------------------------------------------------------------------- /renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | $schema: "https://docs.renovatebot.com/renovate-schema.json", 3 | extends: [ 4 | "config:recommended", 5 | ":semanticCommits", 6 | ":semanticCommitTypeAll(chore)", 7 | ":semanticCommitScope(deps)", 8 | "group:allNonMajor", 9 | "schedule:earlyMondays", // Run once a week. 10 | ], 11 | "packageRules": [ 12 | { 13 | "matchPackageNames": ["golangci-lint"], 14 | "matchManagers": ["hermit"], 15 | "enabled": false 16 | }, 17 | ], 18 | } 19 | -------------------------------------------------------------------------------- /result/result.go: -------------------------------------------------------------------------------- 1 | // Package result provides a Result type that can contain a value or an error. 2 | package result 3 | 4 | import ( 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | ) 9 | 10 | // Stdlib interfaces types implement. 11 | type stdlib interface { 12 | fmt.Stringer 13 | fmt.GoStringer 14 | json.Marshaler 15 | json.Unmarshaler 16 | } 17 | 18 | var _ stdlib = (*Result[int])(nil) 19 | 20 | // Ok returns a Result that contains a value. 21 | func Ok[T any](value T) Result[T] { return Result[T]{value: value} } 22 | 23 | // Err returns a Result that contains an error. 24 | func Err[T any](err error) Result[T] { return Result[T]{err: err} } 25 | 26 | // Map a Result[L] to Result[R]. If Result[L] is an error mapper will not be called and the result will be an error. 27 | func Map[L, R any](l Result[L], mapper func(L) (R, error)) Result[R] { 28 | value, err := l.Result() 29 | if err != nil { 30 | return Err[R](err) 31 | } 32 | return Outcome(mapper(value)) 33 | } 34 | 35 | // From returns a Result that contains a value or an error. 36 | // 37 | // It can be used to convert a function that returns a value and an error into a 38 | // Result. 39 | func From[T any](value T, err error) Result[T] { 40 | if err != nil { 41 | return Err[T](err) 42 | } 43 | return Ok(value) 44 | } 45 | 46 | // Outcome returns a Result that contains a value or an error. 47 | // 48 | // It can be used to convert a function that returns a value and an error into a 49 | // Result. 50 | // 51 | // Deprecated: Use From instead. 52 | func Outcome[T any](value T, err error) Result[T] { 53 | if err != nil { 54 | return Err[T](err) 55 | } 56 | return Ok(value) 57 | } 58 | 59 | // Errf returns a Result that contains a formatted error. 60 | func Errf[T any](format string, args ...any) Result[T] { 61 | return Err[T](fmt.Errorf(format, args...)) 62 | } 63 | 64 | // Errorf returns a Result that contains a formatted error. 65 | // 66 | // Deprecated: Use Errf instead. 67 | func Errorf[T any](format string, args ...any) Result[T] { 68 | return Err[T](fmt.Errorf(format, args...)) 69 | } 70 | 71 | // A Result type is a type that can contain an error or a value. 72 | type Result[T any] struct { 73 | value T 74 | err error 75 | } 76 | 77 | // Get returns the value and a boolean indicating whether the value is present. 78 | func (r Result[T]) Get() (T, bool) { return r.value, r.err == nil } 79 | 80 | // Result returns the underlying value and error. 81 | func (r Result[T]) Result() (T, error) { return r.value, r.err } 82 | 83 | // Default returns the Result value if it is present, otherwise it returns the 84 | // value passed. 85 | func (r Result[T]) Default(value T) T { 86 | if r.err == nil { 87 | return r.value 88 | } 89 | return value 90 | } 91 | 92 | // Err returns the error, if any. 93 | func (r Result[T]) Err() error { return r.err } 94 | 95 | func (r Result[T]) String() string { 96 | if r.err == nil { 97 | return fmt.Sprintf("%v", r.value) 98 | } 99 | return fmt.Sprintf("error(%q)", r.err.Error()) 100 | } 101 | 102 | func (r Result[T]) GoString() string { 103 | if r.err == nil { 104 | return fmt.Sprintf("Ok[%T](%#v)", r.value, r.value) 105 | } 106 | return fmt.Sprintf("Err[%T](%q)", r.value, r.err) 107 | } 108 | 109 | func (r Result[T]) MarshalJSON() ([]byte, error) { 110 | value := map[string]any{} 111 | if r.err == nil { 112 | value["value"] = r.value 113 | return json.Marshal(value) 114 | } else { 115 | value["error"] = r.err.Error() 116 | } 117 | return json.Marshal(value) 118 | } 119 | 120 | func (r *Result[T]) UnmarshalJSON(data []byte) error { 121 | value := map[string]json.RawMessage{} 122 | if err := json.Unmarshal(data, &value); err != nil { 123 | return err 124 | } 125 | if v, ok := value["value"]; ok { 126 | return json.Unmarshal(v, &r.value) 127 | } 128 | if v, ok := value["error"]; ok { 129 | var errStr string 130 | if err := json.Unmarshal(v, &errStr); err != nil { 131 | return err 132 | } 133 | r.err = errors.New(errStr) 134 | return nil 135 | } 136 | return errors.New("invalid result") 137 | } 138 | -------------------------------------------------------------------------------- /result/result_test.go: -------------------------------------------------------------------------------- 1 | package result 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/alecthomas/assert/v2" 10 | ) 11 | 12 | func TestResultGet(t *testing.T) { 13 | r := Ok(1) 14 | v, ok := r.Get() 15 | assert.True(t, ok) 16 | assert.Equal(t, 1, v) 17 | 18 | r = Errf[int]("foo") 19 | _, ok = r.Get() 20 | assert.False(t, ok) 21 | } 22 | 23 | func TestResultMarshalJSON(t *testing.T) { 24 | r := Ok(1) 25 | b, err := json.Marshal(r) 26 | assert.NoError(t, err) 27 | assert.Equal(t, `{"value":1}`, string(b)) 28 | 29 | r = Errf[int]("foo") 30 | b, err = json.Marshal(r) 31 | assert.NoError(t, err) 32 | assert.Equal(t, `{"error":"foo"}`, string(b)) 33 | } 34 | 35 | func TestResultUnmarshalJSON(t *testing.T) { 36 | r := Result[int]{} 37 | err := json.Unmarshal([]byte(`{"value":1}`), &r) 38 | assert.NoError(t, err) 39 | assert.Equal(t, Ok(1), r) 40 | 41 | r = Errf[int]("error") 42 | err = json.Unmarshal([]byte(`{"error":"error"}`), &r) 43 | assert.NoError(t, err) 44 | } 45 | 46 | func TestResultMap(t *testing.T) { 47 | err := errors.New("error") 48 | tests := []struct { 49 | name string 50 | input Result[string] 51 | expected Result[int64] 52 | called bool 53 | }{ 54 | {"Success", Ok("1234"), Ok[int64](1234), true}, 55 | {"Error", Err[string](err), Err[int64](err), false}, 56 | {"Invalid", Ok("hello"), Err[int64](errors.New(`strconv.ParseInt: parsing "hello": invalid syntax`)), true}, 57 | } 58 | called := false 59 | curry := func(v string) (int64, error) { 60 | called = true 61 | return strconv.ParseInt(v, 10, 64) 62 | } 63 | for _, test := range tests { 64 | t.Run(test.name, func(t *testing.T) { 65 | actual := Map(test.input, curry) 66 | assert.Equal(t, test.expected, actual) 67 | assert.Equal(t, test.called, called) 68 | called = false 69 | }) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /result/work.go: -------------------------------------------------------------------------------- 1 | package result 2 | 3 | // Go runs a function in a goroutine and returns a channel that will receive the 4 | // Result. 5 | func Go[T any](f func() (T, error)) chan Result[T] { 6 | out := make(chan Result[T]) 7 | go func() { 8 | defer close(out) 9 | out <- Outcome(f()) 10 | }() 11 | return out 12 | } 13 | -------------------------------------------------------------------------------- /result/work_test.go: -------------------------------------------------------------------------------- 1 | package result 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | "time" 7 | 8 | "github.com/alecthomas/assert/v2" 9 | ) 10 | 11 | func TestGo(t *testing.T) { 12 | out := Go(func() (int, error) { 13 | return 1, nil 14 | }) 15 | assertResult(t, Ok(1), out) 16 | 17 | out = Go(func() (int, error) { 18 | return 0, errors.New("error") 19 | }) 20 | assertResult(t, Errorf[int]("error"), out) 21 | } 22 | 23 | func assertResult[T any](t *testing.T, expected Result[T], actual chan Result[T]) { 24 | t.Helper() 25 | select { 26 | case actual := <-actual: 27 | assert.Equal(t, expected, actual) 28 | 29 | case <-time.After(time.Millisecond * 100): 30 | t.Fatal("timeout") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tuple/tuple.go: -------------------------------------------------------------------------------- 1 | // Package tuple provides pseudo-tuple types. 2 | package tuple 3 | 4 | type Pair[A, B any] struct { 5 | A A 6 | B B 7 | } 8 | 9 | func PairOf[A, B any](a A, b B) Pair[A, B] { 10 | return Pair[A, B]{A: a, B: b} 11 | } 12 | 13 | func (p Pair[A, B]) Get() (A, B) { 14 | return p.A, p.B 15 | } 16 | 17 | type Triple[A, B, C any] struct { 18 | A A 19 | B B 20 | C C 21 | } 22 | 23 | func TripleOf[A, B, C any](a A, b B, c C) Triple[A, B, C] { 24 | return Triple[A, B, C]{A: a, B: b, C: c} 25 | } 26 | 27 | func (t Triple[A, B, C]) Get() (A, B, C) { 28 | return t.A, t.B, t.C 29 | } 30 | 31 | type Quad[A, B, C, W any] struct { 32 | A A 33 | B B 34 | C C 35 | D W 36 | } 37 | 38 | func QuadOf[A, B, C, D any](a A, b B, c C, d D) Quad[A, B, C, D] { 39 | return Quad[A, B, C, D]{A: a, B: b, C: c, D: d} 40 | } 41 | 42 | func (q Quad[A, B, C, W]) Get() (A, B, C, W) { 43 | return q.A, q.B, q.C, q.D 44 | } 45 | --------------------------------------------------------------------------------