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
(incl. VAT)
Buy Linux Magazine
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.
News
-
Halcyon Creates Anti-Ransomware Protection for Linux
As more Linux systems are targeted by ransomware, Halcyon is stepping up its protection.
-
Valve and Arch Linux Announce Collaboration
Valve and Arch have come together for two projects that will have a serious impact on the Linux distribution.
-
Hacker Successfully Runs Linux on a CPU from the Early ‘70s
From the office of "Look what I can do," Dmitry Grinberg was able to get Linux running on a processor that was created in 1971.
-
OSI and LPI Form Strategic Alliance
With a goal of strengthening Linux and open source communities, this new alliance aims to nurture the growth of more highly skilled professionals.
-
Fedora 41 Beta Available with Some Interesting Additions
If you're a Fedora fan, you'll be excited to hear the beta version of the latest release is now available for testing and includes plenty of updates.
-
AlmaLinux Unveils New Hardware Certification Process
The AlmaLinux Hardware Certification Program run by the Certification Special Interest Group (SIG) aims to ensure seamless compatibility between AlmaLinux and a wide range of hardware configurations.
-
Wind River Introduces eLxr Pro Linux Solution
eLxr Pro offers an end-to-end Linux solution backed by expert commercial support.
-
Juno Tab 3 Launches with Ubuntu 24.04
Anyone looking for a full-blown Linux tablet need look no further. Juno has released the Tab 3.
-
New KDE Slimbook Plasma Available for Preorder
Powered by an AMD Ryzen CPU, the latest KDE Slimbook laptop is powerful enough for local AI tasks.
-
Rhino Linux Announces Latest "Quick Update"
If you prefer your Linux distribution to be of the rolling type, Rhino Linux delivers a beautiful and reliable experience.