Harder than scripting, but easier than programming in C

Batteries Included

The all-inclusive binaries that the Go compiler produces turn out to be really useful. For example, if you want to run a Go program on a shared server offered by your favorite budget hoster, you simply compile it in peace on your home machine, even in a Docker container (or on a Mac if you are so fancy), and upload a single file that runs there, without a murmur of protest. No pestering about dependencies, no problems with shared libraries or additional modules that you need to install, and, of course, you won't need root privileges.

This may seem like a solution to a relatively trivial problem. But if you have ever tried to install a DIY Python script for a customer who either didn't have the right Python version, didn't have all the required packages, or perhaps even didn't have an Internet connection to make up for this missing infrastructure, you'll welcome a single ready-to-run binary as a savior.

If you distribute your software publicly and want to save users the trouble of compiling from the Go source code, you can also offer a binary for download on a website. Mind you, just one binary for all Linux variants – and then maybe one for macOS and maybe even a third one for the ARM-based Raspberry Pi. The build machine doesn't even have to run the target platform's architecture. If you want to create a Linux binary on the Mac, you do it with:

GOOS=linux GOARCH=386 go build ...

because Go supports cross-compiling perfectly. It can even create Windows binaries.

Although Go binaries naturally occupy more disk space than dynamically linked programs, compared to a 16TB hard disk, a 2MB "Hello World" binary in Go seems pretty insignificant – especially compared to the dependency hell the installer would inevitably have to descend into.

Draw from GitHub

A language does not live on its core alone. It is also important for as many volunteers as possible to continuously write new extensions and make them freely available to the community. This may sound crazy, but Go can actually reference third-party libraries from code repositories such as GitHub directly from the Go code. The compiler then fetches the source code through the network directly from the original server, along with the dependent packages. For example, Listing 2 uses the progressbar library on GitHub, which draws a beautiful progress bar in the terminal window. In its import section, the program references the project's GitHub page and assigns it the (optional) pb short form.

Listing 2

pb.go

01 package main
02
03 import (
04   pb "github.com/schollz/progressbar/v3"
05   "time"
06 )
07
08 func main() {
09   bar := pb.Default(100)
10   for i := 0; i < 100; i++ {
11     bar.Add(1)
12     time.Sleep(400 * time.Millisecond)
13   }
14 }

Faced with the spontaneous go build of Listing 2, however, the Go compiler would grumble about the missing library, but a preceding go get with the GitHub path listed in the code brings in the progress bar source. Instead of calling go get, however, many developers today define a Go module instead with

go mod init NAME

which remembers dependencies in a newly created go.mod file. A subsequent go build handles the task of fetching the new code, including its dependencies, and linking it all to the existing code in one fell swoop.

Figure 1 shows that the code from Listing 2 compiles smoothly after creating a new Go module. The subsequent go build call succeeds because the compiler drags in a version of the progressbar library directly from GitHub. Imagine the possibilities: Just about anybody can park new Go libraries on GitHub to share them with the world, and the world can just as easily access them at compile time.

Figure 1: Go code references and pull libraries from GitHub.

Attentive readers will notice that this approach simply postpones the dependency hell problem from installation time to compilation time. If Go code relies on open source projects on GitHub, a binary that has been compiled once will always continue to run and can also be reinstalled without any problems. The build process for new versions, however, could encounter a problem if the library author mothballs their GitHub project or makes non-backward compatible changes: That would pull out the support for the user projects relying on the library.

Native JSON

System components often communicate over the network using data in JSON format. Go also takes this approach, packaging its data structures into JSON and unfolding them at the receiving end without any hitches. Naturally, as a rule of thumb in JSON, typed data chafes against Go's strict type model, but Go is to some extent lenient here.

Listing 3 first defines a keyVal data structure with three components A through c, each containing a string as its value. To allow the Go code to access the struct's fields outside the current package scope, the field names begin with a capital letter. JSON, on the other hand, traditionally uses lowercase keys in its data structures. This requires some back-and-forth conversions between Go internal variables and their JSON counterparts.

Listing 3

json.go

package main
import (
  "encoding/json"
  "fmt"
)
type keyVal struct {
  A string `json:a`
  B string `json:b`
  C string `json:c`
}
func main() {
  jsonStr := []byte(`{"a": "x", "b": "y", "d": "z"}`)
  data := keyVal{}
  err := json.Unmarshal(jsonStr, &data)
  if err != nil {
    panic(err)
  }
  fmt.Printf("data=%+v\n", data)
}

Mapping of the Go struct member names to the JSON names is driven by the backticked text following the field definition. An entry such as

A string `json:a`

specifies that the A member of the Go structure of type KeyVal is a string that arrives as a in JSON.

The Go receiver responds quite flexibly to variations in the JSON data. If a value arrives in JSON under a key that the receiving Go struct does not define, the json.Unmarshal() read function simply ignores it. Conversely, if the Go struct contains an entry that does not exist in the incoming JSON, Go leaves the structure field uninitialized.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Rat Rac

    If program parts running in parallel keep interfering with each other, you may have a race condition. Mike Schilli shows how to instruct the Go compiler to detect these conditions and how to avoid them in the first place.

  • Simultaneous Runners

    In the Go language, program parts that run simultaneously synchronize and communicate natively via channels. Mike Schilli whips up a parallel web fetcher to demonstrate the concept.

  • Fighting Chaos

    When functions generate legions of goroutines to do subtasks, the main program needs to keep track and retain control of ongoing activity. To do this, Mike Schilli recommends using a Context construct.

  • Motion Sensor

    Inotify lets applications subscribe to change notifications in the filesystem. Mike Schilli uses the cross-platform fsnotify library to instruct a Go program to detect what's happening.

  • Progress by Installments

    Desktop applications, websites, and even command-line tools routinely display progress bars to keep impatient users patient during time-consuming actions. Mike Schilli shows several programming approaches for handwritten tools.

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News