Last active
          October 22, 2025 17:08 
        
      - 
      
- 
        Save danhodge/f234a6baf9da695c9ddd17f831d9dc2a to your computer and use it in GitHub Desktop. 
    Go Notes
  
        
  
    
      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
    
  
  
    
  | // 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