Harder than scripting, but easier than programming in C

Flexible to Modifications

While the incoming JSON map in Listing 3 defines values for the a, b, and d keys, the receiving keyVal struct contains fields named A, B, and C. As the output data={A:x B:y C:} of the binary compiled from Listing 3 shows, everything still turns out fine.

The excess JSON entry d was silently ignored by the receiver. It left the key c, which was missing in JSON, uninitialized in the Go structure, by leaving the field at its null value, which for strings is the empty string. In this way, Go programs can handle modified JSON data, with existing fields missing or new fields added during development without crashing or aborting with an error.

But to make Go programs actually populate newly added Go struct members with new JSON fields, you have to modify the code by extending the struct and to recompile the program. By the way, there is also the trick that lets you transfer a JSON object directly into a Go map (Go's dictionary data type) and thus adapt the Go program dynamically to changing JSON structures. But die-hard Go wizards will turn up their noses at this, because it opens the door to ignored type errors.

Go is not at all lenient in cases of clear type violations, such as a struct member of the string type arriving as an integer in JSON. In this case, json.Unmarshal() returns an error that the program has to field and hopefully raise an alert with a helpful message.

Runes, Characters, and Bytes

Go interprets program code as UTF-8 encoded (i.e., in the space-saving standard encoding of the Unicode character set). A string in Go contains a series of Unicode code points known as runes in the Go lingo. If you iterate over a string using the range operator, what you get back are runes that represent both ASCII characters and umlauts equivalently.

If you prefer to tinker with raw bytes, it is better to use byte arrays of type []byte instead of strings. Not only is this faster, but it also has the side effect that the code can both read the array and modify it. Strings are immutable in Go.

If, for example, a character is now fetched from a string in order to use it in a hash table (e.g., to store the string under the key of the initial letter in the Go map), you need to pay close attention to the data types, as shown in Listing 4. Otherwise, the compiler will complain and refuse to do its work.

Listing 4

hash.go

01 package main
02
03 import (
04   "fmt"
05 )
06
07 func main() {
08   hash := map[rune]string{}
09   str := "abc"
10
11   for _, ch := range str {
12       hash[ch] = str
13   }
14
15   key := 'a'
16   fmt.Printf("%c: %s\n", key, hash[key])
17 }

The hash table (map) in line 8 assigns keys of the rune type to entries of the string type. The two curly braces at the end of the declaration point to the table's initialization data, which in this case is simply left empty.

The for loop starting in line 11 then iterates over the runes of the string "abc". It creates a map entry for each rune under the respective letter, each of which points to the complete string. Line 16 then reaches into the table and retrieves the entry with the a key and then prints: a: abc.

Regarding the for loop starting in line 11, the range operator iterates over all entries in the data structure passed to it, returning two values for each entry: the current index starting at   and the value of the entry. But Listing 4 is only interested in the individual characters in the string and does not need the index counter. This is why it assigns the index to the _ (underscore) variable. This has the effect of making Go discard the value unseen. It also avoids the error message that would otherwise come up if the index were assigned to a variable i that is not used anywhere else.

Memory (Almost) Automated

Maps grow automatically with their requirements and use dynamically increasing chunks of memory. Go manages memory seemingly automatically. Initializing a hash table in the previous example ensures that a subsequent statement such as

data["a"]="abc"

will work without an explicit memory allocation. For both the keys in the hash table and the values assigned to them, Go internally makes sure that enough memory is reserved.

When a function generates a hash table and returns it, the table remains valid in the main program. If at some later point no one references the table, the garbage collector [4] cleans it up in due time and releases the allocated memory without the programmer having to worry about a thing.

Things get more complicated when you have a two- or multi-dimensional data structure. Then Go programmers need to initialize the structure separately at each level. Scripting languages such as Python or Ruby simply declare a two-dimensional array or hash map, and the runtime environment ensures that a new data[i][j] entry accesses automatically allocated memory and does not end up in a black hole. But anyone who tries this in Go will run into trouble. A script such as the one in Listing 5 compiles without complaint, but triggers the runtime error:

panic: assignment to entry in nil map

Listing 5

dimfail.go

package main
func main() {
  twodim := map[string]map[string]string{}
  twodim["foo"]["bar"] = "baz" // panic!!
}

Listing 6, in contrast, gets it right. Before the program accesses the second level of the hash table, line 5 assigns a freshly allocated sub-hash map to the first-level entry. From this point on, entries are allowed access on two levels. However, it is important to create the sub-hash for each new entry on the first level before accessing the second level.

Listing 6

dimok.go

01 package main
02
03 func main() {
04   twodim := map[string]map[string]string{}
05   twodim["foo"] = map[string]string{}
06   twodim["foo"]["bar"] = "baz" // ok!
07 }

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