Last active
July 2, 2024 21:24
-
-
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 {} | |
// fails to compile due to no: func (s *structName) Func1(s string) string { return "val" } | |
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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment