What makes Go an easy-to-learn language is that everything you need to know about a Go program is right on the surface. There is no need to remember any language rules, as they are all in the application code. This chapter covers the basics of the Go programming language, like accepting input, using math operators and packages, managing memory, using different data structures, and understanding the program flow of a Go-based application. It also covers using functions and returning multiple values, as well as reading and writing data to files and handling JSON data.
Ancestors of Go
Go is based on a number of different languages. It was originally designed to be the next generation of the C language. It’s capable of performing everything you can do with C, including system programming, application development, and so on.
Go Syntax
- 1.Case sensitivity
You must spell identifiers like functions, variables, and type names in the exact manner mentioned in the official documentation (https://go.dev/doc/).
Names for packages and variables use lowercase and mixed case.
Methods and fields of a type are initial capped.
- In Go, initial uppercase characters indicate a special purpose.
Equivalent to the public keyword in other high-level programming languages like C++, C#, Java, and so on, in Go, an initial uppercase character indicates that the symbol is exported.
Similarly, equivalent to the private keyword, in Go, a lowercase initial character indicates that the particular method or variable is not exported nor accessible by the rest of the application other than the code block it is declared in.
- 2.Semicolons can be skipped
For the sake of reducing the amount of typing a developer has to do, as shown in Listing 2-1, Go eliminates semicolons from the ends of lines. This means that line feeds end the statement and no semicolons are required.
var color [2] stringcolors[0] = "black"colors[1] = "white"Listing 2-1Semicolons Are Not Required as End-of-Line Indicators
Even though the language specification requires semicolons, it is not necessary to type them explicitly. Thanks to Lexer, the software component that parses the code and analyses it, semicolons are added during the compilation process as needed.
The Rule Followed by Lexer for Adding SemicolonsWhen a statement is determined to be complete and Lexer encounters a line feed, meaning it’s the end of the statement and the developer has not explicitly added a semicolon, Lexer will do so.
NoteYou can’t always add line feeds freely or break up statements with extra line feeds, as you can in other languages, because in certain cases they can be misinterpreted by Lexer.
- 3.Code blocks with braces
Code blocks, i.e., multiple lines of code that are intended to be grouped together, are wrapped within braces. See Listing 2-2.
sum := 0for i :=0; i<10; i++{sum += i}fmt.Println(sum) //prints '45'Listing 2-2Braces Are Used to Indicate Code Blocks
In Listing 2-2, the variable named sum is declared and then assigned a value of zero. The for loop is initialized from 0 to 10 and is used to increment the value of the sum by i. After the loop terminates, the value stored in the sum variable is printed using a function called Println or “print line”. Println is a built-in function of the package called FMT. The code that you want to iterate over is wrapped between two braces. Note that it is important to make sure that the opening brace is on the same line as any preceding statement.
- 4.Built-in functions
- Go supports a set of several built-in functions that are always available in your code without having to import anything. These functions are members of a special package named Builtin. The Go compiler assumes that the Builtin package is always imported. Example functions include the following:
len(string) returns the length of the string.
panic(error) stops execution and displays an error message.
recover() manages the behavior of a panicking goroutine.
For more information, you can visit the official documentation of the builtin package at https://pkg.go.dev/builtin.
Installing Go
To install the Go compiler, visit https://go.dev/dl/ and download the binary release suitable for your system. Just run the installer to install the compiler on your system.
If you type the path command at the command prompt, you can see where all the Go executable commands are saved. If you can see the path to the directory where Go is installed, e.g. C:UsersUserNamegoin, you are ready to build programs in Go.
If you can see the version, you’re ready to start programming using the Go language. The first element you’ll want to explore is the Go Playground.
Go Playground
The Go Playground is a completely free-to-use service with no limitations. There are no requirements for user registration or licensing fees. Furthermore, it has no limitations on the number of source code files you can work with or the number of times you can run your code. It is a great way to test your Go code without creating or compiling any local source files. Nonetheless, there are other several IDEs available that can be used to develop Go applications.
Developing Go Applications Using IDEs
There isn’t a single integrated development environment (IDE) for good programming that’s endorsed or even developed by the Go development team. There are, however, many plug-ins for commercial and open-source IDEs that have been created by the Go community or by IDE vendors, which you choose mostly depending on what development environments you’re already familiar with. In this book, we use Go Playground and Visual Studio Code to write and run Go programs. Let’s jump into how to get started programming Go applications.
Getting Started Programming Go Applications
Declaring package name
Importing package(s)
Declaring and defining function(s)
Variable(s)
Expressions and statements
Comment(s)
Let’s Print Hello World!
Basic Program that Illustrates Different Parts of a Go Program
Different parts of a Go program
Packages: In the first line of a Go program, packages are always mentioned. In Listing 2-3, the statement package main indicates the name of the package to which the program belongs. This statement is mandatory because, in Go, all programs are organized and run in packages. For any program, the starting point of execution is the main package. Furthermore, with every package, a path and name are associated.
Import: The import keyword is used to import different packages the application will use. It is a preprocessor directive that instructs the Go compiler to include all the files in the mentioned package. In the previous example, we imported the fmt (format) package, which provides different functions for formatting input and output.
Comments: In Go, double forward-slashes // are used to indicate a single-line comment in code. Multi-line comments are enclosed with the /* */ block.
Func: The func keyword is used to declare functions; in this case, a function named main. It is important to enclose the body of each function within curly braces {}. This is necessary for the Go compiler to know where each function starts and ends.
Main function: Execution starts at the main function in the main package, which makes the main identifier very important. The compiler will throw an error if the main function is excluded.
One of the built-in functions available in the fmt package is the Println(...) function. By importing the fmt package, you also export the Println method. The Println function is used to display output on the screen.
In Go, identifiers that are capitalized indicate that the particular identifier is exported, for example, the initial letter of the Println method is a capital letter. In Go, the term “exported” means the function, constant, or variable can be accessed by the importer of that particular package.
How to Execute a Go Program
In order to execute the program, click the Run button on the Go Playground. Your code will be compiled and executed on Google servers and the output will be displayed on the screen.
Interestingly, the compiled application runs faster than the go run command. Building the source code file will result in the creation of a compiled binary file specifically designed to work on your current operating system. Even though it is possible to compile your programs for other operating systems, by default, the compiler will generate the binary compatible with your operating system.
Keywords
Go’s Keywords
chan | const | goto | interface | struct |
defer | case | func | var | select |
break | for | go | import | continue |
default | fallthrough | switch | type | range |
if | else | return | package | map |
Now that you are aware of its keywords, you are ready to learn how to use variables and other data structures in Go.
Variables
Like other programming languages, variables are used in Go to store data in memory. Being a statically-typed language, Go requires that each variable be assigned a type. Once a type is assigned it cannot be changed. There are two ways to set the type for a variable—explicitly or implicitly. By explicitly, we mean that at the time of declaration, the name of the type is mentioned. Implicitly means that, based on the variable’s initial value, the compiler will infer the type.
In Go, every variable should have a type. The type plays an important role in determining the layout and size of the variable’s memory, the allowed value ranges, and the set of operations applicable to the variable.
Variable Data Types
Boolean: Bool represents Boolean data in the form of true/false. True and false are the only two values that a Boolean data type can be assigned.
String: In Go, a variable of string type contains a series of characters.
- Numeric data types:
- Integers: Used to store whole numbers.
Fixed Integer: Included formats are uint8, uint16, uint32, int8, int16, and int 32. These are used to declare unsigned or signed integers. The numbers in the name of the formats are the bits that have an effect on the range of numbers that a variable can be assigned.
Float: Used to store floating-point numbers. Supported formats include float32 and float64.
Complex: Complex numbers contain two parts: the real number and imaginary number. Supported formats are complex64 and complex128.
Aliases: These can be used instead of full type names, such as byte (same as uint8), uint (32 or 64 bits), rune (same as int32), int (same as uint), and uintptr (an unsigned integer that is used to store uninterpreted bits of a pointer value).
- Data collections: Go also has built-in types for different data collections.
Arrays and slices: Used to manage ordered data collections.
Maps and structs: Used to manage aggregations of values.
Enumeration: Used to store a set of named constant values.
- Language organization types:
Function: Go considers function a type. This allows the passing of one function as an argument to another.
Interfaces: Used to specify a set of one or more method signatures.
Channels: Used to connect goroutines.
- Data management:
Pointers: Store direct addresses of memory locations (variables).
Other than the built-in types, Go also allows programmers to create user-defined data types.
Variable Naming Conventions
A variable name can be made up of letters, digits, and underscores. However, the variable name should begin with either an underscore or a letter. The lower- and uppercase letters are treated differently, because of the case-sensitive nature of the Go language.
Declaring Variables
As Go is statically-typed, it requires you to set the type (explicitly or implicitly) of each variable used in your program during the compilation process. The assigned type cannot be changed at runtime.
Ways to Declare Variables in Go
In the case of implicit declaration, the variable type is inferred based on the initial value assigned to the variable. Line#20 in Listing 2-4 illustrates implicit declaration. Note that the := operator only works for variable declarations inside of functions. The var keyword has to be used to declare variables outside the functions.
Constants are always declared outside of functions. If the name of the constant variable starts with a lowercase letter, that means it’s available for use only by the functions of the program (like the private keyword). The variable is public when its name begins with a capital letter.
Taking User Input
In the previous section, you learned about the use of the fmt package through which you can output different messages on the screen. This section demonstrates how to read user input from the console, store it in variables, or echo it to the screen. Note that standard input (stdin) is a stream that is used to read input data.
One important point to remember regarding Go Playground is that it does not support interactive programs and hence cannot read from os.Stdin. Due to this, it is better to use other IDEs, like Visual Studio or Code, to test the code in this book whenever user input is required.
Scan, Scanf, and Scanln can be used to read input from os.Stdin.
Fscan, Fscanf, and Fscanln can be used to read input from a specified io.Reader.
Sscan, Sscanf, and Sscanln can be used to read input from an argument string.
Using scanf
Using the Built-In Scanf Function to Take User Input
Using Scanln
Using the Scanln Built-In Function to Take User Input in Go
Scan vs Reader Functions
To split space-delimited tokens, you use the Scan functions and to read full lines, you used the reader function.
Using bufio
Buffered I/O is implemented via the bufio package. The bufio package wraps the io.Writer or io.Reader objects and returns a new Writer or Reader object, which has the necessary interface implemented for utility methods. This wrapping also provides textual I/O assistance and buffering.
Using the bufio Built-In Function to Take User Input in Go
As illustrated, if you want to ignore a variable in Go, you name it with an underscore.
Now that you know how to use variables and take user input in your Go programs, it is time to learn how to use different math operators in Go.
Math Operators and Packages
List of Arithmetic Operators in Go
Arithmetic Operators | + | Sum operator is used for addition. |
- | Difference operator is used for finding differences. | |
* | Multiply operator is used for finding products. | |
/ | Quotient operator is used for finding the quotient after division. | |
% | Remainder operator is used for finding the remainder after division. | |
-- | Decrement operator decreases one from the value of its operand and stores the result in the operand. | |
++ | Increment operator increases one from the value of its operand and stores the result in the operand. |
List of Bitwise Operators in Go
Bitwise Operators | & | Binary AND operator. If the bit is present in both operands, the binary and operator copy it to the result. |
| | Binary OR operator. If the bit is present in either of the operands, the binary or operator copies it to the result. | |
^ | Bitwise XOR operator. If the bit is set in any operand, the bitwise xor operator copies the bit. | |
&^ | Bit clear. | |
<< | Left shift operator. Shifts the left operand’s value to the left by the number of bits specified by the right operand. | |
>> | Right shift operator. Shifts the left operand’s value to the right by the number of bits specified by the right operand. |
List of Relational Operators in Go
Relational Operators | == | Checks if the two operands have equal values, if they do, it returns true. |
!= | Checks if the two operands have unequal values, if they do, it returns true. | |
> | Checks if the left operand is greater than the right operand, if it is, it returns true. | |
< | Checks if the left operand is less than the right operand, if it is, it returns true. | |
<= | Checks if the left operand is less than or equal to the right operand, if it is, it returns true. | |
>= | Checks if the left operand is greater than or equal to the right operand, if it is, it returns true. |
List of Logical Operators in Go
Logical Operators | && | Logical AND operator. Returns false is both operands are false and true if both the operands are true. |
|| | Logical OR operator. Returns true if any of the operands are true. | |
! | Logical NOT operator. Reverses the logical state of its operand, i.e., changes true to false and vice versa. |
List of Assignment Operators in Go
Assignment Operator | = | Simple assignment operator. The left operand is assigned the value of the right operand. For example, Z=X+Y will assign Z the calculated value of X+Y. |
+= | Add and assignment operator. The value stored in the right operand is added to the value of the left operand and the result is assigned to the left operand. For example, X += Y is equivalent to X = X + Y. | |
-= | Subtract and assignment operator. The value stored in the right operand is subtracted from the value of the left operand and the result is assigned to the left operand. For example, X -= Y is equivalent to X = X – Y. | |
*= | Multiply and assignment operator. The value stored in the right operand is multiplied by the value of the left operand and the result is assigned to the left operand. For example, X *= Y is equivalent to X = X * Y. | |
/= | Divide and assignment operator. The value stored in the right operand is divided by the value of the left operand and the result is assigned to the left operand. For example, X /= Y is equivalent to X = X / Y. | |
%= | Modulus and assignment operator. The value stored in the right operand is divided by the value of the left operand and the remainder of the division is assigned to the left operand. For example, X %= Y is equivalent to X = X % Y. | |
<<= | Left shift and assignment operator. The value of the left operand is shifted to the left by the number of times specified by the right operand. For example, X <<= 1 is equivalent to X = X << 1. | |
>>= | Right shift and assignment operator. The value of the left operand is shifted to the right by the number of times specified by the right operand. For example, X >>= 1 is equivalent to X = X >> 1. | |
&= | Bitwise AND assignment operator. For example, Y &= 3 is equivalent to Y=Y&3. | |
^= | Bitwise exclusive OR and assignment operator. For example, Y ^= 3 is equivalent to Y = Y ^ 3 | |
|= | Bitwise inclusive OR and assignment operator. For example, Y |= 3 is equivalent to Y = Y | 2. |
Note that Go doesn’t support implicit conversion of numeric types. This means that, for example, you cannot take an integer value and add it to a floating-point value without converting it.
Illustration of Go Not Supporting Implicit Conversion of Numeric Types
Output:
Explicit Conversion of Numeric Types in Go
The Math Package
Basic Program Illustrating the Use of the Math Package in Go
Dates and Times
Basic program Illustrating the Use of the Time Package in Go
Listing 2-11 illustrates the use of the time package for using date and time types. In the example, in Line#10, a variable named now is declared. It will be initialized using the built-in time.Now() function, which returns a timestamp containing the current date, time, and time zone. It is also possible in Go for users to create their own explicit date and time values. Line#14 declares a variable named formatDate, which is initialized using the time.Date() function. The Date() function returns the time in the appropriate zone according to a given location corresponding to the format yyyy-mm-dd hh:mm:ss + nsec nanoseconds. You can also use a formatted version of the daytime value, as shown in Line#18. The Format() function returns a textual representation of the time value formatted as per the layout defined by the argument. This example uses the ANSIC format, but you can refer to the official documentation at https://pkg.go.dev/time#Time.Format for other standard formats.
Using the Parse Function from the Time Package
Since the Parse() function can return an error object, in this example, we added an underscore to ignore the object. The time object has many other useful functions. For example, you can add dates and perform special kinds of formatting, and so on. There are also patterns available that give you complete flexibility in how to format daytime values. The functions and constants in the time package give you all the tools you need to store and manage the date and time values. For more on this topic, refer to the official documentation on the time package at https://pkg.go.dev/time.
Operators Precedence in Go
Operator precedence refers to the criteria that determine the grouping of terms in an expression. It also affects the result of the expression evaluation. Like other programming languages, in Go, there is a pre-defined order of precedence of the operators, where some have higher precedence over the others. For example, the division operator / has higher precedence over the subtraction operator -.
Consider the statement a = 56 + 4 * 8 Here, variable a is assigned the value 88, not 480. This is because the multiply operator * has higher precedence over the addition operator +. Therefore, 4 * 8 is multiplied first and then the obtained result is added to 56.
List of Operator Precedence in Go
Category | Operator | Associativity |
---|---|---|
Postfix | ( ) [ ] -> . ++ - - | Left to right |
Unary | + - ! ~ ++ - - (type)* & sizeof | Right to left |
Multiplicative | * / % | Left to right |
Additive | + - | Left to right |
Shift | << >> | Left to right |
Relational | < <= > >= | Left to right |
Equality | == != | Left to right |
Bitwise AND | & | Left to right |
Bitwise XOR | ^ | Left to right |
Bitwise OR | | | Left to right |
Logical AND | && | Left to right |
Logical OR | || | Left to right |
Assignment | = += -= *= /= %=>>= <<= &= ^= |= | Right to left |
Comma | , | Left to right |
Memory Management and Reference Values
The Go runtime is required to run Go applications using the go run command on your system. The runtime is also included in the compiled and build binary Go application. Either way, like in any marked language, such as Java and C#, Go applications rely on the runtime that operates silently in the background utilizing dedicated threads for memory management. The advantage of the runtime is the elimination of the need to explicitly duplicate or allocate memory in the code.
New vs Make
It is critical to ensure that complex types, such as maps, are appropriately initialized. To initialize the complex objects in the Go programming language, there are two built-in functions—make and new. Be careful using them, as there is a difference between the two.
The new function only allocates memory for the complex object but does not initialize the memory. When an object, say a map, is allocated using the new function, it only returns the memory address of where that map is located. However, there is zero memory storage associated with the map object. Therefore, when a key-value pair is added to the map, it will throw an error.
Contrary to this, the make function allocates as well as initializes the memory for the map object created using the make function. It returns the memory address, like the new function, and the storage is initialized to non-zero. This means the map object can accept values without throwing errors.
Incorrect Memory Allocation Example
Illustration of Incorrect Memory Allocation of Map Objects
Correct Memory Allocation Example
Illustration of Correct Memory Allocation of Map Object
Memory Deallocation
Memory deallocation is performed in an autonomous fashion by the garbage collector (GC) that comes packaged with the Go runtime. When the garbage collector is triggered while working in the background, it searches for objects that are either out of scope or set to nil, so it may clear away the memory and return it to your memory pool.
In Go version 1.5, the garbage collector was completely reconstructed to have very low latency for effectively reducing the number of pauses that occur while running Go applications. In the latest version, 1.18.3, the performance of the garbage collector while performing memory allocation is much faster and is also almost unnoticeable even on slower computers. For more information on garbage collectors, refer to the official documentation on runtime at https://pkg.go.dev/runtime and the talk release of the garbage collector improvement at https://talks.golang.org/2015/go-gc.pdf.
Pointers Data Type
Every variable is essentially a memory location. Note also that each memory location has a defined address as well. In Go, the ampersand (&) operator indicates a memory address and is used to access the address of a variable.
What Is a Pointer?
Pointers are in essence variables that have the capability of storing memory addresses of other variables. Note that a memory address refers to the direct address of the variable. A pointer can be declared with any data type, however, initializing it, i.e., storing the address of another variable, is not necessary. Similar to high-level languages, Go also supports pointers. Pointers are useful in Go to perform different tasks, for example, without the use of pointers you cannot perform a call by reference.
Declaring Pointers in Go
Different Ways of Declaring Pointers in Go
It should be noted that, irrespective of its type, the inherent data type of value stored in all pointers is the same, i.e., a long hexadecimal number. This represents memory addresses, as all addresses are in the long hexadecimal format. The data type of the variable or constant that a pointer points to is the only differentiation between pointers of different data types. Furthermore, the value of the pointer can also be set to nil manually in the case of strings and automatically if the pointer is not initialized at the time of declaration.
Example
Declaring Pointers
Program Showing Correct Initialization of Pointers
In the main function, on Line#1, we declare an integer variable with 42 as its value. On Line#2, the ampersand (&) operator is used to assign the direct memory address of the variable value1 to the variable pointer1. Since you are using an ampersand (&) operator, the Go compiler will automatically recognize that pointer1 is a pointer variable used for storing memory addresses. Now, if you print the value stored at the memory pointed by the pointer pointer1, it will be the same as that stored in value1, i.e., 42. In order to print the value stored where the memory address points, the asterisk (*) operator is used, as shown in Lines #3 and #8 of the main function. Similarly, you can use pointers to point to floating-point variables as well. Note that pointer1 is declared and assigned value implicitly, whereas the pointer2 is declared explicitly with a data type.
On Line#1, you access the value stored at the memory location pointed by pointer1 using the asterisk (*) operator, dividing that value by 31 and storing the answer in the same location. As seen in the output, the pointer1 and value1 variables have the same value.
Comparison with Java and C-Style Languages
As in Java or C#, if you have an original variable and a reference variable (pointer) that points to that variable, you can change that value of the original variable either by changing the original variable or by changing the variable that’s pointing to it. However, unlike in Java, the pointer doesn’t have to point at any particular value initially, and you can change it at runtime to point to another value. On the other hand, you’ll find pointers in Go to be very similar and just as valuable in C, C#, and other similar languages.
Ordered Values in Arrays and Slices
This section discusses the arrays data structure and the slice, which is an abstraction of arrays.
Arrays in Go
Declaring Arrays
The general format for declaration of an array in Go is var array_name [SIZE] data_type. Note that you must mention the data_type, as it indicates the type of elements the array will be holding. Also, the total count of elements (SIZE) is also required, as it indicates the size of the array. Arrays declared in this format are known as single-dimensional arrays. Note that Size should be an integer constant value that is greater than zero. Additionally, any valid Go type can be used to specify the data_type, i.e., the type of elements that the array can hold. For example, the statement var floatArray [10] float32 declares an array of ten elements called floatArray of data_type as float32.
Initializing Arrays
Initializing Arrays with Size Specified in Go
Initializing Arrays Without Specifying Size in Go
Accessing Array Elements
Example
Basic Program Illustrating the Use of Arrays in Go
Querying the Size of an Array
Using the len Function with Array in Go
Remember that, in Go, an array is an object, and like all objects, if you pass it to a function, a copy will be made of that array. However, storing the data is just about all you can do with the arrays and it’s not easy to sort or add/remove items at runtime. For such features and other operations, you should package the ordered data in slices instead of arrays.
Slices in Go
In Go, a slice is a layer of abstraction placed on top of the array data structure. The runtime allocates the necessary memory and constructs the array in the background when you define a slice, but only returns the slice. Furthermore, similar to arrays, all elements of a slice are of the same type. Even though arrays can simultaneously hold multiple data items of the same kind, there is no way to dynamically increase its size or extract a sub-array from it using any built-in functions. Slices get around these drawbacks. Slices are commonly utilized in Go and they provide various utility methods that are required with arrays.
Defining Slices
Defining Slices in Go
The len( ) and cap( ) Functions
Example Illustrating the Use of the len() and cap() Functions
Nil Slice
Nil Slices in Go
Sub-Slicing in Go
Sub-Slicing in Go
The append( ) and copy( ) Functions
Using the Append and Copy Functions in Go
Using the Append Function to Remove Items from a Slice
In Listing 2-27, the first item is removed because that range is telling it to start with the item in array at index one, which is the second item.
However, in the colors = append(colors[:len(colors)-1]) statement, if the starting index in the range is eliminated and instead you pass colors[:len(colors)-1], the last item will be removed from the slice.
Sorting Slices
Using Sort Function to Sort Slices
Maps
A map, in Go language, can be considered an unordered collection of key-value pairs. In other terms, a map can be considered a hash table that allows storage of collections of data from which values can then be retrieved arbitrarily based on their keys.
Note that, as the keys are compared for sorting purposes, a map’s keys should be of any type that’s comparable. However, it’s a usual practice of using string type for keys and any other valid type for the associated values.
Defining Maps
Adding Entries to a Map Object
To add an entry into the map object, use this general format: map_variable_name[key] = value. Note that because the map is an unordered collection of items, the order in which the items will be displayed is not always guaranteed.
Deleting Entries from a Map Object
In Go, to delete a map’s entry (i.e., a key-value pair stored in the map), you use a built-in function called delete(). The delete() function takes two arguments—the map whose entry is to be deleted and the key to delete. The general format to achieve this is delete(map_variable, key).
Iterating Over Stored Values in a Map Object
Using the For Loop to Iterate Over a Map
Displaying Items of a Map
The first line declares a slice named keys of string type with a size equal to that of the map object named states. Then a for loop is used to iterate over the keys stored in the map object and store them into the keys slice. After that, to sort the keys slice, you use the sort() function that is available in the sort package. In this example, the initial key-value pairs stored in the states map were [CA:California NY:New York WA:Washington]
Using a For Range to Iterate Over a Map
Example
Example Illustrating the Use of Map in Go
Structs Data Type
As discussed earlier, arrays in Go are essentially variables that can store multiple data items of the same kind. To overcome this limitation, Go offers structures, which are user-defined types that can be used to store data items of different kinds. A structure essentially represents a record of data. For example, if there is a need to track different information about books available in a library, structures are the most useful way to store different attributes of a book, such as BookID, Title, Subject, Author, and so on.
Defining a Structure
General Format for Declaring Structures in Go
After defining a structure type in your code, multiple variables of this struct type can be declared. The general format for doing so is variable_name := structure_variable_type {value1, value2...value n}
Accessing Members of a Structure
Using Structs in Go
Passing Structures as Function Arguments
Passing Structures as Function Arguments in Go
Pointers to Structures
Just as you can define pointers that can be used for pointing (storing) to other constants or variables, you can also define pointers to structures in Go. For example, a pointer variable named struct_pointer of the type Books can be declared using the var struct_pointer *Books statement. The struct_pointer variable can then be used to store addresses of structure variables. The ampersand (&) operator gets the direct address of a structure variable, for example, struct_pointer = &Book1. Furthermore, the member access operator (.) accesses the members of a structure where the pointer is pointing, such as struct_pointer.title
Example
Program to Illustrate Structure Pointers in Go
The struct type in Go is a data structure that has similar purpose and function as the struct type in C and as classes in Java. Structs can encapsulate data and methods. However, unlike Java, Go does not have an inheritance model, that is, there is no concept of a super() function or substructures. Each structure is independent with its own fields for data management and optionally its own methods. Remember that struct in Go is a custom type. Note that the structure name starts with a capital letter, e.g., Book, to ensure that it can be used in other parts of the applications, i.e., exported, similar to public access specifiers in C-style languages. On the other hand, if a lowercase initial character is used, the struct type will be private and cannot be exported for use in other parts of the application.
Program Flow
Conditional logic can be used to change the program flow. Like other C-style languages, Go also offers conditional statements like if, if…else, nested if, switch, and select. However, there is a slight syntax difference in their uses.
If Statement
Illustration of an if-else Statement in Go
An if Statement with Initialization
Switch Statement
Using a switch Statement in Go
Using a Switch Statement with Evaluation Statement
In Go, the fallthrough keyword is used in the case block of the switch statement. If the fallthrough keyword is present in any case block, it will transfer the program’s control flow to the next case, whether or not the current case evaluated to true.
For Statement
- 1.
A for loop with step variable, condition, and range.
The range keyword is used in Go to iterate over elements stored in a variety of data structures. When used with arrays and slices, the range keyword also returns the index and value for each entry. In Listing 2-41, in the second for loop, we used the blank identifier ( _ ) to ignore the index. However, as in the third for loop, the indexes are sometimes required as well. In the fourth for loop, the range will return to the variable i from a comma-delimited list. The first variable will be the index and the second will be the associated value.
package mainimport "fmt"func main() {total := 0for k := 0; k < 5; k++ {total += k}nums := []int{2, 3, 4}total = 0for _, num := range nums {total += num}fmt.Println("Total:", total)for j, num := range nums {if num == 2 {fmt.Println("Index:", j)}}for i := range nums {fmt.Println(nums[i])}}Listing 2-41For Loop with Step Variable, Condition, and Range
Output:Total: 9Index: 0234 - 2.
A for loop with blank pre/post statements
Listing 2-42 illustrates the use of for loops with blank pre/post statements.
total := 1for ; total < 10; {total += total}Listing 2-42For Loop with Blank Pre/Post Statements
- 3.
A for loop as a while loop
Many languages have a while keyword that lets you loop over a set of statement(s) as long as a Boolean expression is true. Go implements this type of looping using the for keyword. Instead of a counter variable or a range, you just declare a Boolean condition. Listing 2-43 illustrates this concept.
total := 1for < 10 {total += total}Listing 2-43Using a For Loop as a While Loop
- 4.
Infinite loop
In general, if the specified condition statement of a loop never turns false, the loop turns into an infinite loop. Usually, for loops are used for creating infinite loops. When all of the three expressions in the for statement are not specified, this makes the condition statement remain true forever, turning the for loop into an infinite loop. Listing 2-44 illustrates this concept.
for {// Any task you want to loop forever}Listing 2-44Using a For loop as an Inifinite Loop
The goto Statement
Using the goto Statement in Go
To summarize, Go supports the use of continue, break, and goto statements. Go also supports looping with a variety of coding patterns and adds features to the for statement to make it concise and readable.
Functions
Go is organized into packages and packages have functions. Every Go application has a package named main and at least one function, also named main. The main function is called automatically by the runtime as the application starts up. In Go, like other programming languages, you can also create custom functions and organize them into your own custom packages.
A function declaration tells the Go compiler what the function name is, its return type, and the input parameters. On the other hand, the function definition is the actual body of the function and is a set of the statement(s) that perform the intended task.
Several built-in functions are provided by the Go standard library that you can call and use in your Go programs. For example, the len() function takes arguments of various types as input and returns their length. Functions are also called methods, subroutines, or procedures.
Defining a Function
General Format of Functions in Go
Here, the func keyword instructs the compiler about the start of a function declaration and function_name represents the function’s name. Function signature is referred to as the function’s name and input parameter list combined. Also, parameters are used as placeholders. Values passed to the function when it is invoked are called function arguments and these parameters are also referred to as formal parameters. A function’s parameter list defines the type, order, and total count parameters it will take as input. It is possible that a function may have no parameters and hence the parameters are optional. return_type is used to define the data type of value(s) the function may return. Here again, the return_type is optional if the function does not return anything. The body of the function contains multiple statements that together define the task it has to perform.
Basic Program Illustrating Custom Functions in Go
Functions with Arbitrary Number of Values
Doing Function Calls in Go
In order to create a function in Go, it is necessary to provide its definition as well, which defines what task the function should perform. In order to use a function, it has to be called from the program. When a function call is made from a program, the control flow is transferred to it. When the called function finishes the defined task or a return statement is encountered or the closing brace of the function is reached, the control flow returns to the program where the initial call was made.
Basic Program to Illustrate Function Calls in Go
Return More than One Value from Functions
Go Program Illustrating How to Return More than One Value from Functions
Passing Arguments to Functions
You must declare all of the variables that would accept the values passed to the function as its arguments. This set of variables are also known as a function’s formal parameters. Note that the scope of all of the formal parameters of a function is local to it only, i.e., they are accessible only within the scope of that particular function. Furthermore, the formal parameters are only created upon entering the function and are destroyed when it exits.
There are two ways to pass arguments to a function—call by value and call by reference. When the call by value way of passing arguments is used, it basically copies the value of the passed argument into the formal parameters of the called function. Any changes made to the formal parameters inside the respective function have no effect on the value of the argument that was passed with function call.
On the other hand, the call by reference technique copies the direct address of the passed argument into the formal parameters of the called function. As the direct address is copied and used to access the actual argument, any changes made to the formal parameters inside the respective function will also be reflected in the argument passed with the function call. To ensure that a function cannot change the value of the passed arguments, by default, call by value is used to pass arguments to functions in the Go programming language.
Call by Value Example
Go Program Showing Call by Value
Call by Reference Example
Go Program Illustrating Call by Reference in Go
Methods
Example Illustrating the Use of Methods in Go
Methods with Returning Values
Note that when you pass the dog object to a function as the receiver, a copy is made of it, i.e. this is pass by value and not reference. If you want it to be a reference, you can use pointers. But within the function, this is a brand new copy of the object. So when you modify the sound field, you are not modifying the version that was created in the main function. The ability to create custom methods for your own types makes the Go programming language behave more like a fully object-oriented language. Even without the sort of type inheritance that you find in C++ and Java, a struct can have as many different methods as it needs to accomplish your application’s requirements.
Write/Read Text Files
There are a number of ways to create a file in Go. There are high-level abstractions of the low-level operations that are required.
Write Text Files
The example in Listing 2-55 shows you how to write to a file in Go. The checkError() will be used for error checking throughout this program. The function will check the object passed to it and if the object is not nil, i.e., an error has occurred performing some operation in the main program, the function will crash the application and display the error message.
In the main() function, the content variable holds the string value you want to write to the fromString.txt file. On Line#2, in the main() function, we create a file reference named file, which is basically a variable that will be used as a reference to the file. Along with this, we also specify a variable called err to store error messages returned by the os.Create() function. The Create() function takes the location of the file to which the data should be written. Furthermore, it creates the named file if it doesn’t exist or truncates it if it already exists. Here, the ./ means the file is in the same directory as the application.
Program Illustrating How to Write to Text Files in Go
Read Text Files
Reading from Text Files in Go
HTTP Package
To help programmers build applications that can effectively communicate with different web applications and services, the Go programming language offers an extensive set of different tools, one of them being the http package. The http package allows you to easily communicate with remote hosts by creating requests and sending data. It also helps in creating HTTP server applications that can listen and respond to the received requests. The example in Listing 2-57 illustrates the use of the http package.
In Listing 2-57, a request is sent to a remote host to fetch some data. Here, we want to download the JSON contents of the page specified in the content named url to the local development machine. To do so, the first step is to import the net/http and ioutil packages. To download the content use the get() function from the http package. The get() function returns a response object as well as an error object. The resp variable is used to store the returned response object. When you print the contents of the resp variable, you will get a pointer to an object named response. Notice that it’s a member of the http package. The response object has a public field called body. It contains the JSON packet you intend to download. Just like with files, after reading the contents of the body, you should use the defer keyword along with the resp.Body.Close() command to close the body.
Basic Program Showing the Use of the HTTP Package in Go
JSON
The data returned as a response to a request made to a web service is commonly encoded in the JSON format. To facilitate working with web services and APIs, Go offers support for handling JSON-formatted data in the form of the json package. The json package allows you to easily create and read text that is in the JSON format. In the previous section, you learned how to get JSON data from a web service. In this section, you learn how to parse the JSON data and format it into structured data for use in your Go applications. Listing 2-58 is a demonstration of how to parse JSON data and format it into structured data. It parses the tour name and price from the received JSON data.
In Listing 2-58, you first create a new custom type named Tour, which will be a struct with two member fields of string type—Name and Price. Note that to make these field names public, they start with an uppercase character. Also note that in the JSON content the labels are all in lowercase. However, the JSON decoder is not affected by this, as it can easily match these labels to the members of the struct. For this example, we imported the encoding/json (https://pkg.go.dev/encoding/json), io/ioutil (https://pkg.go.dev/io/ioutil), net/http (https://pkg.go.dev/net/http) and strings (https://pkg.go.dev/strings) packages.
The toursFromJson() function is a custom function used to decode the body content in the JSON format of the HTTP response. It takes one argument, which is the JSON-formatted string fetched from the website. The function will return these values as structured data, specifically a slice containing instances of that tour object.
Within the toursFromJson() function, the first step is to create a slice tour of tour objects. The tour slice is initially set to size zero and an initial capacity of 20. Note that 20 is just a guess and not the exact number of objects that might be fetched. As slices can resize dynamically, you don’t have to give a large initial capacity. The NewDecoder() built-in function returns a decoder that will read from the reader object passed to it. The NewReader() returns a reader that reads from the string passed to it. The Token() function returns the next JSON token in the input stream.
Basic Program Illustrating How to Handle JSON Data in Go
Summary
This chapter covered the basic programming fundamentals you need to get started programming in Go, ranging from how to install the Go compiler and different IDEs available, the program structure of Go, to programming concepts. You also gained insight into how to use different data structures and programming features like variables, taking user input, available math operators and packages, managing memory and referencing values, and using pointers. You learned how to manage ordered values using arrays and slices, how to use maps to store key-value pairs, and how to define user-defined types using structs.
Managing program flow is a key point in programming, and this chapter also provided guidance into how to control the flow using different conditional statements, like if, if..else, switch, and goto. You also learned about the only type of loop available in Go that can be used in whichever way possible to achieve desired results.
Functions are an important part of modular programming. This chapter also covered how to use user-defined functions and attach methods to slices in Go. Another important capability of any program is the ability to read and write to files. This chapter also provided details into doing this. It also covered how to use HTTP package and handle JSON data. This is important to help programmers build applications that can effectively communicate with different web applications and services.
In the upcoming chapters, you’ll go through Go recipes based on real-life scenarios in order to get a better understanding of the concepts covered in this chapter.