golang

Aliases
  • go
  • Go
Image of Author
August 28, 2025 (last updated September 29, 2025)

Resources

Installation

Go seems to want you to download a binary to get started. For MacOS you install a package that puts the go command on your path and then from go it seems like you can do almost everything else you need to through it. The package installation takes up 200+ mb on my MacOS which is an interestingly large amount of space.

Testing

Testing for instantiation

When you use Testify in golang note that assert.Empty() will not differentiate between instantiated lists, e.g., []User{} and uninstantiated lists, e.g., []User. They are both considered empty. Instead use assert.NotNil().

Why does this matter? Because is you were to turn this list into JSON one would be an empty list "users": [] while the other would be null "users": null

Gin

https://gin-gonic.com/

Gin is a web framework.

When constructing responses in gin you can use gin.H{"results": ...}, for example, instead of constructing custom response body types. Sometimes it is better to construct custom response body types, but this is a nice convenience for those other times.

JSON unmarshaling converts all numbers to float64 by default

Even if the number in the payload has no decimal and is clearly an int, you will still need to type assert float64 and then later call int(value) to convert it.

if value, ok := deeply.Nested.Struct.Value.(int); ok {
    fmt.Prinln("this will never be true!")
}

if value, ok := deeply.Nested.Struct.Value.(float64); ok {
    fmt.Prinln("this will always be true!")
}

Embedded Structs

https://gobyexample.com/struct-embedding

type Address struct {
	City  string
	State string
}

type Person struct {
	Name string
	Address
}

type State interface {
	getState() string
}

func (a Address) getState() string {
	return a.State
}

func example() {
	p := Person{
		Address: Address{
			City: "Sydney",
		},
	}

	fmt.Println(p.City)
	fmt.Println(p.Address.City)
	fmt.Println(p.getState())
}

The parent struct references the child struct like a field without a type. Instantiation requires the typical nested struct syntax. After that you can call the child fields as if they were top level fields, e.g., Person.City. You can also call it the typical way. Methods receiving Address will also now work on Person, e.g., p.printAddress(). This also works with interfaces.

Sentinel errors

The errors package is impressively small. It is easy to wrap multiple errors with errors.Join and unwrap with errors.Unwrap. errors.New() is how you create new errors with simple text messages. An important point about errors.New() is in the function's docs:

Each call to New returns a distinct error value even if the text is identical.

This means errors.New("a") != errors.New("a"). But, err := errors.New("a"); err == err. Sentinel errors are package level errors that are expected to arise in the normal workflows of the package, e.g., var ErrEntityNotFound = errors.New("entity not found"). In tests you will need to use the sentinel error and not create new errors with the same text, since those will not pass equality checks, as seen above.

The problem with returning nil, nil

https://stackoverflow.com/questions/62947968/is-return-nil-nil-not-idiomatic-go

What is an error, semantically speaking? Is it an error for a lookup function to return no results? What about a search to return an empty list? It seems to me return nil, nil is capable of capturing a sense of there being no results as a successful outcome.

However this seems to not be conventional. Instead it is common to see something like return nil, error.NotFound. Practically speaking, I default to following conventions, and so this is what I will do. The case in favor of it I think begin with rejecting the idea that the semantic meaning of 'error' in the english language is not the same as the semantic meaning of 'error' in the go language. But this is a digression for some future blog post, perhaps, on the semantics of programming languages.

Pass values

https://google.github.io/styleguide/go/decisions#pass-values

https://www.reddit.com/r/golang/comments/17xakot/comment/k9m833a/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button

Consider two different ways to return from a function. First, a return type of (Entity, error) and when an error occurs return Entity{}, err. Second, a return type of (*Entity, error) and when an error occurs return nil, err.

The google style guide suggests you should default to passing values, which I read as arguing in favor of (Entity, error). If you have good reason to do otherwise, then you are empowered to do so, and you get the upside (in my opinion) of return nil, err which reads to me as a natural way to return an error in a function.

Pointers

  • & gives the pointer for a value
  • * dereferences the pointer value

What confused me at first is * being used as a type versus and deref. You deref a type of *string with *, not &. & is only used to create the pointer itself.

var x string
var y *string
var z string

x = "hello"
y = &x
z = *y

If you already have a pointer, var s *string, and you then write &s, you end up with a pointer to a pointer, **string.

Methods in golang

Go does not have classes but you can still define "methods" on types. This is done via what golang calls "receiver arguments".

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

(v Vertex) above is the receiver argument. The receiver type must be defined in the same package as the function definition.

Receiver types are copies by default (pass-by-value), and so cannot modify the underlying type. If you want to modify the type you need to a pointer type (pass-by-reference).

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

To quote from "A Tour of Go",

Methods with pointer receivers can modify the value to which the receiver points (as Scale does here). Since methods often need to modify their receiver, pointer receivers are more common than value receivers.

There are two reasons to use a pointer receiver. The first is so that the method can modify the value that its receiver points to. The second is to avoid copying the value on each method call. This can be more efficient if the receiver is a large struct, for example.

Interfaces and type assertions

From "A Tour of Go",

Under the hood, interface values can be thought of as a tuple of a value and a concrete type. (value, type). An interface value holds a value of a specific underlying concrete type. Calling a method on an interface value executes the method of the same name on its underlying type.

You can extract the underlying concrete type via a type assertion, where in the below i is the interface value and T is that value's concrete type.

t := i.(T)
func main() {}
  var i interface{} = "hello"

  s := i.(string)
  fmt.Println(s) // "hello"

  s, ok := i.(string)
  fmt.Println(s, ok) // "hello", true
}