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:

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.