tags
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.
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)).
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
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.
That select can only be used to race communications operations (e.g. <- myChan).
See https://go.dev/ref/spec#Select_statements
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!
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 \
...
That go build ./path/to/main.go disregards other files in the same directory/package.
go build ./path/to works fine.
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
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
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.
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.
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
How to set build-time variables in go:
go build -ldflags "-X importpath.name=value"
See https://pkg.go.dev/cmd/link
Apparently, the blessed way to do prettier-style line-wrapping in Go is golines:
https://github.com/golang/vscode-go/issues/847#issuecomment-816992225
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
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
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”.
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
That google’s Go style guide prefers all-same-case acronyms: https://google.github.io/styleguide/go/decisions.html#initialisms
That gopls can emit UI hints about escape analysis: https://github.com/golang/vscode-go/wiki/settings#uidiagnosticannotations
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
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/
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!
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.
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.