Today I Learned

tags


2021/09/05

about go build constraints. Add

//go:build (booleanExprUsingTagNames)

to the top of the conditionally-included file. The boolean expression can be composed of tags (passed via go build -tag <tag>...) or expressions referencing the host or target os and arch.


2021/10/09

That cgo will automagically ship a C.sizeof_YourStruct for every C struct that you can reference as C.YourStruct. This is useful for pointer arithmetic via unsafe.Pointer(uintptr(something) + uintptr(intIndex * C.sizeof_YourStruct)).


2024/03/07

That go mod tidy needs to be followed by go mod vendor to keep ./vendor/modules.txt up-to-date. That go mod vendor only pulls in files that are referenced by your current project.

See https://go.dev/ref/mod#vendoring


2024/04/07

That any function named func init(){ ... } runs on load of a module. See https://go.dev/ref/spec#Package_initialization. See also https://www.digitalocean.com/community/tutorials/understanding-init-in-go.


2024/04/08

That select can only be used to race communications operations (e.g. <- myChan). See https://go.dev/ref/spec#Select_statements


2024/04/22

That Go has labels and can goto label. I’m not entirely sure what the usefulness of these is outside of switch statements, but it’s still cool!


2024/05/04

That a go.work file can point to local “main modules” used for minimum-version selection. go will maintain a separate go.work.sum file with the checksums of the workspace’s dependencies. go work {init,use,edit} manages the work-files. $GOWORK pointing to a file named like *.work can switch between multiple workspace files. See https://go.dev/ref/mod#workspaces; the syntax of *.work files is roughly equivalent to the syntax in go.mod.


That none of the following options ensure unused debug info is not included in rust wasm output:

[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"

Commenting each line and rebuilding with --target=wasm32-unknown-unknown --release resulted in 0% change in output .wasm size. In each experiment, twiggy garbage ./target/wasm*/release/my_lib.wasm reported

 Bytes  │ Size % │ Garbage Item
────────┼────────┼─────────────────────────────────────────
 280220 ┊ 33.30% ┊ custom section '.debug_str'
 181364 ┊ 21.55% ┊ custom section '.debug_info'
 159353 ┊ 18.94% ┊ custom section '.debug_line'
  96881 ┊ 11.51% ┊ custom section '.debug_pubnames'
  87936 ┊ 10.45% ┊ custom section '.debug_ranges'
   2030 ┊  0.24% ┊ custom section '.debug_abbrev'
    342 ┊  0.04% ┊ custom section '.debug_pubtypes'
     67 ┊  0.01% ┊ custom section 'producers'
     28 ┊  0.00% ┊ custom section 'target_features'
 808221 ┊ 96.05% ┊ Σ [9 Total Rows]

I cut those sections out using the 2-year-old recipe from https://github.com/Xe/x/blob/c87eb51e0afe78a958eecaffb83318f91c6f78dd/web/mastosan/README.md:

wasm-opt -oZ ...
wasm-snip \
  --skip-producers-section \
  --snip-rust-panicking-code \
  --snip-rust-fmt-code \
  ...

2024/08/19

That go build ./path/to/main.go disregards other files in the same directory/package. go build ./path/to works fine.


2024/09/24

That you an render code blocks in Go doc comments by indenting the lines of code:

// Add two numbers.
//   this
//   is a
//   code block
func Add(a, b int) int {
  return a + b
}

See https://tip.golang.org/doc/comment#code


2024/10/23

That

An empty go.mod in a directory will cause that directory and all of its subdirectories to be excluded from the top-level Go module.

https://go.dev/wiki/Modules#can-an-additional-gomod-exclude-unnecessary-content-do-modules-have-the-equivalent-of-a-gitignore-file


2024/11/25

That (1) go tries to bake VCS info into go binaries and (2) go still doesn’t understand git worktrees :/ and (3) the easiest way to get go builds to work in a git worktree or bare git repo is to

go env -w GOFLAGS=-buildvcs=false

I’m not sure how global these flags are.


2025/01/30

That you can write golang examples that get run using go test:

func ExampleHello() {
   fmt.Println("hello")
   // Output: hello
}

The naming convention to declare examples for the package, a function F, a type T and method M on type T are:

func Example() { ... }
func ExampleF() { ... }
func ExampleT() { ... }
func ExampleT_M() { ... }

https://pkg.go.dev/testing#hdr-Examples

I feel like this might be able to be combined with doc comments.


2025/02/12

That defer statements are block-scoped:

package main

import "fmt"

func main() {
	defer fmt.Println("end func") // runs at end of function
	fmt.Println("start func")
	{
		defer fmt.Println("end block") // runs at end of block
		fmt.Println("start block")
	}
}
// Output:
// start func
// statt block
// end block
// end func

see https://go.dev/play/p/2OYL2-g1iUE


2025/02/19

How to set build-time variables in go:

go build -ldflags "-X importpath.name=value"

See https://pkg.go.dev/cmd/link


2025/04/25

Apparently, the blessed way to do prettier-style line-wrapping in Go is golines: https://github.com/golang/vscode-go/issues/847#issuecomment-816992225


2025/05/01

That wrapper types preserve type information in switch statements:

package main

import "fmt"

func main() {
	type x string
	y := x("asdf")
	z := any(y)
	switch z.(type) {
	case x:
		fmt.Println("cool")
	case string:
		fmt.Println("uh")
	}
}

https://go.dev/play/p/VkDckf3vp_O


2025/05/09

Clicking “Debug Test” in VSCode doesn’t use the configuration in launch.json: https://github.com/golang/vscode-go/wiki/debugging#:~:text=the%20%22debug%20test%22%20codelens%20and%20the%20test%20ui%20do%20not%20use%20the%20launch.json%20configuration


2025/06/14

That mobile OS’s often do not define a $HOME variable:

	// On some geese the home directory is not always defined.
	switch runtime.GOOS {
	case "android":
		return "/sdcard", nil
	case "ios":
		return "/", nil
	}

Side note: hats off to the programmer that called the plural of GOOSes “geese”.


2025/06/19

That go’s runtime/debug package keeps track of the version of all modules:

package main

import (
	"fmt"
	"runtime/debug"
)

func main() {
	info, _ := debug.ReadBuildInfo()
	fmt.Printf("info: %#v", info)
}

// info: &debug.BuildInfo{
//   GoVersion: "go1.24.4",
//   Path: "play",
//   Main:debug.Module{
//     Path:"play",
//     Version:"(devel)",
//     Sum:"",
//     Replace:(*debug.Module)(nil),
//   },
//   Deps:[]*debug.Module(nil),
//   Settings:[]debug.BuildSetting{
//     debug.BuildSetting{Key:"-buildmode", Value:"exe"},
//     debug.BuildSetting{Key:"-compiler", Value:"gc"},
//     debug.BuildSetting{Key:"-tags", Value:"faketime"},
//     debug.BuildSetting{Key:"CGO_ENABLED", Value:"0"},
//     debug.BuildSetting{Key:"GOARCH", Value:"amd64"},
//     debug.BuildSetting{Key:"GOOS", Value:"linux"},
//     debug.BuildSetting{Key:"GOAMD64", Value:"v1"}}}

https://go.dev/play/p/RPP3ld4kPmD

See also https://pkg.go.dev/runtime/debug#BuildInfo

I heard about this from https://github.com/charmbracelet/fang#:~:text=automatic%20--version%3A%20set%20it%20to%20the%20build%20info%2C%20or%20a%20version%20of%20your%20choice


2025/09/24

That google’s Go style guide prefers all-same-case acronyms: https://google.github.io/styleguide/go/decisions.html#initialisms


2025/11/01

That gopls can emit UI hints about escape analysis: https://github.com/golang/vscode-go/wiki/settings#uidiagnosticannotations


2025/12/03

When compiling packages, build ignores files that end in ‘_test.go’.

https://pkg.go.dev/cmd/go#:~:text=When%20compiling%20packages%2C%20build%20ignores%20files%20that%20end%20in%20%27%5Ftest%2Ego%27%2EA


2025/12/04

That not using a pointer reciever for a custom error type can cause runtime panics:

If you use non-pointer values and folks do shallow comparisons without an Is method, you may put yourselves in a bad spot if the error type later includes a non-comparable child field (e.g., map[K]V). See https://go.dev/play/p/7fgO0EY5hWm.

https://www.reddit.com/r/golang/comments/1bd1xzf/seeking_advice_on_custom_error_types_value_vs/


2025/12/19

About os/signal.NotifyContext:

NotifyContext returns a copy of the parent context that is marked done (its Done channel is closed) when one of the listed signals arrives, when the returned stop function is called, or when the parent context’s Done channel is closed, whichever happens first.

https://pkg.go.dev/os/signal#example-NotifyContext

I’ve always used CLI frameworks like spf13/cobra, so this is the first time I’ve seen go’s built-in capability to listen for ctrl-c!


2025/12/29

That you can use golangci-lint and a //sumtype:decl comment to declare a pale imitation of rust’s enum/pattern-matching functionality.

package main

//sumtype:decl
type MySumType interface {
        sealed()
}

type VariantA struct{}

func (*VariantA) sealed() {}

type VariantB struct{}

func (*VariantB) sealed() {}

func main() {
        switch MySumType(nil).(type) {
        case *VariantA:
        } // <-- exhaustiveness check failed for sum type 'MySumType': missing cases for VariantB
}

See https://github.com/alecthomas/go-check-sumtype see https://golangci-lint.run/docs/linters/configuration/#gochecksumtype

Since this pattern/check relies on go’s fat pointers (128 bits = 16 bytes each), it’s not good for primitive/small-sized values.


2026/03/19

That go tool buildid reads a build ID from go binaries: see https://alexanderobregon.substack.com/p/go-build-id-and-reproducibility It could be useful for caching in CI systems.