Last active
November 13, 2024 14:39
-
-
Save danhodge/f234a6baf9da695c9ddd17f831d9dc2a to your computer and use it in GitHub Desktop.
Go Notes
This file contains 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) | |
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) | |
// 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 | |
} | |
// 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 | |
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) | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment