StateDB: The first few days, Part A - Learning Go basics
Learning to build and run Go, declare variables, read configuration, and group settings in a struct.
The first few days of StateDB were mostly spent learning the basics. What is a package? How do I build this? How do I run it? How do I import something so I can talk to a database?
When I said this was a project to learn Go, this is what I meant.
This is Part A of my notes from those first few days, covering the language basics and configuration. Part B picks up with pointers, contexts, and getting to a database query.
Building and running
One of the commands I ended up using was:
go build -buildvcs=false
At this point, I haven’t put the project in a repository yet (I know, not the smartest thing). The flag tells Go to leave version-control information out of the build. That doesn’t mean every project outside Git needs it; figuring out the exact reason it came up in my setup is a separate question.
I was also learning the difference between building a program and running it. For a simple executable in the current directory:
go build -o statedb .
./statedb
That builds an executable, then runs it on Linux or macOS. In PowerShell on Windows, the equivalent is go build -o statedb.exe ., followed by .\statedb.exe.
Or, while working on it:
go run .
That compiles and runs the program in one step.
What is a package?
Before getting very far, I had to understand what package meant at the top of a Go file.
A package groups related Go code. Files in the same directory generally belong to the same package. package main, with a main function, is how an executable program starts.
That’s a pretty basic starting point, but it was part of what I needed to learn before the rest made sense.
What does := mean?
I also had to learn what := meant. Even declaring a variable was something to stop and figure out.
Inside a function, := declares a variable and gives it a value, with Go figuring out the type:
name := "StateDB"
Here, name is a string. Once it’s declared, = assigns it a new value:
name = "StateDB development"
One detail to remember: := needs at least one new variable in the current block. It isn’t something to use every time a value changes. It’s also only available inside functions; outside them, I can use var.
Small things like this were part of those first few days, too.
Doing the check in the if
Then there were compact patterns like this:
if err := db.Ping(ctx); err != nil {
log.Fatal(err)
}
The part before the semicolon calls db.Ping(ctx) and stores the result in err. The part after it checks whether err is non-nil. If it is, log.Fatal(err) logs the error and exits the program.
The declaration and the check fit into one if statement. That err is scoped to the if, including an else branch if there is one; it isn’t available after the statement.
Reading environment variables
I also had to figure out how to read configuration from environment variables:
host := os.Getenv("DB_HOST")
This uses Go’s standard-library os package, which needs an import "os" in the file. os.Getenv reads DB_HOST from the program’s environment and returns a string. The := declares host and gives it that value.
Printf versus Println
Then I had to learn the difference between Printf and Println.
Both are in the standard-library fmt package, so the file needs import "fmt". Using the host value from above:
fmt.Println("Database host:", host)
fmt.Printf("Database host: %s\n", host)
These print the same line. Println puts spaces between its arguments and adds a newline at the end. Printf uses a format string: %s is replaced by the string value of host, and \n adds the newline. Printf doesn’t add that newline automatically.
For just printing a value, Println is straightforward. When I want to control how the values fit into a message, Printf gives me that control.
Grouping settings in a struct
I also learned what a struct was. For the database settings, that looked like this:
type DatabaseConfig struct {
Host string
Port string
User string
Password string
Database string
}
This defines a type called DatabaseConfig. A struct groups named fields together, and each field has a type. Here, all five fields are strings.
config := DatabaseConfig{
Host: os.Getenv("DB_HOST"),
Port: os.Getenv("DB_PORT"),
User: os.Getenv("DB_USER"),
Password: os.Getenv("DB_PASSWORD"),
Database: os.Getenv("DB_NAME"),
}
Those environment variable names are an example; they need to match what’s set for the program. Now config.Host gives me the host, and I can pass the configuration around as one value instead of five separate strings.
A starting point
This part was about learning to read and write the basics: packages, variables, error checks, environment variables, formatted output, and structs.
In Part B, I put those pieces to use connecting to PostgreSQL and reading rows.