Pet and Go
2025-09-27
For a couple lives pet avoided Go, but it seems to be about time to start using it. Not for anything serious, just for something to compare PetLang with.
Pet would prefer Python, but there's a huge difference between static and dynamic languages. Ask any human: which one is faster, Go or Python? And the instant answer will be Go.
This memo is just a dump of a virgin (well, almost) pet's brain after feeding it with Go. Pet struggles to be neutral, but it has to collect pros and cons which are quite subjective.
Pet installs Go
Pet simply downloads and unpacks binaries. Pet believes they aren't worth any effort to be compiled from sources.
Kate settings
Pet isn't fond of gospeak formatting. It prefers space-only indentation, so it has to change settings of its favorite Kate editor.
First, pet opens settings dialog: Settings -> Configure Kate
Second, pet chooses Open/Save panel and Modes & Filetypes tab.
Then, pet sets the following value in the Variables field for Sources/Go file type:
kate: indent-mode cstyle; indent-width 4; newline-at-eof true; replace-tabs true; tab-width 4;
Kate needs to be restarted to make changes take the effect
(unfortunately, the first sucks
in this memo goes to Kate).
Hello, world
The application pet wants to create is a static site generator, but the journey starts from "Hello, world."
Pet creates a file named sitegen.go
with the following content:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
What's next? Pet asks:
go help
and runs
go build
It fails.
In Go, the only source file is not sufficient.
There needs to be go.mod
along with it.
Pet creates that with the following content:
module main
Basically, it's a guesswork.
From pet's perspective, the Go Modules Reference
is too difficult to understand by a beginner, and
the Go Tutorial does not explain
what go mod init
exactly does.
Okay, after creating go.mod
pet runs go build
again.
It generates ridiculously big main
executable: 2.15M.
After stripping it becomes 1.43M but that's too big anyway.
The next question is how to change the name of executable.
By default go
uses the name of package.
But if pet changes it, the executable isn't built.
The main
function should be in the main
package.
The Go Modules Reference has no relevant occurences
of executable
, output
, and file name
.
Stackoverflow came to rescue
but the only reasonable option pet has found is:
go build -o bin/ sitegen.go
i.e. use bin
directory for output and specify source file explicitly.
Of course, this works too:
go build -o sitegen
but pet has a gut feeling it may cause problems if go build
is used for a project with multiple output executables.
Pet also found this recipe but it did not work.
Pet starts feeling a little nausea, but suddenly it stumbles across another stackoverflow page which provided the solution:
- the file that contains
main
function must containpackage main
declaration, the name of file does not matter - the name of the executable is defined by
package
directive ingo.mod
file and that package does not need any corresponding.go
source file at all
It's weird. Does such design decision suck? Definitely, it does. Especially the documentation.
Getting command line options
A real application needs some configuration and command line options is a simplest form.
In gospeak, command line parsing package is named flag
, but unlike another ridiculous name Dial
(that stands for connect
in both petspeak and humanspeak), this didn't deposited in pet's
brain after previous Go sessions for some reason.
Okay, let it be flags.
The documentation says
If you're using the flags themselves, they are all pointers; if you bind to variables, they're values.
Fuck!
This means one-liners like this make pointers:
my_arg := flag.String("my-arg", "default value", "Just an argument")
POINTERS!
Why pointers? All possible flag data types take less bits on 64-bit architectures than pointers. Except strings, but even strings are always passed by value in rest of the Go Standard Library. What complicated data structures do command line arguments use so they need pointers? And why? For super efficiency?
Fuck.
So, the simplest and most frequent use case needs two lines instead of one.
That sucks.
Well, okay.
Basically, pet needs --src-dir
and --dest-dir
command line options.
Both are mandatory.
But there's no way to make command line options mandatory in Go!
No way to make command line options mandatory in Go!
That sucks more than twice.
Pet does not want this memo negatively biased, but it recalls that the development of Go commenced approximately at the same time when Google search quality started to decline.
Again, why "flags"?
Pet thinks this term means something important to Go developers and
consults the Cambridge dictionary.
But the only relevant meaning of flag
seems to be
to become tired, weaker, or less effective
which probably reflects the state of a person at google who was working off their salary implementing command line parsing.
Despite pet starts feeling sick, it has to proceed with Go.
Source code formatting
Why can't pet write
func main()
{
}
???
The compiler says
syntax error: unexpected semicolon or newline before {
Why?
Well, it could be understandable for pet if grammar nazi go fmt
complained about that,
but why making the syntax analyzer to fail?
That sucks.
Third party modules
Maybe pet will write its MYAW parser someday, but for now it's okay to use YAML.
The most up to date parser seems to be github.com/goccy/go-yaml
.
Pet downloads zip file from github and unpacks it to the project directory.
As of time of writing, the name of module subdirectory is go-yaml-1.18.0
.
After reading official Go documentation pet adds the following lines to go.mod
:
require (
github.com/goccy/go-yaml v1.18.0
)
replace github.com/goccy/go-yaml v1.18.0 => ./go-yaml-1.18.0
However, go build
complains
go: updates to go.mod needed; to update it:
go mod tidy
Okay, pet runs go mod tidy
and that removes require
directive.
Fuck, why?
Pet re-reads https://go.dev/ref/mod#go-mod-file-require
and understands that it understands nothing.
Pet runs go build
again and gets the following error:
module github.com/goccy/go-yaml provides package github.com/goccy/go-yaml and is replaced but not required; to add it:
go get github.com/goccy/go-yaml@v1.18.0
Pet is rolling on the floor from laugh.
Well, pet should clarify that it treats binaries from google as malware and runs them offline. Pet does not want to setups a disposable system and tries to figure out what it might miss.
What exactly?
What exactly leads to such circular errors?
As a guesswork, pet gives go
directive a try and voila! It works!
Well, if this directive is so important in go.mod
, why didn't they make it mandatory?
Not the version it declares, as the documentation states
The go directive sets the minimum version of Go required to use this module. Before Go 1.21, the directive was advisory only; now it is a mandatory requirement: Go toolchains refuse to use modules declaring newer Go versions.
Not the version it declares, but the directive itself.
Why compilation does not fail when this directive is missing? For sake of "Hello, World!" simplicity?
Doesn't this point deserve sucks
? From pet's perspective it does, definitely.
Markdown to HTML and code highlighting
Pet looks into HUGO and finds two major packages for that purpose:
While goldmark
is self-sufficient, chroma
needs a few dependencies.
It's time to create deps
directory for all third party packages.
This requires simple changes in go.mod
, like this:
-replace github.com/goccy/go-yaml v1.18.0 => ./go-yaml-1.18.0
+replace github.com/goccy/go-yaml v1.18.0 => ./deps/go-yaml-1.18.0
It was quite easy for pet to write its own static site generator peeping at HUGO codebase.
Pet borrowed markup/codeblocks/render.go
, but implemented syntax highlighting
using examples from Chroma.
The resulting code is here.
The ease of working with Go code deserves "rulez". Twice.
Miscellaneous complaints
New variables
Pet often sees the following error:
no new variables on left side of :=
I.e.
a, err := foo() // ok
err := bar() // error, must be err = bar()
If pet reorders these statements, it has to change '=' to ':='
But:
a, err := foo() // ok
b, err := baz() // okay too, despite err is not new!
Pet is unable to comprehend this desigh decision.
That sucks. A little, but it does.
Flat import namespace.
If package a
has subdirectory extras
and package b
also has the same directory,
the following code is impossible:
import (
"a"
"b"
)
a.extras.foo()
b.extras.bar()
Intelligent Go coders would say "the above is written by a retard, the right way is":
import (
a_extras "a/extras"
b_extras "b/extras"
)
a_extras.foo()
b_extras.bar()
Pet has to admit they're right. In terms of Go. But the approach sucks anyway.
Go rulez
Go is not totally bad.
First, pet loves the way of return values declaration.
Second, Go is quite easy to work with, although the learning curve looks like a spiral through all its quirks.
Third, ... What is the third? Pet does not know at the moment.