Skip to content

Instantly share code, notes, and snippets.

@danhodge
Last active October 22, 2025 17:08
Show Gist options
  • Save danhodge/f234a6baf9da695c9ddd17f831d9dc2a to your computer and use it in GitHub Desktop.
Save danhodge/f234a6baf9da695c9ddd17f831d9dc2a to your computer and use it in GitHub Desktop.
Go Notes
// types and literals
// defines a new type (note - this is not a type alias, type aliases are a different thing, defined by: type ALIAS = ORIGNAL)
type Thing int32
// this function only accepts arguments of type Thing, int32 arguments are not allowed by the compiler
func justThings(t Thing) {
// do stuff
}
var v Thing = 2
justThings(v) // compiles
var i int32 = 7
justThings(i) // does not compile because i is an int32
justThings(9) // compiles, because of default typing: https://go.dev/blog/constants
// arrays of interfaces
// 1. Define an interface
type Thingable interface {
DoIt(s string) int
}
// 2. Define a function that operates on an array of the interface defined in step 1
func TakesThingables(t []Thingable) int {
total := 0
for i, thing := range t {
total += thing.DoIt("things") - i
}
return total
}
type Impl struct {
Value int
}
// 3. There exists an impl somewhere that conforms for the interface defined in step 1
func (i *Impl) DoIt(s string) int {
return len(s) + i.Value
}
func main() {
// 4. Here's an array of those Impls
a := []Impl{{Value: 12}, {Value: 3}}
// 5. This doesn't compile
fmt.Printf("Result 1 = %v\n", TakesThingables(a))
// 6. You can't pass an arrray of Impls into TakesThingables, you need to convert it into an array of Thingable first
aPrime := make([]Thingable, len(a))
for i, v := range a {
aPrime[i] = &v
}
fmt.Printf("Result 2 = %v\n", TakesThingables(aPrime))
}
// Get an io.ReadCloser for a string
import (
"io"
"strings"
)
rc := io.NopCloser(strings.NewReader("string"))
// implement a one-function interface with a function
// Here's the interface
type Doer interface {
DoIt(v int) string
}
// 1. Add a function type
type DoerFunc func(v int) string
// 2. Implement the interface function on the function type
func (f DoerFunc) DoIt(v int) string {
// f is a DoerFunc, so the only thing that you can do with it is call it - the purpose of this
// method is to turn an arbitrary DoerFunc function into an implementation of the Doer interface
return f(v)
}
// 3. Some function that takes the original interface
func TakesDoer(d Doer, i int) string {
return d.DoIt(i)
}
// 4a. Create a function of type DoerFunc, or...
var fn DoerFunc = func(v int) string {
return "VAL"
}
// 4b. just reference an existing function that matches the type
func TakesIntReturnsString(i int) string {
return fmt.Sprintf("%d", i)
}
var fn DoerFunc
fn = TakesIntReturnsString
// 5. Pass it into the function from step 3
TakesDoer(fn, 8)
// implement a function that captures state without using a struct
type Searcher func(q string) string
func NewSearcher(client *search.client) Searcher {
return func(q string) string {
return client.SearchQuery(q)
}
}
// Shorthand for passing an error (or any other local variable) into a deferred function
func somefunc() (_ int, err error) { // use an _ for the unnamed return value - all values have to be named when using named values
// this declaration is required if not using named return values
// var err error
// need to wrap the deferred call in a function because defer evaluates the arguments to the deferred call immediately
defer func() {
OnCompletion(err)
}()
// do stuff that may or may not result in err being set
}
// the only way to modify a return value from a deferred function is using named parameters
// returns 0
func f() int {
res := 0
defer func(){
res++
}()
return res
}
// returns 1
func f() (res int) {
res = 0
defer func(){
res++
}()
return res
}
// struct embedding + methods & "inheritance"
type Base struct {
field string
}
type Derived struct {
Base
other int
}
// method 1
func (b *Base) basefn() string {
return fmt.Sprintf("Base+%s", b.field)
}
// method 2
func (d *Derived) basefn() string {
return fmt.Sprintf("Override+%s", d.field)
}
// method 3
func (d *Derived) derivedfn() string {
return fmt.Sprintf("Derived+%s", d.field)
}
func main() {
b := Base{field: "basic"}
d := Derived{Base: Base{field: "advanced"}, other: 2}
fmt.Printf("%s\n", b.basefn()) // works, calls method 1
fmt.Printf("%s\n", d.basefn()) // works, calls method 2
fmt.Printf("%s\n", b.derivedfn()) // does not compile
fmt.Printf("%s\n", d.derivedfn()) // works, calls method 3
}
// Check to see if an error chain includes a particular error
// Note: the type of p needs to correspond to the receiver type for the Error() method on SomeErrorType
// meaning, if Error() has a pointer receiver on SomeErrorType, p needs to be a pointer, otherwise, it needs to be a value
var p *SomeErrorType
errors.As(err, &p)
// You can pass the results of a multiple return function directly into another function call (as long as the types line up)
takesIntStrErr(returnsIntStrErr(x, y))
// Returning a "zero" value from a generic function
func genericFunction[T any]() (T, error) {
var zero T
return zero, nil
}
// Setting a client-side connection timeout (at least in gRPC) can be done via the context
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Duration(deadline) * time.Millisecond))
defer cancel()
conn.Operation(ctx, ...)
// when setting deadlines, the earliest deadline takes precedence
root := context.Background()
ctx1, cancel1 := context.WithDeadline(root, time.Now().Add(time.Duration(10)*time.Millisecond))
defer cancel1()
// the deadline for ctx2 will not override the parent context's deadline because 10ms < 10s
ctx2, cancel2 := context.WithDeadline(ctx1, time.Now().Add(time.Duration(10)*time.Second))
defer cancel2()
// the deadline for ctx1a will override the parent context's deadline because 5ms < 10ms
ctx1a, cancel1a := context.WithDeadline(ctx1, time.Now().Add(time.Duration(5)*time.Millisecond))
defer cancel1a()
// json.NewDecoder() ignores garbage at the end of the stream
type Thing struct {
Name string `json:"name"`
}
func parse1(stream io.ReadCloser) (Thing, error) {
t := Thing{}
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(stream); err != nil {
return t, err
}
err := json.Unmarshal(buf.Bytes(), &t)
return t, err
}
func parse2(stream io.ReadCloser) (Thing, error) {
t := Thing{}
err := json.NewDecoder(stream).Decode(&t)
return t, err
}
func main() {
// parse1 fails, parse2 succeeds
val, err := parse1(io.NopCloser(strings.NewReader(`{"name":"Bob"}PLUS GARBAGE`)))
if err != nil {
fmt.Printf("Error happened: %v\n", err)
} else {
fmt.Printf("OK: %v\n", val)
}
}
// force a compile-time error if a struct doesn't fully implement a given interface
type InterfaceName {
Func1(s string) string
}
type structName struct {}
// fails to compile due to no: func (s *structName) Func1(s string) string { return "val" }
// what is this doing?
// declares a variable of type Interface name (that we don't care about so it's assigned to _)
// and tries assigning a variable of type pointer to structName to it and the assignment will fail
// if structName does not implement all of the methods defined on InterfaceName
// - uses nil because nil can be assigned to any pointer so no need to declare a structName variable
// - uses a pointer because it is idiomatic to use pointer receivers when defining methods, if you could
// guarantee that structName would never need to use pointer receivers, you could achieve the same thing
// by assinging a zero value of structName to an InterfaceName variable.
var _ InterfaceName = (*structName)(nil)
// JSON marshaling omit attributes if not set
type Thing struct {
Value1 string `json:"value1"` // marshals "value1": "" if not set
Value2 string `json:"value2,omitempty"` // omits "value1" if not set or set to ""
Value3 *string `json:"value3"` // marshals "value1": null if not set
Value4 *string `json:"value4,omitempty"` // omits "value1" if not set or set to nil
}
// Custom JSON unmarshaling with validation: https://go.dev/play/p/jYPARNjmhVn
type Outer struct {
Name string `json:"name"`
Things []Inner `json:"things"`
}
func (o *Outer) UnmarshalJSON(b []byte) error {
var raw map[string]json.RawMessage
if err := json.Unmarshal(b, &raw); err != nil {
return err
}
if err := json.Unmarshal(raw["name"], &o.Name); err != nil {
return err
}
if err := json.Unmarshal(raw["things"], &o.Things); err != nil {
return err
}
return nil
}
type Inner struct {
Value int32 `json:"val"`
}
func (i *Inner) UnmarshalJSON(b []byte) error {
var raw map[string]json.RawMessage
if err := json.Unmarshal(b, &raw); err != nil {
return err
}
if err := json.Unmarshal(raw["val"], &i.Value); err != nil {
return err
}
if i.Value < 100 {
return errors.New("Too Low")
}
return nil
}
func main() {
var x Outer
str := `{"name": "Me", "things": [{ "val": 120 }, { "val": 30 }]}`
err := json.Unmarshal([]byte(str), &x)
if err != nil {
// fails because val=30 is < 100
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Value: %+v\n", x)
}
}
// custom JSON unmarshaling using an embedded struct to separate custom & default parsing
// https://ukiahsmith.com/blog/improved-golang-unmarshal-json-with-time-and-url/
type Descriptor struct {
Name string `json:"name"`
Key string `json:"key"`
Description string `json:"description"`
BaseURL url.URL `json:"baseUrl"`
}
func (a *Descriptor) UnmarshalJSON(data []byte) error {
// need to use a different type definition here (desc2 instead of Descriptor) to avoid infinite recursion during unmarshaling
type desc2 Descriptor
tmp := struct {
BaseURL string `json:"baseUrl"`
*desc2
}{
// embeds a pointer to the receiver in this anonymous struct to receive the other attributes as is
desc2: (*desc2)(a),
}
err := json.Unmarshal(data, &tmp)
if err != nil {
return err
}
baseURL, err := url.Parse(tmp.BaseURL)
if err != nil {
return err
}
a.BaseURL = *baseURL
return nil
}
// embedding an interface in a struct can be used for delegation
// https://go.dev/play/p/xnmxLdCXjqL
// the common interface
type Thing interface {
Increase(i int32) int32
Decrease(i int32) int32
}
// the base implementation
type thing struct{}
func (t thing) Increase(i int32) int32 {
return i + 10
}
func (t thing) Decrease(i int32) int32 {
return i - 10
}
// the wrapped implementation (embeds the common interface)
type Wrapped struct {
Thing
Amount int32
}
// changes the Increase method to delegate to the base
// implementation and then do something else
func (w Wrapped) Increase(i int32) int32 {
return w.Thing.Increase(i) + w.Amount
}
// wraps a base implementation in the wrapped implementation
func WrappedThing(thing Thing, amt int32) Thing {
return &Wrapped{thing, amt}
}
func main() {
var n int32 = 20
t := thing{}
fmt.Printf("%d, %d\n", t.Increase(n), t.Decrease(n))
wt := WrappedThing(t, 17)
fmt.Printf("%d, %d\n", wt.Increase(n), wt.Decrease(n))
}
// print full struct info (FQN & field names/values)
fmt.Printf("%#v", val)
// channels and go routines
// https://go.dev/play/p/SwLwKziIb_A
func work(num int) string {
return fmt.Sprintf("Job: %d", num)
}
func main() {
ct := 10
ch := make(chan string, ct)
var wg sync.WaitGroup
for i := range ct {
wg.Add(1)
go func() error {
defer wg.Done()
ch <- work(i)
return nil
}()
}
wg.Wait() // wait for all goroutines to finish
close(ch) // close the results channel (otherwise, range loop will never terminate)
for s := range ch {
fmt.Printf("Result: %s\n", s)
}
}
// Deserialize JSON string as int
payload = `{"identifier": "1234"}`
type Thing struct {
Id int `json:identifier,string`
}
// Closures are able to capture local variables
type Thing struct {
Val string
}
func Run(fn func()) {
fn()
}
func NewThing(v string) Thing {
return Thing{Val: v}
}
func main() {
t1 := Thing{Val: "123"}
Run(func() {
// can modify locals from a closure
t1.Val = "456"
})
fmt.Printf("t1 = %v\n", t1)
t2 := Thing{}
Run(func() {
// can reassign locals from a closure
t2 = NewThing("789")
})
fmt.Printf("t2 = %v\n", t2)
}
// initializing slices & maps
// these both allocate empty data structures that can have keys/values added to them
m := map[string]string{}
s := []string{}
// these don't allocate any storage and are nil
// nil slices can be appended to using the append function but nil maps can't accept keys/values
var m map[string]string
var s []string{}
// clearing a slice
var s []string
s = append(s, "A")
s = append(s, "B")
s = append(s, "C")
// this clears the slice by setting len = 0 but it doesn't remove any of the elements
s = s[:0]
fmt.Printf("%v\n", s) // prints []
// you can still create a subslice from s up to the slice's existing capacity, which will include the existing elements
sub := s[0:2]
fmt.Printf("%v\n", sub) // prints [A B]
// interface satisfaction
// given this interface
type Identifiable interface {
GetId() string
}
// and these implementations
type ValueThing struct {
Id string
}
func (t ValueThing) GetId() string {
return t.Id
}
type PointerThing struct {
Id string
}
func (t *PointerThing) GetId() string {
return t.Id
}
vt := ValueThing{Id: "123"}
pt := PointerThing{Id: "456"}
// compiles
TakesIdentifiable(vt)
// does not compile because PointerThing does not implement the Identifiable, *PointerThing does (due to pointer receiver)
TakesIdentifiable(pt)
// compiles since the argument is now a *PointerThing, which does satisfy the Identifiable interface
TakesIdentifiable(&pt)
// Go does not support covariance on slices, so this doesn't compile
var slc = []Identifable{ValueThing{Id: "123"}, ValueThing{Id: "456"}}
// range over func can be used to pass an slice of concrete types as a parameter to a function that takes an interface type
func TakesIdentifiable[T Identifiable](seq iter.Seq[T]) {
for val := range seq {
fmt.Printf(val)
}
}
TakesIdentifiable(slices.Values([]ValueThing{ValueThing{Id: "123"}, ValueThing{Id: "456"}}))
// "return" a value, err from a goroutine
func DoStuff() (int, error) {
return 12, errors.New("error")
}
func main() {
ch := make(chan struct {
r int
err error
}, 1)
go func() {
r, err := DoStuff()
ch <- struct {
r int
err error
}{r, err}
}()
v := <-ch
fmt.Printf("r = %d, err = %v\n", v.r, v.err)
}
// how to override a method in a test without using interfaces or mocks - somewhat contrived example
// some external dependency that defines the method named "Produce" that needs to be mocked
type ExternalDependency struct {
}
// my_code.go
type MyThing {
ExternalDependency
}
// my_code_test.go
var fakeProducer func(string) error
// tests will use this version of the Produce function, which exposes a hook function for spying/mocking
func (m *MyThing) Produce(msg string) error {
if fakeProducer != nil {
return fakeProducer(msg)
}
return nil
}
// Method Sets
// from: https://stackoverflow.com/a/33591156
/*
1. If you have a *T you can call methods that have a receiver type of *T as well as methods that have a receiver type of T.
2. If you have a T and it is addressable you can call methods that have a receiver type of *T as well as methods that have
a receiver type of T, because the method call t.Meth() will be equivalent to (&t).Meth() (Calls).
3. If you have a T and it isn't addressable (for instance, the result of a function call, or the result of indexing into a
map), Go can't get a pointer to it, so you can only call methods that have a receiver type of T, not *T.
4. If you have an interface I, and some or all of the methods in I's method set are provided by methods with a receiver of
*T (with the remainder being provided by methods with a receiver of T), then *T satisfies the interface I, but T doesn't.
That is because *T's method set includes T's, but not the other way around (back to the first point again).
In short, you can mix and match methods with value receivers and methods with pointer receivers, and use them with variables
containing values and pointers, without worrying about which is which. Both will work, and the syntax is the same. However,
if methods with pointer receivers are needed to satisfy an interface, then only a pointer will be assignable to the interface
— a value won't be valid.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment