Uploaded by Deepak Sharma

way to go

advertisement
Origin and Evolution of Go
This lesson provides a brief introduction to the Go programming language by discussing its history and how it
outshines other languages.
WE'LL COVER THE FOLLOWING
• Introduction
• History
• Programming language hall of fame
Introduction #
Also known as Golang, Go is a programming language
designed by Robert Griesemer, Rob Pike, and Ken Thompson.
It is an open-source programming language that makes it
easy to build simple, reliable, and efficient software
solutions. Go is a statically typed and compiled
programming language. Statically typed means that variable
types are explicitly declared and thus are determined at
compile time. Whereas, by compiled language we mean a
language that translates source code to machine code before
execution.
Note: Throughout this course, we will use the word Go and Golang
interchangeably.
History #
Go’s year of genesis was 2007 at Google, and it was publicly launched in 2009
with a fully open-source BSD-style license released for the Linux and Mac OS
Reasons for Developing Go
This lesson covers some reasons why there was a need for Go when other languages were doing their job.
WE'LL COVER THE FOLLOWING
• Languages that in uenced Go
• Why a new language?
• Evolving with computing landscape
• Need for faster software development
• Need for ef ciency and ease of programming
• Targets of Go
• Support for network communication, concurrency, and parallelization
• Support for excellent building speed
• Support for memory management
Languages that in uenced Go #
Go belongs to the C-family, like C++, Java, and C#, and is inspired by many
other languages created and used by its designers. The resemblance with the
syntax of C language was maintained to gain an advantage of familiarity
among developers. However, compared to C/C++, the syntax is made more
incisive (concise). Additionally, Go has the features of a dynamic language, so
Python and Ruby programmers feel more comfortable with it. The following
figure shows some of the influences on the Go programming language:
Java
Java, C#, etc.
Inheritance via "Interface"
"Package" Definitions
C language
Javascript, Ruby
and other dynamic
languages
Basic syntax,
simple structure
Google's "Go" Programming
Language
Adoption of
"Communicating
Sequential Process"
Limbo
Polymorphism independent
of inheritance
Developers Ken Thompson,
Rob Pike, etc., join effort
Application
Google Server
software
development
Spinoff
Chrome plug-in
development
Plan 9
UNIX
Influence on Go
Why a new language? #
Following are the reasons that led to the development of Go:
Evolving with computing landscape
Need for faster software development
Need for efficiency and ease of programming
Let’s discuss each need one by one.
Evolving with computing landscape #
Programming languages like C/C++ did not evolve with the computing
landscape, so there is a need for a new systems language, appropriate for the
needs of our computing era.
Need for faster software development #
In contrast to computing power, software development has not become
considerably faster or more successful (considering the number of failed
projects), whereas applications still grow in size. Therefore, a new low-level
language was needed, equipped with higher concepts.
Need for ef ciency and ease of programming #
Before Go, a developer had to choose between fast execution but slow and
inefficient building (like C++) or efficient compilation but not so fast execution
(like .NET or Java), or ease of programming but slower execution (like
dynamic languages such as Python, Ruby or JavaScript). Go is an attempt to
combine all the three wishes: efficient and fast compilation, fast execution,
and ease of programming.
Targets of Go #
The main target of Golang’s design was to combine
the efficacy, speed, and safety of a statically typed and
compiled language with the ease of programming of a
dynamic language to make programming more fun
again.
Some other targets that Go was meant to meet were:
Support for network communication,
concurrency, and parallelization
Support for excellent building speed
Support for memory management
Support for network communication, concurrency, and
parallelization #
To get the most out of distributed and multi-core machines excellent support
for networked-communication, concurrency, and parallelization. Golang was
expected to achieve this target for internal use in Google, and this target is
achieved through the concepts of goroutines. Don’t worry if you do not have
an idea about goroutines. We’ll cover them in Chapter 12 of this course. It is
a
dea about go out es.
e
co e t e
C apte
o t s cou se. t s
undoubtedly a great stronghold of Go as compared to other programming
languages keeping in mind the importance of multicore and multiprocessor
computers and the minimal support for them in existing programming
languages.
Support for excellent building speed #
There was a growing concern to improve the building speed (compilation and
linking to produce machine code) of C++ projects, which are heavily used in
Google’s infrastructure. This concern gave birth to the idea of developing the
Go programming language. In particular, dependency management is a very
important part of software development today. The “header files” of
languages caused considerable overhead leading in order to build times of
hours for the biggest projects. Developers felt the need for clean dependency
analysis and fast compilation, which Go language provides with its package
model.
The entire Go standard library compiles in less than 20 seconds. Typical
projects compile in half a second. This lightning-fast compiling process is even
faster than C or Fortran, making compilation a non-issue. Until now, this was
regarded as one of the great benefits of using dynamic languages for
development because the long compile/link step of C++ could be skipped.
However, with Go, this is no longer an issue! Compilation times are negligible,
and we have the same productivity as in the development cycle of a scripting
or dynamic language. In addition to this, the execution speed of the native
code is comparable to C/C++.
Support for memory management #
Because memory problems (memory leaks) are a long-time problem of C++,
Go’s designers decided that memory management should not be the
responsibility of a developer. So although Go executes native code, it runs in a
small runtime, which takes care of an efficient and fast garbage collection. Go
also has a built-in runtime reflection capability.
That’s it about the reasons and targets of Go. Now let’s study Go’s
characteristics.
Characteristics of Go
This lesson discusses the aspects that make Go a successful language in the programming world.
WE'LL COVER THE FOLLOWING
• Features of Go
• Uses of Go
• Used as a system programming language
• Used as a general programming language
• Used as an internal support
• Beware of the trap
• Guiding design principles
Features of Go #
Go is essentially an imperative (procedural, structural)
language, built with concurrency in mind. It is not truly
object-oriented like Java and C++ because it doesn’t have
the concepts of classes and inheritance. However, it does
have a concepts of interfaces, with which much of the
polymorphism can be implemented. Go has a clear and
expressive type-system, but it is lightweight and without
hierarchy. So in this respect, it could be called a hybrid
language.
Some features of modern OOP languages were intentionally left out. Because,
object orientation was too heavy often leading to cumbersome development
constructing big type-hierarchies, and so not compliant with the speed goal of
the language. As per the decision made by the Go-team, the following OOP
features are missing from Golang. Although, some of them might still be
implemented in its future versions.
To simplify the design, no function or operator overloading was added.
Implicit conversions were excluded to avoid the many bugs and confusion
arising from this in languages like C/C++.
No classes and type inheritance is supported in Golang.
Golang does not support variant types. However, almost the same
functionality is realized through interfaces.
Dynamic code loading and dynamic libraries are excluded.
Generics are not included (but this is a possible feature for Go 2.0).
Exceptions are not included (although recover/panic often goes in that
direction).
Assertions are not included.
Immutable (unable to change) variables are excluded.
A discussion around these features by the Go-team itself can be found in the
Go FAQ.
Golang is a functional language, meaning that functions are the basic
building blocks, and their use is very versatile. There is more information on
functions in Golang in Chapter 4.
Go is statically typed, making it a safe language that compiles to native code
and has a very efficient execution. It is also strongly typed, which means
according to the principle keep things explicit. Implicit type conversions (also
called castings or coercions) are not allowed. An important thing to note is
that Go does have some features of dynamically typed languages (using var
keyword). That’s why Go also appeals to programmers who left Java and the
.Net world for Python, Ruby, PHP, and JavaScript.
Last but not least, Go has support for cross-compilation, for example,
developing and compiling on a Linux-machine for an application that will
execute on Windows. It is one of the first programming languages that can use
UTF-8 not only in strings but also in program code. Go is truly an
international language because Go source-files are also in UTF-8.
Uses of Go #
There are many uses of Go. Following are some main uses of this language:
Used as a system programming language
Used as a general programming language
Used as an internal support
Used as a system programming language #
Go was originally conceived as a systems programming language for the
heavy server-centric (Google) world of web servers, storage architecture, and
the like. For certain domains like high performance distributed systems, Go
has already proven to be a more productive language than most others.
Golang shines in and makes massive concurrency and event-processing easy.
So it should be a good fit for the game server and IoT (Internet of Things)
development.
Used as a general programming language #
Go is also a general programming language, useful for solving text-processing
problems, making frontends, or even scripting-like applications. However, Go
is not suited for real-time software because of the garbage collection and
automatic memory allocation.
Used as an internal support #
Go is being used for some time internally in Google for heavy-duty distributed
applications, e.g., parts of Google Maps run on Go.
Before moving any further, if you are interested in exploring some interesting
real-life usage of Golang in other organizations around the world, click here.
The web also brings you a lot of Go success stories.
Beware of the trap #
If you come to Go and have a background in other
contemporary (mostly class or inheritance-oriented
languages like Java, C#, Objective C, Python, or Ruby), then
beware! You can fall in the trap of trying to program in Go as
you did in your previous language. However, Go is built on a
different model. So if you decide to move code from
language X to Golang, you will most likely produce nonidiomatic code that works poorly overall.
You have to start over by thinking in Go. If you take a higher
point of view and start analyzing the problem from within
the Go mindset, often a different approach suggests itself,
which leads to an elegant and idiomatic Go solution.
Guiding design principles #
Go tries to reduce typing, clutter, and complexity in coding through a minimal
amount of keywords (25). This, together with the clean, regular, and concise
syntax, enhances the compilation speed because the keywords can be parsed
without a symbol table as its grammar is LALR(1).
These aspects reduce the number of code lines necessary, even when
compared with a language like Java. Additionally, Go has a minimalist
approach: there tends to be only one way of getting things done, so reading
other people’s code is generally pretty easy, and we all know the code’s
readability is of the utmost importance in software engineering.
The design concepts of the language are orthogonal because they don’t stand
in each other’s way, and they don’t add up complexity to one another.
Golang is completely defined by an explicit specification that can be found
here; it is not defined by an implementation as is Ruby, for example. An
explicit language specification was required for implementing the two
different compilers gc and gccgo, and this in itself was a great help in
clarifying the specification.
We have completed building the context of the Go language. A quiz awaits you
in the next lesson.
Test Yourself
In this lesson, we’ll practice what we have learned so far in this chapter.
Choose only one correct answer.
1
Which of the following are features of Golang?
COMPLETED 0%
1 of 3
We hope that you performed well in the quiz. Next, we’ll get ready to cover
the basic elements of Golang.
Filenames, Keywords and Identifiers
This lesson describes what a basic Go program usually comprises of.
WE'LL COVER THE FOLLOWING
• Filename
• Keyword
• Identi ers
• Blank identi er
• Anonymous
• The basic structure of a Go program
Filename #
Go source code is stored in .go files. Their filenames consist of lowercaseletters, like educative.go. If the name consists of multiple parts, they are
separated by underscores ‘_’, like educative_platform.go. Filenames cannot
contain spaces or any other special characters. A source file contains code
lines whose length has no intrinsic limits.
Keyword #
A reserved word, with a special meaning in a programming language, is called
a keyword. Below is the set of 25 keywords, or reserved words, used in Gocode:
Keywords
Identi ers #
An identifier is a name assigned by the user to a program element like a
variable, a function, a template, and a class, etc. Nearly all things in Go-code
have a name or an identifier. Like all other languages in the C-family, Go is
case-sensitive. Valid identifiers begin with a letter (a letter is every letter in
Unicode UTF-8) or _ and are followed by 0 or more letters or Unicode digits,
like X56, group1, _x23, i, and Σ©Τ‘12.
The following are NOT valid identifiers:
1ab because it starts with a digit
case because it is a keyword in Go
a+b because operators are not allowed
Apart from the keywords, Go has a set of 36 predeclared identifiers which
contain the names of elementary types and some basic built-in functions (see
Chapter 4); all these will be explained further in the next chapters:
Predeclared Identifiers
Blank identi er #
The _ itself is a special identifier, called the blank identifier. Like any other
identifier, - can be used in declarations or variable assignments (and any
type can be assigned to it). However, its value is discarded, so it can no longer
be used in the code that follows. We will demonstrate its use later in the
course.
Anonymous #
Sometimes it is possible that even functions have no name because it is not
really necessary at that point in the code and not having a name even
enhances flexibility. Such functions are called anonymous. We will
demonstrate their use later in the course.
The basic structure of a Go program #
Programs consist of keywords, constants, variables, operators, types and
functions. It is also important to know the delimiter and punctuation
characters that are a part of Golang.
The following delimiters are used in a Go program:
Parentheses ()
Braces {}
Brackets []
The following punctuation characters are used in a Go program:
.
,
;
:
...
The code is structured in statements. A statement doesn’t need to end with a
; (like it is imposed on the C-family of languages). The Go compiler
automatically inserts semicolons at the end of statements. However, if
multiple statements are written on one line (a practice which is not
encouraged for readability reasons), they must be separated by ; .
That’s it about the basic structure and elements of Golang. In the next lesson,
we’ll see a basic component of the Go program.
Import Functionality
This lesson introduces a basic component of Go program, i.e., packages.
WE'LL COVER THE FOLLOWING
• Packages
• Package dependencies
• Import keyword
• Visibility
• Visibility rule
Packages #
A library, module, or namespace in any other language is called a package.
Packages are a way to structure code. A program is constructed as a package
which may use facilities from other packages. A package is often abbreviated
as ‘pkg’.
Every Go file belongs to only one package whereas one package can comprise
many different Go files. Hence, the filename(s) and the package name are
generally not the same. The package to which the code-file belongs must be
indicated on the first line. A package name is written in lowercase letters. For
example, if your code-file belongs to a package called main, do the following:
package main
A standalone executable belongs to main. Each Go application contains one
main.
An application can consist of different packages. But even if you use only
package main, you don’t have to stuff all code in 1 big file. You can make a
number of smaller files, each having package main as the 1st line of code. If
you compile a source file with a package name other than main, e.g., pack1,
the object file is stored in pack1.a.
Package dependencies #
To build a program, the packages, and the files within them must be compiled
in the correct order. Package dependencies determine the order in which to
build the packages. Within a package, the source files must all be compiled
together. The package is compiled as a unit, and by convention, each directory
contains one package. If a package is changed and recompiled, all the client
programs that use this package must be recompiled too!
Import keyword #
A Go program is created by linking set of packages together, with the import
keyword. For example, if you want to import a package say fmt, then you do:
package main
import "fmt"
import "fmt" tells Go that this program needs functions, or other elements
from the package fmt , which implements a functionality for formatted IO.
The package names are enclosed within " "(double quotes).
Import loads the public declarations from the compiled package; it does not
insert the source code. If multiple packages are needed, they can each be
imported by a separate statement. For example, if you want to import two
packages, fmt and os in one code file, there are some following ways to do so:
import "fmt"
import "os"
or you can do:
import "fmt"; import "os"
Go has provided us with a shorter and more elegant way of importing
multiple packages known as factoring the keyword. It is stated as:
import (
"fmt"
"os"
)
Factoring means calling a keyword once on multiple instances. You may have
noticed that we imported two packages using a single import keyword. It is
also applicable to keywords like const , var , and type .
Visibility #
Packages contain all other code objects apart from the blank identifier ( _ ).
Also, identifiers of code-objects in a package have to be unique which means
that there can be no naming conflicts. However, the same identifier can be
used in different packages. The package name qualifies a package to be
different.
Visibility rule #
Packages expose their code objects to code outside of the package according to
the following rule enforced by the compiler:
When the identifier (of a constant, variable, type, function, struct field, …)
starts with an uppercase letter, like, Group1, then the ‘object’ with this
identifier is visible in code outside the package (thus available to clientprograms, or ‘importers’ of the package), and it is said to be exported (like
public identifiers/variables in OO languages). Identifiers that start with a
lowercase letter are not visible outside the package, but they are visible and
usable in the whole package (like private identifiers/variables).
Note: Capital letters can come from the entire Unicode-range, like Greek;
not only ASCII letters are allowed.
Importing a package gives access only to the exported objects in that package.
Suppose we have an instance of a variable or a function called Object (starts
with O so it is exported) in a package pack1 . When pack1 is imported in the
current package, Object can be called with the usual dot-notation from OOlanguages:
pack1.Object
Packages also serve as namespaces and can help us avoid name conflicts. For
example, variables with the same name in two packages are differentiated by
their package name, like pack1.Object and pack2.Object .
A package can also be given another name called an alias. If you name a
package then its alias will be used throughout the code, rather than its
original name. For example:
import fm "fmt"
Now in the code, whenever you want to use fmt , use its alias (not fmt ).
Note: Go has a motto known as “No unnecessary code!”. So importing a
package which is not used in the rest of the code is a build-error.
That’s how functionalities are imported in the Golang. In the next lesson,
you’ll learn how to write a function in Go.
Overview of Functions
This lesson explains how to write a simple function in Go.
WE'LL COVER THE FOLLOWING
• Functions
• Hello World 🌍
• Comments
• Naming things in Go
Functions #
The simplest function declaration has the format:
func functionName()
Between the mandatory parentheses ( ) no, one, or more parameters
(separated by ,) can be given as input to the function. After the name of each
parameter variable must come its type.
The main function as a starting point is required (usually the first function),
otherwise the build-error: undefined: main.main occurs. The main function
has no parameters and no return type (in contrary to the C-family) otherwise,
you get the build-error: func main must have no arguments and no return
values . When the program executes, after initializations the first function
called (the entry-point of the application) will be the main.main() (like in C).
The program exits immediately and successfully when main.main returns.
The code or body in functions is enclosed between braces { }. The first { must
be on the same line as the declaration otherwise you get the error: syntax
error: unexpected semicolon or newline before { ) . The last } is positioned
after the function-code in the column beneath the function. The syntax is as
follows:
func func_Name(param1 type1, param2 type2, ...){
...
}
If the function is returning an object of type type1 , we follow the syntax as:
func func_Name(param1 type1, param2 type2, ...) type1 {
...
}
or:
func func_Name(param1 type1, param2 type2, ...) ret1 type1 {
...
}
where ret1 is a variable of type type1 to be returned. So a general function
returning multiple variables looks like:
func func_Name(param1 type1, param2 type2, ...) (ret1 type1, ret2 ret2,
...) {
...
}
Smaller functions can be written on one line like:
func Sum(a, b int) int { return a + b }
Let’s create the main function now as an entry point.
package main
import "fmt"
func main(){
}
The main structure of a Go program is ready. How about making it capable of
doing something?
Hello World 🌍 #
package main // making package for standalone executable
import "fmt" // importing a package
func main() { // making an entry point
// printing using fmt functionality
fmt.Println("Hello World")
} // exiting the program
Hello World
BINGO! We just made our first Go program using the components above. At
line 1, we make a package main and then import package fmt to get the
functionality of formatted IO at line 2. At line 4, we make a main function to
add an entry point, and at line 6, we print Hello World with
fmt.Println("Hello World") . This function Println from the fmt package
takes a parameter and prints it on the screen.
Comments #
Explanation of source code added to a program as a text note is called a
comment. Comments are un-compilable. They are just for the understanding
of the user. In Go, a one-line comment starts with // . A multi-line or blockcomment starts with /* and ends with */ , where nesting is not allowed. You
can see we had added comments (the sentences in green) to the above
program.
Run the following program and see the magic.
package main
import "fmt" // Package implementing formatted I/O.
func main() {
fmt.Printf("ΚαλημΞ­ρα κόσμε; or こんにけは δΈ–η•Œ\n")
}
Go an International Language
This illustrates the international character of Go by printing ΚαλημΞ­ρα
κόσμε; or こんにけは δΈ–η•Œ.
Naming things in Go #
Clean, readable code and simplicity are major goals of Go development.
Therefore, the names of things in Go should be short, concise, and evocative.
Long names with mixed caps and underscores which are often seen e.g., in
Java or Python code, sometimes hinder readability. Names should not contain
an indication of the package. A method or function which returns an object is
named as a noun, no Get… is needed. To change an object, use SetName. If
necessary, Go uses MixedCaps or mixedCaps rather than underscores to write
multiword names.
You have finally run your first Go program. Now let’s get an overview of
elementary types.
Overview of Data Types
This lesson tells us about the type of data that Go can handle.
WE'LL COVER THE FOLLOWING
• Types
• Conversions
Types #
Variables contain data, and data can be of different data types or types for
short. Go is a statically typed language. It means the compiler must know the
types of all the variables, either because they were explicitly indicated, or
because the compiler can infer the type from the code context. A type defines
the set of values and the set of operations that can take place on those values.
Here is an overview of some categories of types:
Types
Examples
elementary (or primitive)
int , float , bool , string
structured (or composite)
struct , array , slice , map , channel
interfaces
They describe the behavior of a
type.
Data Types in Go
A structured type, which has no real value (yet), has the value nil , which is
also the default value for these types. To declare a variable, var keyword is
used as:
var var1 type1
var1 is the variable name, and type1 is the type of var1 .
Functions can also be of a certain type. The type of function is the type of
variable which is returned by it. This type is written after the function name
and its optional parameter-list, like:
func FunctionName (a typea, b typeb) typeFunc
So, you can see that typeFunc is the (return) type of the function,
FunctionName . The returned variable var of type typeFunc appears
somewhere in the function in the statement as:
return var
A function can have more than one return variables. In this case, the returntypes are separated by comma(s) and surrounded by ( ), like:
func FunctionName (a typea, b typeb) (t1 type1, t2 type2)
Two variables are returned with type type1 and type2 respectively. For such
a case, the return statement takes the form:
return var1, var2
We can also have user-defined data types (our own data types), which we’ll
study in detail in Chapter 8. But, it is possible to have an alias for data types
similar to what we have for packages. For example, to create an alias for an
integer type you can do:
type IZ int
Now to declare an integer variable, we have to use an alias like:
var a IZ = 5
If you have more than one type to define, you can use the factored keyword
form, as in:
type (
IZ int
FZ float32
STR string
)
In the above code, we create IZ , FZ , STR as an alias for int , float32 , and
string respectively.
Conversions #
Sometimes a value needs to be converted into a value of another type called
type-casting. Go does not allow implicit conversion, which means Go never
does such a conversion by itself. The conversion must be done explicitly as
valueOfTypeB = typeB(valueOfTypeA). How about writing a program on
type-casting and observing the result? Let’s get started.
package main
import "fmt"
func main(){
var number float32 = 5.2
fmt.Println(number)
fmt.Println(int(number))
}
// Declared a floating point variable
// Printing the value of variable
// Printing the type-castes result
Type Casting
At line 5 we declare a floating-point variable called number , and print the
value in the next line. Then at line 7 we print the type-casted value by
converting the value to int type using int(number) . You can see that the value
5.2 becomes 5 after type-casting.
Now that you are familiar with data types. Let’s begin storing some data, in
the next lesson.
Constants
This lesson discusses how constants are used to store data values in Go.
WE'LL COVER THE FOLLOWING
• Introduction
• Explicit and implicit typing
• Typed and untyped constants
• Compilation
• Over ow
• Multiple assignments
• Enumerations
Introduction #
A value that cannot be changed by the program is called a constant. This data
can only be of type boolean, number (integer, float, or complex) or string.
Explicit and implicit typing #
In Go, a constant can be defined using the keyword const as:
const identifier [type] = value
Here, identifier is the name, and type is the type of constant. Following is
an example of a declaration:
const PI = 3.14159
You may have noticed that we didn’t specify the type of constant PI here. It’s
perfectly fine because the type specifier [type] is optional because the
compiler can implicitly derive the type from the value. Let’s look at another
example of implicit typing:
const B = "hello"
The compiler knows that the constant B is a string by looking at its value.
However, you can also write the above declaration with explicit typing as:
const B string = "hello"
Remark: There is a convention to name constant identifiers with all
uppercase letters, e.g., const INCHTOCM = 2.54. This improves
readability.
Typed and untyped constants #
Constants declared through explicit typing are called typed constants, and
constants declared through implicit typing are called untyped constants. A
value derived from an untyped constant becomes typed when it is used within
a context that requires a typed value. For example:
var n int
f(n + 5)
// untyped numeric constant "5" becomes typed as int, becaus
e n was int.
Compilation #
Constants must be evaluated at compile-time. A const can be defined as a
calculation, but all the values necessary for the calculation must be available
at compile time. See the case below:
const C1 = 2/3 //okay
Here, the value of c1 was available at compile time. But the following will
give an error:
const C2 = getNumber() //not okay
Because the function getNumber() can’t provide the value at compile-time. A
constant’s value should be known at compile time according to the design
principles where the function’s value is computed at run time. So, it will give
the build error: getNumber() used as value .
Over ow #
Numeric constants have no size or sign. They can be of arbitrarily high
precision and do not overflow:
const Ln2= 0.693147180559945309417232121458\
176568075500134360255254120680009
const Log2E= 1/Ln2 // this is a precise reciprocal
const BILLION = 1e9 // float constant
const HARD_EIGHT = (1 << 100) >> 97
We used \ (backslash) in declaring constant Ln2 . It can be used as a
continuation character in a constant.
Multiple assignments #
The assignments made in one single assignment statement are called multiple
assignments. Go allows different ways of multiple assignments. Let’s start
with a simple example:
const BEEF, TWO, C = "meat", 2, "veg"
As you can see, we made 3 constants. All of them are untyped constants. Let’s
look at another method where all the constants are named first, and then
their values are written if needed. For example:
const MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY int= 1, 2, 3,
4, 5, 6
As you can see, the constants, MONDAY , TUESDAY , WEDNESDAY , THURSDAY , FRIDAY
and SATURDAY are typed constants because their type (int) is mentioned
explicitly, and they have the values 1, 2, 3, 4, 5 and 6 respectively.
Enumerations #
Listing of all elements of a set is called enumeration. Constants can be used for
enumerations. For example:
const (
UNKNOWN = 0
FEMALE = 1
MALE = 2
)
UNKNOWN , FEMALE and MALE are now aliases for 0, 1 and 2. Interestingly value
iota can be used to enumerate the values. Let’s enumerate the above example
with iota:
const (
UNKNOWN = iota
FEMALE = iota
MALE = iota
)
The first use of iota gives 0. Whenever iota is used again on a new line, its
value is incremented by 1; so UNKNOWN gets 0, FEMALE gets 1 and MALE gets 2.
Remember that a new const block or declaration initializes iota back to 0. The
above notation can be shortened, making no difference as:
const (
UNKNOWN = iota
FEMALE
MALE
)
You can give enumeration a type name. For example, FEMALE , MALE and
UNKNOWN are categories of Gender. Let’s give them Gender as the type name:
type Gender int
const (
UNKNOWN = iota
FEMALE
MALE
)
Now you are familiar with constants. In the next lesson, you’ll study variables.
Variables
This lesson discusses how variables are used in Go.
WE'LL COVER THE FOLLOWING
• Introduction
• Assigning values
• Short form with := assignment operator
Introduction #
A value that can be changed by a program during execution is called a
variable. The general form for declaring a variable uses the keyword var as:
var identifier type
Here, identifier is the name of the variable, and type is the type of the
variable. As discussed earlier in this chapter, type is written after the
identifier of the variable, contrary to most older programming languages.
When a variable is declared, memory in Go is initialized, which means it
contains the default zero or null value depending upon its type automatically.
For example, 0 for int, 0.0 for float, false for bool, empty string ("") for string,
nil for pointer, zero-ed struct, and so on.
Run the following program to see how declaring a variable works.
package main
import "fmt"
func main(){
var number int
fmt.Println(number)
var decision bool
fmt.Println(decision)
}
//
//
//
//
Declaring an integer variable
Printing its value
Declaring a boolean variable
Printing its value
Declaring a Variable
You can see that in the above code, we declare a variable number of type int
at line 5. As memory is initialized, the default value for number is printed at
line 6, which is 0. Similarly, a variable decision of type bool at line 7 is
declared, and false is printed as its value at line 8.
Remark: The naming of identifiers for variables follows the camelCasing
rules (start with a small letter, and every new part of the word starts
with a capital letter). But if the variable has to be exported, it must start
with a capital letter, as discussed earlier in this chapter.
Assigning values #
Giving a value to a variable is called assigning a value. A variable is assigned a
value using the assignment operator( = ) at compile time. But of course, a
value can also be computed or changed during runtime. Declaration and
assignment (initialization) can be combined in the general format:
var identifier type = value
Here, value can be a value of type type , or can even be a variable of type
type or can also be an expression.
Run the following program to see how the assignment operator works on
variables.
package main
import "fmt"
func main(){
var number int = 5
fmt.Println(number)
var decision bool = true
fmt.Println(decision)
}
// Declaring and initializing an integer variable
// Printing its value
// Declaring a initializing a boolean variable
// Printing its value
Declaring & Initializing a Variable
You can see that in the above code, we declared a variable number of type int
at line 5, and initialized it with the value of 5. Similarly, a variable decision of
type bool at line 7 was declared and initialized with true. These initialized
values are printed later on by line 6 and line 8 respectively.
Go-compiler is intelligent enough to derive the type of a variable from its
value dynamically, also called automatic type inference at runtime, so omitting
the type of a variable is also a correct syntax. Let’s see a program on
automatic type inference.
package main
import "fmt"
func main(){
var number = 5
fmt.Println(number)
var decision = true
fmt.Println(decision)
}
//
//
//
//
Declaring and initializing an integer variable without stating
Printing its value
Declaring and initializing a boolean variable without stating
Printing its value
Automatic Type Inference
You can see that we just declared variable number at line 5 and decision at
line 8, without stating their types explicitly. The compiler infers type by itself,
and the result is the same as the previous program. 5 and true are printed.
Short form with := assignment operator #
With the type omitted, the keyword var is pretty superfluous in line 5 and
line 7 of the above program. So we can also write it as:
number := 5 // line 5
decision := true // line 7
Again the types of number and decision (int and bool) are inferred by the
compiler. This is the preferred form, but it can only be used inside functions,
not in package scope. This operator (:=) effectively makes a new variable; it is
also called an initializing declaration.
If after the lines above in the same code block, we declare :
number:= 20
This is not allowed. The compiler gives the error: no new variables on the
left side of :=" . However, number = 20 is okay because then the same
variable only gets a new value.
Note: If a variable named v is used but not declared, it will give a
compiler error: undefined: v . And, if v was declared as a local variable
but not used, then the compiler will give the error: v declared and not
used .
That’s all for the introduction to variables. Let’s see how the scope for a
variable is defined and how referencing works in Go.
Scope of Variables
In this lesson, you'll study the different data types in detail.
WE'LL COVER THE FOLLOWING
• Boolean type
• Numerical type
• Integers and oating-point numbers
• Format speci ers
• Complex numbers
• Random numbers
• Character type
• Unicode package
The three main elementary types in
svg viewer
Go are:
Boolean
Numeric
Character
Let’s discuss them in detail one by
one.
Boolean type #
The possible values of this type are the predefined constants true and false.
For example:
var b bool = true
Numerical type #
Integers and oating-point numbers #
Go has architecture-dependent types such as int, uint, and uintptr. They have
the appropriate length for the machine on which a program runs.
An int is a default signed type, which means it takes a size of 32 bit (4 bytes) on
a 32-bit machine and 64 bit (8 bytes) on a 64-bit machine, and the same goes
for unit (unsigned int). Whereas uintptr is an unsigned integer large enough to
store a bit pattern of any pointer.
The architecture independent types have a fixed size (in bits) indicated by
their names. For integers:
int8 (-128 to 127)
int16 (-32768 to 32767)
int32 (− 2,147,483,648 to 2,147,483,647)
int64 (− 9,223,372,036,854,775,808 to 9,223,372,036,854,775,807)
For unsigned integers:
uint8 (with the alias byte, 0 to 255)
uint16 (0 to 65,535)
uint32 (0 to 4,294,967,295)
uint64 (0 to 18,446,744,073,709,551,615)
For floats:
float32 (± 1O −45 to ± 3.4 ∗ 1O 38 )
float64 (± 5 ∗ 1O −324 to 1.7 ∗ 1O 308 )
Note: Unlike other languages, a float type on its own does not exist in
Golang. We have to specify the bits. For example float32 or float64 .
int is the integer type, which offers the fastest processing speeds. The initial
(default) value for integers is 0, and for floats, this is 0.0. A float32 is reliably
accurate to about 7 decimal places, and a float64 to about 15 decimal places.
Use float64 whenever possible, because all the functions of the math package
expect that type.
Numbers may be denoted in:
Octal notation with a prefix of 0: 63 can be written as 077.
Hexadecimal notation with a prefix of 0x: 255 can be written as 0xFF.
Scientific notation with e, which represents the power of 10: 1000 can be
written as 1e3 or 6.022 ∗ 1023 can be written as 6.022e23 .
As Go is strongly typed, the mixing of types is not allowed, as in the following
program. But constants are considered to have no type in this respect, so with
constants, mixing is allowed. Let’s do some type-mixing.
package main
func main() {
var a int
var b int32
a = 15
b = a + a // compiler error
b = b + 5 // ok: 5 is a constant
}
Type Mixing
The program will give a compiler error: cannot use a + a (type int) as type
int32 in assignment . Line 8 will work fine because 5 is a constant not a
variable.
Similarly, if we declare two variables as:
var n int16 = 34
var m int32
And we do:
m = n
It will give a compiler error: cannot use n (type int16) as type int32 in
assignment . Because an int16 cannot be assigned to an int32, there is no
implicit casting. In the following program, an explicit conversion is done to
avoid this.
package main
import "fmt"
func main() {
var n int16 = 34
var m int32
// int16 variable
// int32 variable
m = int32(n)
// explicit typing
fmt.Printf("32 bit int is: %d\n", m)
fmt.Printf("16 bit int is: %d\n", n)
}
Casting
In the above code, n is an int16 variable and m is an int32 variable. To set
the value of m equals to n , we need explicit type casting because these
variables have different types. At line 8 doing m = int32(n) cast n with type
of int32 as m type is int32 not int16 . Then the results are printed at line 9
and line 10.
Format speci ers #
In format-strings %d is used as a format specifier for integers (%x or %X
can be used for a hexadecimal representation).
The %g is used for float types (%f gives a floating-point, %e gives a
scientific notation).
The %0nd shows an integer with n digits, a leading 0 is necessary.
The %n.mg represents the number with m digits after the decimal sign,
and n before it, instead of g also e and f can be used. For example, the
%5.2e formatting of the value 3.4 gives 3.40e+00.
Complex numbers #
A complex number is written in the form of:
re + im¡
where re is the real part, and im is the imaginary part, and ¡ is the √ -1. For
these data we have the following types:
complex64 (with a 32 bit real and imaginary part each)
complex128 (with a 64 bit real and imaginary part each)
Consider the following program:
package main
import "fmt"
func main(){
var c1 complex64 = 5 + 10i
// Declaring complex num (real +imaginary(¡))
fmt.Printf("The value is: %v", c1)
}
Complex Number
You can see that in the above code, we declare and initialize a complex
variable c1 with type complex 64 . In this number, 5 is the real part, and 10i is
an imaginary part. Then at line 6, its value is printed using a general format
specifier %v. In format-strings the default format specifier %v can be used for
complex numbers; otherwise, use %f for both constituent parts ( real and
imaginary separate).
If re and im are of type float32, a variable c of type complex64 can be made
with the function complex :
c = complex(re, im)
We can also get the parts of a complex number through built-in functions. The
functions real(c) and imag(c) give the real and imaginary part of c ,
respectively.
Random numbers #
Some programs, like games or statistical applications, need random numbers.
The package math/rand implements pseudo-random number generators. For
a simple example, see the following program that prints a random number:
package main
import (
fmt
"math/rand"
)
func main(){
a := rand.Int()
b := rand.Intn(8)
fmt.Printf("a is: %d\n", a)
fmt.Printf("b is: %d\n", b)
}
// generates a random number
// generates a random number in [0, n)
Random Number
In the above program, you can see we import a package math/rand at line 4 to
generate random numbers. We declare a variable a at line 8. In a a random
value is placed. But if we want to create a random number with a range we
can use Intn(n) function. Random values of range [0, n) (starting from 0 to n1) can be generated. So at line 9 we made a variable b and set it as a random
number from 0 to 7. Then at line 10 and line 11, a and b are printed
respectively.
Character type #
Strictly speaking, this is not a type in Go. The characters are a special case of
integers. The byte type is an alias for uint8, and this is okay for the traditional
ASCII-encoding for characters (1 byte). A byte type variable is declared as:
var ch byte = 'A'
Single quotes ‘’ surround a character. In the ASCII-table the decimal value for
A is 65, and the hexadecimal value is 41, so the following are also declarations
for the character A:
var ch byte = 65
or
var ch byte = '\x41'
\x is always followed by exactly two hexadecimal digits. Another possible
notation is a \ followed by exactly 3 octal digits, e.g., \377.
But there is also support for Unicode (UTF-8). Characters are also called
Unicode code points, and a Unicode character is represented by an int in
memory. In the documentation, they are commonly represented as U+hhhh,
where h is a hexadecimal digit. In fact, the type rune exists in Go and is an
alias for type int32. To write a Unicode-character in code, preface the
hexadecimal value with \u or \U. If 4 bytes are needed for the character, \U is
used. Where \u is always followed by exactly four hexadecimal digits and \U
by eight. Run the following program to see how Unicode character type works.
package main
import "fmt"
func main(){
var ch1 int = '\u0041'
var ch2 int = '\u03B2'
var ch3 int = '\U00101234'
fmt.Printf("%d - %d - %d\n", ch1, ch2, ch3)
fmt.Printf("%c - %c - %c\n", ch1, ch2, ch3)
fmt.Printf("%X - %X - %X\n", ch1, ch2, ch3)
fmt.Printf("%U - %U - %U", ch1, ch2, ch3)
}
//
//
//
//
integer
character
UTF-8 bytes
UTF-8 code point
Characters
At line 5 and line 6, the characters ch1 and ch2 that are declared are
represented by four bytes because we used \u . Where ch3 is represented
with eight bytes using \U . You may have noticed that we print these
characters using four different format specifiers: %d, %c, %X, %U from line 8
to line 11. In format-strings %c is used as a format specifier to show the
character, format-specifiers %v or %d show the integer representing the
character, and %U outputs the U+hhhh notation.
Unicode package #
The package unicode has some useful functions for testing characters. Suppose
if we have a character named ch. Following are some main functions of this
package:
Testing for a letter
unicode.IsLetter(ch)
Testing for a digit
unicode.IsDigit(ch)
Testing for a whiteSpace character
unicode.IsSpace(ch)
These functions return a bool value. The utf8 package further contains
functions to work with runes.
Now you are familiar with data types in detail; it’s time to study the different
operations that are possible on data.
Elementary Types
In this lesson, you'll study the different data types in detail.
WE'LL COVER THE FOLLOWING
• Boolean type
• Numerical type
• Integers and oating-point numbers
• Format speci ers
• Complex numbers
• Random numbers
• Character type
• The unicode package
The three main elementary types in
Go are:
Boolean
Numeric
Character
Let’s discuss them in detail one by
one.
Boolean type #
The possible values of this type are the predefined constants true and false.
For example:
var b bool = true
Numerical type #
Integers and oating-point numbers #
Go has architecture-dependent types such as int, uint, and uintptr. They have
the appropriate length for the machine on which a program runs.
An int is a default signed type, which means it takes a size of 32 bit (4 bytes) on
a 32-bit machine and 64 bit (8 bytes) on a 64-bit machine, and the same goes
for unit (unsigned int). Meanwhile, uintptr is an unsigned integer large
enough to store a bit pattern of any pointer.
The architecture independent types have a fixed size (in bits) indicated by
their names. For integers:
int8 (-128 to 127)
int16 (-32768 to 32767)
int32 (− 2,147,483,648 to 2,147,483,647)
int64 (− 9,223,372,036,854,775,808 to 9,223,372,036,854,775,807)
For unsigned integers:
uint8 (with the alias byte, 0 to 255)
uint16 (0 to 65,535)
uint32 (0 to 4,294,967,295)
uint64 (0 to 18,446,744,073,709,551,615)
For floats:
float32 (± 1O −45 to ± 3.4 ∗ 1O 38 )
float64 (± 5 ∗ 1O −324 to 1.7 ∗ 1O 308 )
Note: Unlike other languages, a float type on its own does not exist in
Golang. We have to specify the bits. For example, float32 or float64 .
int is the integer type, and it offers the fastest processing speeds. The initial
(default) value for integers is 0, and for floats, this is 0.0. A float32 is reliably
accurate to about 7 decimal places, and a float64 to about 15 decimal places.
Use float64 whenever possible, because all the functions of the math package
expect that type.
Numbers may be denoted in:
Octal notation with a prefix of 0: 63 can be written as 077.
Hexadecimal notation with a prefix of 0x: 255 can be written as 0xFF.
Scientific notation with e, which represents the power of 10: 1000 can be
written as 1e3 or 6.022 ∗ 1023 can be written as 6.022e23 .
As Go is strongly typed, the mixing of types is not allowed, as in the following
program. However, constants are considered to have no type in this respect.
Therefore, with constants, mixing is allowed. Let’s do some type-mixing.
package main
func main() {
var a int
var b int32
a = 15
b = a + a // compiler error
b = b + 5 // ok: 5 is a constant
}
Type Mixing
The program will give a compiler error: cannot use a + a (type int) as type
int32 in assignment . Line 8 will work fine because 5 is a constant, not a
variable.
Similarly, if we declare two variables as:
var n int16 = 34
var m int32
And we do:
m = n
It will give a compiler error: cannot use n (type int16) as type int32 in
assignment . Because an int16 cannot be assigned to an int32, there is no
implicit casting. In the following program, an explicit conversion is done to
avoid this.
package main
import "fmt"
func main() {
var n int16 = 34
var m int32
// int16 variable
// int32 variable
m = int32(n)
// explicit typing
fmt.Printf("32 bit int is: %d\n", m)
fmt.Printf("16 bit int is: %d\n", n)
}
Casting
In the above code, n is an int16 variable, and m is an int32 variable. To set
the value of m equals to n , we need explicit type casting because these
variables have different types. At line 8, doing m = int32(n) cast n with a
type of int32 as m type is int32 , not int16 . Then the results are printed at
line 9 and line 10.
Format speci ers #
In format-strings, %d is used as a format specifier for integers (%x or %X
can be used for a hexadecimal representation).
The %g is used for float types (%f gives a floating-point, and %e gives a
scientific notation).
The %0nd shows an integer with n digits, and a leading 0 is necessary.
The %n.mg represents the number with m digits after the decimal sign,
and n before it. Instead of g, e and f can also be used. For example, the
%5.2e formatting of the value 3.4 gives 3.40e+00.
Complex numbers #
A complex number is written in the form of:
re + im¡
where re is the real part, im is the imaginary part, and ¡ is the √ -1. For
these data we have the following types:
complex64 (with a 32 bit real and imaginary part each)
complex128 (with a 64 bit real and imaginary part each)
Consider the following program:
package main
import "fmt"
func main(){
var c1 complex64 = 5 + 10i
// Declaring complex num (real +imaginary(¡))
fmt.Printf("The value is: %v", c1)
}
Complex Number
You can see that in the above code, we declare and initialize a complex
variable c1 with type complex 64 . In this number, 5 is the real part, and 10i is
an imaginary part. Then at line 6, its value is printed using a general format
specifier %v. In format-strings, the default format specifier %v can be used
for complex numbers; otherwise, use %f for both constituent parts (real and
imaginary separate).
If re and im are of type float32, a variable c of type complex64 can be made
with the function complex :
c = complex(re, im)
We can also get the parts of a complex number through built-in functions. The
functions real(c) and imag(c) give the real and imaginary parts of c ,
respectively.
Random numbers #
Some programs, like games or statistical applications, need random numbers.
The package math/rand implements pseudo-random number generators. For
a simple example, see the following program that prints a random number:
package main
import (
fmt
"math/rand"
)
func main(){
a := rand.Int()
b := rand.Intn(8)
fmt.Printf("a is: %d\n", a)
fmt.Printf("b is: %d\n", b)
}
// generates a random number
// generates a random number in [0, n)
Random Number
In the above program, you can see we import a package math/rand at line 4 to
generate random numbers. We declare a variable a at line 8. In a a random
value is placed. But if we want to create a random number with a range, we
can use Intn(n) function. Random values of range: [0, n) (starting from 0 to
n-1) can be generated. So at line 9, we made a variable b and set it as a
random number from 0 to 7. Then at line 10 and line 11, a and b are printed
respectively.
Character type #
Strictly speaking, this is not a type in Go. The characters are a special case of
integers. The byte type is an alias for uint8, and this is okay for the traditional
ASCII-encoding for characters (1 byte). A byte type variable is declared as:
var ch byte = 'A'
Single quotes ‘’ surround a character. In the ASCII-table the decimal value for
A is 65, and the hexadecimal value is 41. The following are also declarations
for the character A:
var ch byte = 65
or
var ch byte = '\x41'
\x is always followed by exactly two hexadecimal digits. Another possible
notation is a \ followed by exactly 3 octal digits, e.g., \377.
But there is also support for Unicode (UTF-8). Characters are also called
Unicode code points, and a Unicode character is represented by an int in
memory. In the documentation, they are commonly represented as U+hhhh,
where h is a hexadecimal digit. In fact, the type rune exists in Go and is an
alias for type int32. To write a Unicode-character in code, preface the
hexadecimal value with \u or \U. If 4 bytes are needed for the character, \U is
used. Where \u is always followed by exactly four hexadecimal digits and \U
by eight. Run the following program to see how Unicode character type works.
package main
import "fmt"
func main(){
var ch1 int = '\u0041'
var ch2 int = '\u03B2'
var ch3 int = '\U00101234'
fmt.Printf("%d - %d - %d\n", ch1, ch2, ch3)
fmt.Printf("%c - %c - %c\n", ch1, ch2, ch3)
fmt.Printf("%X - %X - %X\n", ch1, ch2, ch3)
fmt.Printf("%U - %U - %U", ch1, ch2, ch3)
}
//
//
//
//
integer
character
UTF-8 bytes
UTF-8 code point
Characters
At line 5 and line 6, the declared characters ch1 and ch2 are represented by
four bytes because we used \u . Where ch3 is represented with eight bytes
using \U . You may have noticed that we print these characters using four
different format specifiers: %d, %c, %X, and %U from line 8 to line 11. In
format-strings, %c is used as a format specifier to show the character, formatspecifiers %v or %d show the integer representing the character, and %U
outputs the U+hhhh notation.
The unicode package #
The package unicode has some useful functions for testing characters. Suppose
we have a character named ch. Following are some main functions of this
package:
Testing for a letter
unicode.IsLetter(ch)
Testing for a digit
unicode.IsDigit(ch)
Testing for a whiteSpace character
unicode.IsSpace(ch)
These functions return a bool value. The utf8 package further contains
functions to work with runes.
Now that you are familiar with data types in detail, it’s time to study the
different operations possible on data.
Operators
This lesson discusses operators supported by Go.
WE'LL COVER THE FOLLOWING
• Introduction
• Arithmetic operators
• Logical operators
• Bitwise operators
• Operators and precedence
Introduction #
A symbol that is used to perform logical or mathematical tasks is called an
operator. Go provides the following built-in operators:
Arithmetic operators
Logical operators
Bitwise operators
Arithmetic operators #
The common binary operators +, -, * and / that exist for both integers and
floats in Golang are:
Addition operator +
Division operator /
Modulus operator %
Multiplication operator *
The + operator also exists for strings. The / operator for integers is (floored)
integeral division. For example, 9/4 will give you 2, and 9/10 will give you 0.
The modulus operator % is only defined for integers. For example, 9%4 gives 1
.
Integer division by 0 causes the program to crash, and a run-time panic occurs
(in many cases the compiler can detect this condition). Division by 0.0 with
floating-point numbers gives an infinite result: +Inf.
There are shortcuts for some operations. For example, the statement:
b = b + a
can be shortened as:
b += a
The same goes for -= , *= , /= and %= .
As unary operators for integers and floats we have:
Increment operator ++
Decrement operator -However, these operators can only be used after the number, which means:
i++ is short for i+=1 which is, in turn, short for i=i+1. Similarly, i– is short for i=1 which is short for i=i-1.
Moreover, ++ and -- may only be used as statements, not expressions; so n =
i++ is invalid, and subtler expressions like f(i++) or a[i]=b[i++], which are
accepted in C, C++ and Java, cannot be used in Go.
No error is generated when an overflow occurs during an operation because
high bits are simply discarded. Constants can be of help here. If we need
integers or rational numbers of unbounded size (only limited by the available
memory), we can use the math/big package from the standard library, which
provides the types big.Int and big.Rat.
Logical operators #
Following are logical operators present in Go:
Equality operator ==
Not-Equal operator !=
Less-than operator <
Greater-than operator >
Less-than equal-to operator <=
Greater-than equal-to operator >=
The following illustration describes how they work.
A
B
A==B
2
5
False
10
10
True
7
3
False
Returns true if both sides of operator have same values
Equality Operator
1 of 6
A
B
A!=B
2
5
True
10
10
False
7
3
True
Returns true if both sides of operator have different values
Not Equal Operator
2 of 6
A
B
A<B
2
5
True
10
10
False
7
3
False
Returns true if left side of operator is less than right side
Less Than Operator
3 of 6
A
B
A>B
2
5
False
10
10
False
7
3
True
Returns true if left side of operator is greater than right side
Greater Than Operator
4 of 6
A
B
A <= B
2
5
True
10
10
True
7
3
False
Returns true if left side of operator is less than or equal to right side
Less Than or Equal To Operator
5 of 6
A
B
A >= B
2
5
False
10
10
True
7
3
True
Returns true if left side of operator is greater than or equal to right side
Greater Than or Equal To Operator
6 of 6
Go is very strict about the values that can be compared. It demands that
values have to be of the same type. If one of them is a constant, it must be of a
type compatible with the other. If these conditions are not satisfied, one of the
values has first to be converted to the other’s type.
<, <=, >, >=, == and != not only work on number types but also on strings. They
are logical because the result value of these operators is of type bool. Run the
following program to see how these operators work:
package main
import "fmt"
func main(){
b3 := 10 > 5
fmt.Println(b3)
b3 = 10 < 5
fmt.Println(b3)
b3 = 5 <= 10
fmt.Println(b3)
b3 = 10 != 10
fmt.Println(b3)
}
// greater than operator
// less than operator
// less than equal to
// not equal to
Logical Operators
As you can see, we use >, <, <= and != operators and change the value of b3
one by one at line 5, line 7, line 9 and line 11 to evaluate the results. The
results are either true or false based on the comparison made.
Boolean constants and variables can also be combined with logical operators
to produce a boolean value. Such a logical statement is not a complete Gostatement on itself. Go has three boolean logical operators:
AND operator ( && )
OR operator ( || )
NOT operator ( ! )
The following illustration describes how they work.
A
B
A&&B
True
True
True
True
False
False
False
True
False
False
False
False
Returns true if both operands are true
AND Operator
1 of 3
A
B
A||B
True
True
True
True
False
True
False
True
True
False
False
False
Returns true if one of the operands or both operands are true
OR Operator
2 of 3
A
!A
True
False
False
True
Returns the opposite of the value
NOT Operator
3 of 3
AND and OR are binary operators where NOT is a unary operator. The && and
|| operators behave in a shortcut way that when the value of the left side is
known, and it is sufficient to deduce the value of the whole expression (false
with && and true with ||), then the right side is not computed anymore. For
that reason, if one of the expressions involves a long-lasting calculation, put
this expression on the right side.
Run the following program to see how these three operators work:
package main
import "fmt"
func main(){
b3 := 10 > 5 && 7 < 15
fmt.Println(b3)
b3 = 10 < 5 || 2 > 7
fmt.Println(b3)
b3 = !b3
fmt.Println(b3)
}
// AND operator
// OR operator
// NOT operator
Boolean Logical Operators
As you can see, we use AND, OR and NOT operators and change the value of
b3 one by one at line 5, line 7, line 9 to evaluate the results. The results were
either true or false based on the comparison made.
Bitwise operators #
They work only on integer variables having bit-patterns of equal length. %b is
the format-string for bit-representations. Following are some bitwise
operators:
Bitwise AND operator &
Bitwise OR operator |
Bitwise XOR operator ^
Bit CLEAR operator &^
Bitwise COMPLEMENT operator ^
These are explained in the illustration below:
A
B
A&B
0
0
0
0
1
0
1
0
0
1
1
1
Bits in the same position are and-ed together
Bitwise AND Operator
1 of 5
A
B
A|B
0
0
0
0
1
1
1
0
1
1
1
1
Bits in the same position are or-ed together
Bitwise OR Operator
2 of 5
A
B
A^B
0
0
0
0
1
1
1
0
1
1
1
0
Bits in the same position are xor-ed together
Bitwise XOR Operator
3 of 5
A
B
^B
A&^B
0
0
1
0
0
1
0
0
1
0
1
1
1
1
0
0
Use to clear a bit to 0, by definition: A&^B = A &(NOT B)
Bit Clear Operator
4 of 5
A
^A
0
1
1
0
When used as a unary operator, it applies complement on all bits
Bitwise Complement Operator
5 of 5
Bitwise AND, OR, XOR, and CLEAR are binary operators which means they
require two operands to work on. However, the complement operator is a
unary operator.
There are other two major bitwise operators used for shifting:
Left shift operator <<
Right shift operator >>
Assume a holds 10. Let’s see how left shifting by 2 works:
Shift every bit to the left
a=
n=1
0
0
0
0
1
0
1
0
0
0
0
1
0
1
0
0
Append 0 at empty place
Left Shift Operator
1 of 2
Shift every bit to the left
a=
n=2
0
0
0
1
0
1
0
0
0
0
1
0
1
0
0
0
Append 0 at empty place
Left Shift Operator
2 of 2
Assume a holds 10. Let’s see how right shifting by 2 works:
Shift every bit to the right
a=
n=1
0
0
0
0
1
0
1
0
0
0
0
0
0
1
0
1
Append 0 at empty place
Right Shift Operator
1 of 2
Shift every bit to the right
a=
n=2
0
0
0
0
0
1
0
1
0
0
0
0
0
0
1
0
Append 0 at empty place
Right Shift Operator
2 of 2
Left shift by n causes multiplication by 2n , and right shift by n causes
division by 2n . For example, if n is 2, the number is multiplied by 4 for left
shifting and divided by 4 for right shifting.
Operators and precedence #
Some operators have higher priorities (precedence) than others. Binary
operators of the same precedence associate from left to right. The following
table lists all the operators and their precedence (much shorter and clearer
than in C or Java), top to bottom is highest to lowest:
Precedence
Operator(s)
7
^ !
6
* / % << >> & &^
5
+ - | ^
4
== != < <= >= >
3
<-
2
&&
1
||
Using ( ) is of course allowed to clarify expressions, to indicate priority in
operations as expressions contained in ( ) are always evaluated first.
That’s it about operators. There is a challenge in the next lesson for you to
solve.
Challenge: Temperature Conversion
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Input
• Output
• Sample input
• Sample output
Problem statement #
Implement function toFahrenheit(t Celsius) to convert
from Celsius to Fahrenheit. Types of temperatures are
float32 which are aliased to Celsius and Fahrenheit for
you.
Temperature in ˚F = (Temp in °C * 9/5) + 32
Input #
A variable of type Celsius
Output #
A variable of type Fahrenheit
Sample input #
100
Sample output #
212
Try to implement the function below. Feel free to view the solution, after
giving it a few shots. Good Luck!
package main
import "fmt"
import "strconv"
import "encoding/json"
// aliasing type
type Celsius float32
type Fahrenheit float32
// Function to convert celsius to fahrenheit
func toFahrenheit(t Celsius) Fahrenheit {
var temp Fahrenheit
return temp
}
Temperature Conversion
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Temperature Conversion
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"fmt"
)
// aliasing type
type Celsius float32
type Fahrenheit float32
// Function to convert celsius to fahrenheit
func toFahrenheit(t Celsius) Fahrenheit {
return Fahrenheit((t*9/5 )+ 32)
}
func main() {
var tempCelsius Celsius = 100
tempFahr := toFahrenheit(tempCelsius)
// function call
fmt.Printf("%f ˚C is equal to %f ˚F",tempCelsius,tempFahr)
}
Celsius to Fahrenheit
We aliased the types of the temperature, i.e., float32 to Celsius and
Fahrenheit . At line 7, we alias the type float 32 by giving the name Celsius .
We need another type called Fahrenheit , so we alias the type float32 again at
line 8. Now, we have to write a function that does the conversion from Celsius
to Fahrenheit.
Look at the function header at line 11: func toFahrenheit(t Celsius)
Fahrenheit . toFahrenheit is the function identifier. Parameter t with type
Celsius is passed, and the return type of function is Fahrenheit . Inside the
function, a one-line implementation is given. The simple formula is applied,
and then the result is type-casted to Fahrenheit before returning as
Fahrenheit((t*9/5)+32) .
In the main function, we declare a variable tempCelsius of type Celsius at
line 16. Next at line 18, we call the function toFahrenheit , and send
tempCelsius as the parameter to it and store the result in tempFahr a variable
of Fahrenheit type. Finally, at line 19, we print the temperature to view the
result.
That’s it about the solution. In the next lesson, you’ll study strings, a nonelementary yet an important datatype of Go.
Strings
This lesson discusses the datatype string in detail.
WE'LL COVER THE FOLLOWING
• Introduction
• Strings in Go
• Advantages of strings
• Types of string literals
• Interpreted strings
• Raw strings
• Length of a string
• Concatenation of strings
Introduction #
Strings are a sequence of UTF-8 characters (the 1-byte ASCII-code is used
when possible, and a 2-4 byte UTF-8 code when necessary). UTF-8 is the most
widely used encoding. It is the standard encoding for text files, XML files, and
JSON strings. With string data type, you can reserve 4 bytes for characters,
but Go is intelligent enough that it will reserve one-byte if the string is only an
ASCII character.
Strings in Go #
Contrary to strings in other languages as C++, Java or Python that are fixedwidth (Java always uses 2 bytes), a Go string is a sequence of variable-width
characters (each 1 to 4 bytes).
Advantages of strings #
The advantages of strings are:
Go strings and text files occupy less memory/disk space (because of
variable-width characters).
Since UTF-8 is the standard, Go doesn’t need to encode and decode strings
like other languages have to do.
Strings are value types and immutable, which means that once created, you
cannot modify the contents of the string. In other words, strings are
immutable arrays of bytes.
Types of string literals #
Two kinds of string literals exist:
Interpreted strings
Raw strings
Interpreted strings #
They are surrounded by " " (double quotes). For example, escape sequences
are interpreted:
"\n" // represents a newline
"\r" // represents a carriage return
"\t" // represents a tab
"\u" // represents Unicode characters
"\U" // also represents Unicode characters
Raw strings #
They are surrounded by back quotes `` and are not interpreted. For example
in the following string:
`This is a raw string \n`
\n is not interpreted but taken literally. They can span multiple lines.
Length of a string #
Strings in Go do not terminate by a special character as in C/C++. The initial
(default) value of a string is the empty string “”. The usual comparison
operators (==, !=, <, <=, >=, and >) work on strings by comparing byte by byte in
memory.
The length of a string str (the number of bytes) is given by the len()
function:
len(str)
The contents of a string (the raw bytes) are accessible via standard indexing
methods, the index is written between [], with the index starting from 0. The
following illustration shows indexing of a string of length 5:
H
e
l
l
o
Length=5
Indexing the String
1 of 6
Index = 0
H
e
l
l
Length=5
o
Indexing the String
2 of 6
Index = 1
H
e
l
l
o
Length=5
Indexing the String
3 of 6
Index = 2
H
e
l
l
o
Length=5
Indexing the String
4 of 6
Index = 3
H
e
l
l
o
Length=5
Indexing the String
5 of 6
Index = 4
H
e
l
l
o
Length=5
Indexing the String
6 of 6
So you can see that:
The first byte of a string str is given by: str[0]
The i-th byte by: str[i]
The last byte by: str[len(str)-1]
Note: Taking the address of a character in a string is illegal.
Concatenation of strings #
Two strings s1 and s2 can be made into one string s with:
s := s1 + s2
s2 is appended to s1 to form a new string s . Multi-line strings can be
constructed as follows:
str := "Beginning of the string " +
"second part of the string"
The + has to be on the first line due to the insertion of ; by the compiler. The
append shorthand += can also be used for strings. Run the following program
to see how concatenation works:
package main
import "fmt"
func main(){
s := "Hel" + "lo "
s += "World!"
fmt.Println(s) // prints out “Hello World!”
}
Concatenation of Strings
As you can see, the two strings Hel and lo are concatenated at line 5, and
the result of this concatenation is further concatenated with the string World!
at line 6. The final string Hello World! is printed at line 7.
Now that you are familiar with the basics of strings in Go, you will write a
function to solve a problem with strings in the next lesson.
Strings and strconv Package
In this lesson, you'll study strings, strconv package, and the functions supported by them.
WE'LL COVER THE FOLLOWING
• Pre xes and suf xes
• Testing whether a string contains a substring
• Indicating the index a substring or character in a string
• Replacing substring
• Counting occurrences of a substring
• Repeating a string
• Changing the case of a string
• Trimming a string
• Splitting a string
• On whitespaces
• On a separator
• Joining over a slice
• Reading from a string
• Conversion to and from a string
Strings are a basic data structure, and every language has a number of
predefined functions for manipulating strings. In Go, these are gathered in a
package, strings . We’ll discuss below some very useful functions one by one.
Pre xes and suf xes #
HasPrefix tests whether the string s begins with a prefix prefix :
strings.HasPrefix(s, prefix string) bool
HasSuffix tests whether the string s ends with a suffix suffix :
strings.HasSuffix(s, suffix string) bool
The following program implements these functions:
package main
import (
"fmt"
"strings"
)
func main() {
var str string = "This is an example of a string"
fmt.Printf("T/F? \nDoes the string \"%s\" have prefix %s? ", str, "Th")
fmt.Printf("\n%t\n\n", strings.HasPrefix(str, "Th"))
// Finding prefix
fmt.Printf("Does the string \"%s\" have suffix %s? ", str, "ting")
fmt.Printf("\n%t\n\n", strings.HasSuffix(str, "ting")) // Finding suffix
}
Prefixes and Suffixes in a String
As you can see in the above code, we declare a string str and initialize it with
This is an example of a string at line 9. At line 11, we used the function
HasPrefix to find prefix Th in string str . The function returns true because
str does start with Th. Similarly, at line 14, we used the function HasSuffix
to find the suffix ting in string str . The function returns false because str
does not end with ting. This also illustrates the use of the escape character \ to
output a literal " with \" , and the use of 2 substitutions in a format-string.
Testing whether a string contains a substring #
The function Contains returns true if substr is within s :
strings.Contains(s, substr string) bool
Indicating the index a substring or character in a
string #
Index returns the index of the first instance of str in s , or -1 if str is not
present in s :
strings.Index(s, str string) int
LastIndex returns the index of the last instance of str in s , or -1 if str is not
present in s :
strings.LastIndex(s, str string) int
If ch is a non-ASCII character, use:
strings.IndexRune(s string, ch int) int.
The following program finds the index of the substrings in a string:
package main
import (
"fmt"
"strings"
)
func main() {
var str string = "Hi, I'm Marc, Hi."
fmt.Printf("The position of the first instance of\"Marc\" is: ")
fmt.Printf("%d\n", strings.Index(str, "Marc"))
// Finding first occurence
fmt.Printf("The position of the first instance of \"Hi\" is: ")
fmt.Printf("%d\n", strings.Index(str, "Hi"))
// Finding first occurence
fmt.Printf("The position of the last instance of \"Hi\" is: ")
fmt.Printf("%d\n", strings.LastIndex(str, "Hi"))
// Finding last occurence
fmt.Printf("The position of the first instance of\"Burger\" is: ")
fmt.Printf("%d\n", strings.Index(str, "Burger"))
// Finding first occurence
}
Position of Substring
As you can see in the above code, we declare a string str and initialize it with
Hi, I’m Marc, Hi. at line 8. At line 10, we find the index of the first occurrence
of Marc in str using the Index function that gives 8 as a result. Similarly, at
line 12, we find the index of the first occurrence of Hi in str using the Index
function, which is 0, and its last occurrence by using LastIndex at line 14,
which is 14. At line 16, we find the first occurrence of Burger in str which
gives -1 because it doesn’t exist.
Replacing substring #
We can replace an old string with a new string like:
strings.Replace(str, old, new string, n int)
We can replace the first n occurrences of old in str by new . A copy of str is
returned, and if n is -1, all occurrences are replaced.
Counting occurrences of a substring #
Count counts the number of non-overlapping instances of substring str in s
with:
strings.Count(s, str string) int
Run the below program as an example.
package main
import (
"fmt"
"strings"
)
func main() {
var str string = "Hello, how is it going, Hugo?"
var manyG = "gggggggggg"
fmt.Printf("Number of H's in %s is: ", str)
fmt.Printf("%d\n", strings.Count(str, "H"))
// count occurences
fmt.Printf("Number of double g's in %s is: ", manyG)
fmt.Printf("%d\n", strings.Count(manyG, "gg"))
// count occurences
}
Counting Occurrences of a Substring in a String
As you can see in the above code, we declare the two strings str and manyG
and initialize them with Hello, how is it going, Hugo? and gggggggggg at line
8 and line 9, respectively. At line 11, we find the count of H in str using the
Count function that gives 2 as a result. Similarly, at line 13, we find the count
of gg in manyG using the Count function, which is 5.
Repeating a string #
The Repeat function returns a new string consisting of count copies of the
string s :
strings.Repeat(s, count int) string
The following program is the implementation of Repeat function:
package main
import (
"fmt"
"strings"
)
func main() {
var origS string = "Hi there! "
var newS string
newS = strings.Repeat(origS, 3)
// Repeating origS 3 times
fmt.Printf("The new repeated string is: %s\n", newS)
}
Repeating a String
As you can see in the above code, we declare a string origS and initialize it
with Hi there! at line 8. At line 10, we repeat origS 3 times using the Repeat
function and store the new string in newS at line 10. Then, the result is
printed at line 11.
Changing the case of a string #
The ToLower function returns a copy of the string s with all Unicode letters
mapped to their lower case:
strings.ToLower(s) string
All uppercase is obtained with:
strings.ToUpper(s) string
Run the following program to see how these functions work.
package main
import (
"fmt"
"strings"
)
func main() {
var orig string = "Hey, how are you George?"
var lower string
var upper string
fmt.Printf("The original string is: %s\n", orig)
lower = strings.ToLower(orig)
// changing to lower case
fmt.Printf("The lowercase string is: %s\n", lower)
upper = strings.ToUpper(orig)
// changing to upper case
fmt.Printf("The uppercase string is: %s\n", upper)
}
Changing Case of Strings
As you can see in the above code, we declare a string orig and initialize it
with Hey, how are you George? , at line 8. At line 12, we change the case of
orig to lowercase, using the function ToLower , and at line 14, we change the
case of orig to uppercase, using the function ToUpper . We print the new
strings at line 13 and line 15 to verify the result.
Trimming a string #
TrimSpace can be used to remove all leading and trailing whitespaces as:
strings.TrimSpace(s)
If you want to trim a specific string str from a string s , use:
strings.Trim(s, str)
For example:
strings.Trim(s, "\r\n")
The above statement will remove all leading and trailing \r and \n from the
string s . The 2nd string-parameter can contain any characters, which are all
removed from the left and right-side of s .
If you want to remove only the leading or only trailing characters or strings,
use TrimLeft or TrimRight independently.
Splitting a string #
On whitespaces #
The strings.Fields(s) splits the string s around each instance of one or
more consecutive white space characters, and returns a slice of substrings
[]string of s or an empty list, if s contains only white space.
On a separator #
The strings.Split(s, sep) works the same as Fields , but splits around sep .
The sep can be a separator character ( : , ; , , , - ,…) or any separator string
sep .
Joining over a slice #
The strings.Join(sl []string, sep string) results in a string containing all
the elements of the slice sl , separated by sep :
Reading from a string #
The strings package also has a function called strings.NewReader(str) . This
produces a pointer to a Reader value, that provides amongst others the
following functions to operate on str:
Read() to read a []byte
ReadByte() to read the next byte from the string.
ReadRune() to read the next rune from the string.
Conversion to and from a string #
Package strconv contains a few variables to calculate the size in bits of the int
of the platform on which the program runs:
strconv.IntSize
Converting a variable of a certain type to a string will always succeed. For
converting from numbers, we have the following functions:
strconv.Itoa(i int) string
It returns the decimal string representation of i . Next, we have:
strconv.FormatFloat(f float64, fmt byte, prec int, bitSize int) string
It converts the 64-bit floating-point number f to a string, according to the
format fmt (can be ‘b’,‘e’, ‘f’ or ‘g’), precision prec , with bitSize being 32 for
float32 or 64 for float64.
For converting to numbers, we have the following functions:
strconv.Atoi(s string) (i int, err error)
It converts to an int. Second, we have:
strconv.ParseFloat(s string, bitSize int) (f float64, err error)
It converts to a 64-bit floating-point number
As can be seen from the return-type these functions will return 2 values: the
converted value (if possible) and the possible error. So, when calling such a
function, the multiple assignment form will be used:
val, err = strconv.Atoi(s)
Converting a string to another type will not always be possible (as in the case
of the functions Atoi and ParseFloat above). Therefore, a runtime error is
thrown: parsing "...": invalid argument .
Run the following programs to see how conversions work:
package main
import (
"fmt"
"strconv"
)
func main() {
var orig string = "666"
var an int
var newS string
fmt.Printf("The size of ints is: %d\n", strconv.IntSize)
an, _ = strconv.Atoi(orig) // converting to number
fmt.Printf("The integer is: %d\n", an)
an = an + 5
newS = strconv.Itoa(an)
// converting to string
fmt.Printf("The new string is: %s\n", newS)
}
String Conversion
As you can see in the above code, we declare a string orig and initialize it
with 666 , at line 8. At line 11, we calculate the size of ints using
strconv.IntSize that appears to be 64. At line 12 we convert orig to an
integer using the function strconv.Atoi(orig) and store it in a new variable
an . We modify an and convert it into a string at line 15 using
strconv.Itoa(an) and then store it in a new variable newS . an and newS are
printed at line 13 and line 16 to verify the results.
Documentation for other functions in this package can be found here.
That’s it about the strings and strconv package. In the next lesson, you’ll
study time package provided by Golang.
Times and Dates
In this lesson, you'll study the time package and the functions supported by it.
WE'LL COVER THE FOLLOWING
• The time Package
The time Package #
The package time gives us a datatype Time (to be used as a
value) and functionality for displaying and measuring time
and dates. The current time is given by time.Now() , and the
parts of a time can then be obtained as t.Day() , t.Minute() ,
and so on. You can make your own time-formats as in:
t := time.Now()
fmt.Printf("%02d.%02d.%4d\n", t.Day(), t.Month(), t.Year
()) // e.g.: 29.10.2019
The type Duration represents the elapsed time between two instants as an
int64 nanosecond count. A handy function is Since(t Time) that returns the
time elapsed since t . The type Location maps time instants to the zone in use
at that time. UTC represents Universal Coordinated Time. There is a
predefined function Format with syntax as:
func (t Time) Format(s string) string
It formats a time t into a string according to an s string, with some
predefined formats like time.ANSIC or time.RFC822 . The general layout
defines the format by showing the representation of a standard time, which is
then used to describe the time to be formatted; this seems strange, but an
example makes this clear:
t := time.Now().UTC()
fmt.Println(t.Format("02 Jan 2006 15:04"))// e.g: 29 Oct 2019 11:00
Run the following program to see how the above-discussed functionalities
work.
package main
import (
"fmt"
"time"
)
var week time.Duration
func main() {
t := time.Now()
fmt.Println(t)
fmt.Printf("%02d.%02d.%4d\n", t.Day(), t.Month(), t.Year())
t = time.Now().UTC()
fmt.Println(t)
fmt.Println(time.Now())
// calculating times:
week = 60 * 60 * 24 * 7 * 1e9 // must be in nanosec
week_from_now := t.Add(week)
fmt.Println(week_from_now)
// formatting times:
fmt.Println(t.Format(time.RFC822))
fmt.Println(t.Format(time.ANSIC))
fmt.Println(t.Format("02 Jan 2006 15:04"))
s := t.Format("2006 01 02")
fmt.Println(t, "=>", s)
}
Date and Time
As you can see in the above code, at line 10 we declared t time and initialized
with the present time by time.Now() . Printing the t at line 11 prints complete
time with default format. At line 12 we specify the format of t as
dd.mm.yyyy. Next, we decide to add week in our time. We declared the week
variable and initialize it with a value of (60 ∗ 60 ∗ 24 ∗ 7 ∗ 1e9) at line 17. We
multiply a factor of 1e9 because we have to keep it in nanoseconds. At line 18
we add week to our time t . Now after printing t , change is noticeable, as
weeks are added into t . At line 21 time is printed in RFC822 format, e.g., 30
Oct 19 11:34 UTC At line 22 time is printed in ANSIC format, e.g., Wed Oct 30
11:34:03 2019 . At line 23 time is printed in 02 Jan 2006 15:04 format, e.g., 30
Oct 2019 11:34 . At line 24 we create a format(yyyy.mm.dd) and store it in s .
In the next line, we print the time t in default format and then in the format
specified above.
For more information on Golang’s time package, visit this page.
That’s about time package. In the next lesson, you’ll study pointers.
Pointers
This lesson discusses how pointers are used in the Go language.
WE'LL COVER THE FOLLOWING
• Introduction
• Pointers in Go
Introduction #
Unlike Java and .NET, Go gives the programmers control over which data
structure is a pointer and which is not; however, calculations with pointer
values are not supported in Go programs. By giving the programmer control
over basic memory layout, Go provides you the ability to control the total size
of a given collection of data structures, the number of allocations, and the
memory access patterns; all of which are important for building systems that
perform well.
Pointers are important for performance and indispensable if you want to do
systems programming close to the operating system and network. Because
pointers are somewhat unknown to contemporary OO-programmers, we will
explain them here and in the coming chapters in depth.
Pointers in Go #
Programs store values in memory, and each memory block (or word) has an
address, which is usually represented as a hexadecimal number, like
0x6b0820 or 0xf84001d7f0. Go has the address-of operator &, which, when
placed before a variable, gives us the memory address of that variable. For
example:
package main
import "fmt"
func main(){
var i1 = 5
fmt.Printf("An integer: %d, it's location in memory: %p\n", i1, &i1)
}
Memory Location of a Variable
The output of this example can be different every time because the value &i1
can be different each time we execute this statement.
This address can be stored in a special data type called a pointer. In the above
case, it is a pointer to an int. So i1 is denoted by *int . If we call that pointer
intP , we can declare it as:
var intP *int
Then, the following statement is true:
intP = &i1
So, intP stores the memory address of i1 which means it points to the
location of i1 . In other words, it references the variable i1 .
A pointer variable contains the memory address of another value which
means it points to that value in memory. The size of a pointer is 4 bytes on 32bit machines, and 8 bytes on 64-bit machines, regardless of the size of the
value they point to. Of course, pointers can be declared to a value of any type,
be it primitive or structured. The * is placed before the type of the value
(prefixing), so the * is a type modifier here.
Using a pointer to refer to a value is called indirection. A newly declared
pointer which has not been assigned to a variable has the nil value. A pointer
variable is often abbreviated as ptr.
In an expression like:
var p *type
always leave a space between the name of the pointer and the . The statement
var p*type is syntactically correct, but in more complex expressions, it can
easily be mistaken for a multiplication.
The same symbol * can be placed before a pointer like *intP , and it gives the
value which the pointer is pointing to and it is called the dereference (or
contents or indirection) operator. Another way to say this is that the pointer is
flattened. So for any variable var , the following is true:
var == *(&var)
Now you have the basic concepts of pointers, you can understand the
following program and its output.
package main
import "fmt"
func main() {
var i1 = 5
fmt.Printf("An integer: %d, its location in memory: %p\n", i1, &i1)
var intP *int
// Pointer variable
intP = &i1
// Storing address of i1 in pointer variable
fmt.Printf("The value at memory location %p is %d\n", intP, *intP)
}
Pointers
As you can see, we declared a variable i1 at line 5 and set the value of 5 to it.
In the next line, we print its value along with its memory address with &
operator as &i1 . At line 7, we declared a pointer variable intP that is meant
to have an address value. At line 8, we give intP the address of i1 using &i1 .
Finally, at line 9, we print the address using intP and value at that address by
dereferencing it as *intP , which is 5.
You can represent the memory usage as:
i1
5
intP
0xc82000a290
Pointers and Memory Usage
The following is another program that deals with strings and shows that
assigning a new value to the pointer variable changes the value of the
variable itself.
package main
import "fmt"
func main() {
s := "good bye"
var p *string = &s
*p = "ciao"
// changing the value at &s
fmt.Printf("Here is the pointer p: %p\n", p) // prints address
fmt.Printf("Here is the string *p: %s\n", *p) // prints string
fmt.Printf("Here is the string s: %s\n", s)
// prints same string
}
Changing Values with Pointers
As you can see, we declare a pointer variable p initially and set the address to
it of a string variable s , at line 6. At line 7, we are dereferencing the p
variable and changing value to ciao. It means the address that p holds which
was &s , does not have an initial value of s anymore but a new value that is
ciao. Then from line 9 to line 11, we are printing to see the results, which
show that dereferencing the p variable actually changes the s value from
good bye to ciao.
You can represent the memory usage as:
s
ciao
p
0xc82000a290
Pointers and Memory Usage
You cannot take the address of a literal or a constant as the following code
snippet shows:
package main
func main(){
const i = 5
ptr1 := &i //error: cannot take the address of i
ptr2 := &10 //error: cannot take the address of 10
}
Go, like most other low level (system) languages like C, C++, and D, has the
concept of pointers. But calculations with pointers (so-called pointer
arithmetic, e.g., pointer + 2, to go through the bytes of a string or the positions
in an array), which often lead to erroneous memory access in C and thus fatal
crashes of programs, are not allowed in Go, making the language memorysafe.
One advantage of pointers is that you can pass a reference to a variable (for
example, a parameter to a function) instead of passing a copy of that variable.
Pointers are cheap to pass, only 4 or 8 bytes. When the program has to work
with variables that occupy a lot of memory, or many variables, or both,
working with pointers can reduce memory usage and increase efficiency.
Pointer variables also persist in memory as long as there is at least 1 pointer
pointing to them, so their lifetime is independent of the scope in which they
were created.
On the other hand, because a pointer causes what is called an indirection (a
shift in the processing to another address), using them unnecessarily could
cause a performance decrease. Pointers can also point to other pointers, and
this nesting can go arbitrarily deep so that you can have multiple levels of
indirection. Still, in most cases, this will not contribute to the clarity of your
code. As we will see, in many instances Go makes it easier for the programmer
and will hide indirection like for example performing an automatic
dereference.
Run the following program to see that nill pointer dereference is not allowed.
package main
func main() {
var p *int = nil
// Making a nil pointer
*p = 0
}
// panic: runtime error: invalid memory address or nil pointer dereference
Nil Pointer Dereference
Oops! The program crashed. The reason is that we create a nil pointer at line
4 and then try to dereference it at line 5. This is not allowed. Remember that a
nil pointer dereference is illegal and makes a program crash.
That’s it about the basics of the Go language. In the next chapter, you’ll study
some control structures, but before we jump to it, let’s take a little quiz to test
our concepts.
Quiz: Deduce the Outputs
In this lesson, your concepts will be evaluated through a quiz.
Choose one correct answer
1
Deduce the output of the following program.
package main
var a = "G"
func main() {
n()
m()
n()
}
func n() { print(a) }
func m() {
a := "O"
print(a)
}
COMPLETED 0%
1 of 3
We hope that you performed well in the quiz. In the next chapter, you’ll study
some control structures.
The if-else Construct
This lesson discusses the if-else construct in detail.
WE'LL COVER THE FOLLOWING
• Introduction
• The if-else Construct
Introduction #
Until now, we have seen that a Go program starts executing in main() and
sequentially executes the statements in that function. However, we often want
to execute certain statements only if a condition is met, which means we want
to make decisions in our code. For this, Go provides the following conditional
or branching structures:
The if-else construct
The switch-case construct
The select construct
Repeating one or more statements (a task) can be done with the iterative or
looping structure:
for (range) construct
Some other keywords like break and continue can also alter the behavior of
the loop. There is also a return keyword to leave a body of statements and a
goto keyword to jump the execution to a label in the code. Go entirely omits
the parentheses ( and ) around conditions in if, switch and for-loops, creating
less visual clutter than in Java, C++ or C#.
The if-else Construct #
The if tests a conditional statement. That statement can
be logical or boolean. If the statement evaluates to true ,
the body of statements between { } after the if is
true
Condition
executed, and if it is false , these statements are ignored
and the statement following the if after } is executed.
if condition {
// do something
false
Statement(s)
rest of the code
if structrure
}
In a 2nd variant, an else , with a body of statements
surrounded by { }, is appended, which is executed when
the condition is false . It means we have two exclusive
branches, and only one of them is executed:
if condition {
// do something
true
Condition
false
Statement(s)
inside if body
Statement(s)
inside else body
} else {
// do something else
rest of the code
}
if and else
structrure
In a 3rd variant, another if condition can be placed
after the else , so we have 3 exclusive branches:
true
Condition
if condition1 {
// do something
} else if condition2 {
// do something else
} else {
// catch-all or default
}
The number of else if branches is in principle, not
false
Statement(s)
inside if body
true
Condition
false
Statement(s) inside
else-if body
Statement(s) inside
else body
limited, but for readability reasons, the use of else if
should not be exaggerated. When using this form, place
rest of the code
the condition which is most likely true first.
one else-if along
with if and else
structrure
The { } braces are mandatory even when there is only one statement in the
body (some people do not like this, but it is consistent and follows mainstream
software engineering principles). The { after the if and else must be on the
same line. The else if and else keywords must be on the same line as the
closing } of the previous part of the structure. Both of these rules are
mandatory for the compiler. This is in-valid Go code:
if x {
}
else { // INVALID
}
Note that every branch is indented with 4 (or 8) spaces or 1 tab and that the
closing } is vertically aligned with the if ; this is enforced by applying gofmt (a
default formatting tool for Go). The condition can also be composite, using the
logical operators && , || , and ! with the use of the parentheses () to enforce
precedence or improve readability.
Moreover, the parentheses () around the conditions are not needed. However,
for complex conditions, they may be used to make the code clearer. A possible
application is the testing of different values of a variable and executing
different statements in each case.
As a first example, let’s test whether an integer is even or odd:
package main
import "fmt"
func main() {
n := 42
// Use of control strcuture if and else to check whether number is even or not
if n % 2 == 0 {
fmt.Printf("The value is even\n")
} else {
fmt.Printf("The value is odd\n")
}
}
Even Integers
In the above code, we declare a variable n at line 5. Now, there are only two
cases whether a number is even or odd. So we’ll use the 2nd variant of the ifelse structure. We make a condition at line 7 that if n%2==0 , which will be
only true if n is even. If it is true then, we’ll print The value is even.
Otherwise, if the condition is false the control will transfer to the else
structure without even executing line 8. Then line 10 will be executed and
The value is odd will be printed.
The conditional statement after the if is a boolean expression, but it can also
be a boolean value, as in the following example:
package main
import "fmt"
func main() {
bool1 := true
// condition as a boolean value itself
if bool1 {
fmt.Printf("The value is true\n")
} else {
fmt.Printf("The value is false\n")
}
}
Condition as a Boolean Value
In the above code, we declare a boolean variable bool1 and initialize it with
true at line 5. At line 7, we make the condition if bool . Note that it is not
necessary to test: if bool1 == true , because bool1 is already a boolean value.
For this program, control will always transfer to the if structure as bool1 is
true, and The value is true will be printed on the screen.
For special cases, note that you could write something like:
if true {
fmt.Printf("I'm always executing this branch");
}
Here, the code block in the {} is always executed, so this is not very useful.
Inversely you could write:
if false {
fmt.Printf("I will never execute this code!")
}
Now, the code block in the {} will never be executed. It is almost always better
to test for true or positive conditions, but it is possible to test for the reverse
with ! (not):
if !bool1 // or if !(condition)
In the last case, the ( ) around the condition are often necessary, for example:
if !(var1 == var2)
which can be rewritten as the shorter:
if var1 != var2
The idiom in Go-code omits the else clause when the if ends in a break ,
continue , goto or return statement. For example, when an even value is to
be returned from the code, you would write:
if n % 2 == 0 {
return n
}
fmt.Printf("Continuing with an odd value\n")
When returning different values x and y whether or not a condition is true,
use the following pattern:
if condition {
return x
}
return y
Here is an example:
if n % 2 == 0 {
return n // return even value
}
return (n + 1) // return odd value
The structure of if can start with an initialization statement (in which a
value is given to a variable). This takes the form (the ; after the initialization is
mandatory):
if initialization; condition {
// do something
}
For example, instead of:
val := 10
if val > max {
// do something
}
you can write:
if val := 10; val > max {
// do something
}
But remember, the val variable will only be accessible within statements of
if block. If you try to access val anywhere in the program, it will give the
compiler error: undefined: val . This initialization during the if statement
can be put to use even more, as the following code snippet shows:
if value := process(data); value > max {
...
}
The result of a function process() can be retrieved in the if , and action is
taken according to the value .
Here is another complete example using variants of the if construct:
package main
import "fmt"
func main() {
var first int = 10
var cond int
if first <= 0 {
fmt.Printf("first is less than or equal to 0\n")
} else if first > 0 && first < 5 {
fmt.Printf("first is between 0 and 5\n")
} else {
fmt.Printf("first is 5 or greater\n")
}
if cond = 5; cond > 10 {
fmt.Printf("cond is greater than 10\n")
} else {
fmt.Printf("cond is not greater than 10\n")
}
}
If-Else with Variants
In the above code, we declare two integer variables first and cond . At line7,
we make a condition if first <=0 . If it is true for the value of first , line 8
will be executed, and then control will go to line 14.
If false, control will transfer from line 7 to line 9 directly. There, we make
another condition using else if , i.e., else if first > 0 && first < 5 . If this
condition is true, then control will transfer to line 10 and then directly to line
14. Otherwise, control will be transferred directly to line 11 from line 9.
At line 11 is the else structure. Within the else structure, line 12 will be
executed. After executing statements within else , control will come to line
14. If the condition at line 14 is true, then line 15 will be executed. Otherwise,
line 17 within else structure.
Look at the condition on line 14: if cond =5; cond > 10 . We are initializing
cond at the time of condition.
Below is the illustration that shows the transfer of control throughout the
above program.
Code executing
var first int = 10
var cond int
if first <= 0 {
fmt.Printf("first is less than or equal to 0\n")
} else if first > 0 && first < 5 {
fmt.Printf("first is between 0 and 5\n")
} else {
fmt.Printf("first is 5 or greater\n")
}
if cond = 5; cond > 10 {
fmt.Printf("cond is greater than 10\n")
} else {
fmt.Printf("cond is not greater than 10\n")
}
Code skipped
Code executed
Flow of Control of Execution
1 of 14
Code executing
var first int = 10
var cond int
if first <= 0 {
fmt.Printf("first is less than or equal to 0\n")
} else if first > 0 && first < 5 {
fmt.Printf("first is between 0 and 5\n")
} else {
fmt.Printf("first is 5 or greater\n")
}
if cond = 5; cond > 10 {
fmt.Printf("cond is greater than 10\n")
} else {
fmt.Printf("cond is not greater than 10\n")
}
Code skipped
Code executed
Flow of Control of Execution
2 of 14
Code executing
var first int = 10
var cond int
if first <= 0 {
fmt.Printf("first is less than or equal to 0\n")
} else if first > 0 && first < 5 {
fmt.Printf("first is between 0 and 5\n")
} else {
fmt.Printf("first is 5 or greater\n")
}
if cond = 5; cond > 10 {
fmt.Printf("cond is greater than 10\n")
} else {
fmt.Printf("cond is not greater than 10\n")
}
Code skipped
Code executed
Flow of Control of Execution
3 of 14
Code executing
var first int = 10
var cond int
if first <= 0 {
fmt.Printf("first is less than or equal to 0\n")
} else if first > 0 && first < 5 {
fmt.Printf("first is between 0 and 5\n")
} else {
fmt.Printf("first is 5 or greater\n")
}
if cond = 5; cond > 10 {
fmt.Printf("cond is greater than 10\n")
} else {
fmt.Printf("cond is not greater than 10\n")
}
Code skipped
Code executed
Flow of Control of Execution
4 of 14
False
Code executing
var first int = 10
var cond int
if first <= 0 {
fmt.Printf("first is less than or equal to 0\n")
} else if first > 0 && first < 5 {
fmt.Printf("first is between 0 and 5\n")
} else {
fmt.Printf("first is 5 or greater\n")
}
if cond = 5; cond > 10 {
fmt.Printf("cond is greater than 10\n")
} else {
fmt.Printf("cond is not greater than 10\n")
}
Code skipped
Code executed
Flow of Control of Execution
5 of 14
Code executing
var first int = 10
var cond int
if first <= 0 {
fmt.Printf("first is less than or equal to 0\n")
} else if first > 0 && first < 5 {
fmt.Printf("first is between 0 and 5\n")
} else {
fmt.Printf("first is 5 or greater\n")
}
if cond = 5; cond > 10 {
fmt.Printf("cond is greater than 10\n")
} else {
fmt.Printf("cond is not greater than 10\n")
}
Code skipped
Code executed
Flow of Control of Execution
6 of 14
False
Code executing
var first int = 10
var cond int
if first <= 0 {
fmt.Printf("first is less than or equal to 0\n")
} else if first > 0 && first < 5 {
fmt.Printf("first is between 0 and 5\n")
} else {
fmt.Printf("first is 5 or greater\n")
}
if cond = 5; cond > 10 {
fmt.Printf("cond is greater than 10\n")
} else {
fmt.Printf("cond is not greater than 10\n")
}
Code skipped
Code executed
Flow of Control of Execution
7 of 14
Code executing
var first int = 10
var cond int
if first <= 0 {
fmt.Printf("first is less than or equal to 0\n")
} else if first > 0 && first < 5 {
fmt.Printf("first is between 0 and 5\n")
} else {
fmt.Printf("first is 5 or greater\n")
}
if cond = 5; cond > 10 {
fmt.Printf("cond is greater than 10\n")
} else {
fmt.Printf("cond is not greater than 10\n")
}
Code skipped
Code executed
Flow of Control of Execution
8 of 14
first is 5 or greater
True
Code executing
Code skipped
var first int = 10
var cond int
if first <= 0 {
fmt.Printf("first is less than or equal to 0\n")
} else if first > 0 && first < 5 {
fmt.Printf("first is between 0 and 5\n")
} else {
fmt.Printf("first is 5 or greater\n")
}
if cond = 5; cond > 10 {
fmt.Printf("cond is greater than 10\n")
} else {
fmt.Printf("cond is not greater than 10\n")
}
first is 5 or greater
Code executed
Flow of Control of Execution
9 of 14
first is 5 or greater
Code executing
Code skipped
var first int = 10
var cond int
if first <= 0 {
fmt.Printf("first is less than or equal to 0\n")
} else if first > 0 && first < 5 {
fmt.Printf("first is between 0 and 5\n")
} else {
fmt.Printf("first is 5 or greater\n")
}
if cond = 5; cond > 10 {
fmt.Printf("cond is greater than 10\n")
} else {
fmt.Printf("cond is not greater than 10\n")
}
first is 5 or greater
Code executed
Flow of Control of Execution
10 of 14
first is 5 or greater
False
Code executing
Code skipped
var first int = 10
var cond int
if first <= 0 {
fmt.Printf("first is less than or equal to 0\n")
} else if first > 0 && first < 5 {
fmt.Printf("first is between 0 and 5\n")
} else {
fmt.Printf("first is 5 or greater\n")
}
if cond = 5; cond > 10 {
fmt.Printf("cond is greater than 10\n")
} else {
fmt.Printf("cond is not greater than 10\n")
}
first is 5 or greater
Code executed
Flow of Control of Execution
11 of 14
first is 5 or greater
Code executing
Code skipped
var first int = 10
var cond int
if first <= 0 {
fmt.Printf("first is less than or equal to 0\n")
} else if first > 0 && first < 5 {
fmt.Printf("first is between 0 and 5\n")
} else {
fmt.Printf("first is 5 or greater\n")
}
if cond = 5; cond > 10 {
fmt.Printf("cond is greater than 10\n")
} else {
fmt.Printf("cond is not greater than 10\n")
}
first is 5 or greater
Code executed
Flow of Control of Execution
12 of 14
first is 5 or greater
Code executing
Code skipped
True
var first int = 10
var cond int
if first <= 0 {
fmt.Printf("first is less than or equal to 0\n")
} else if first > 0 && first < 5 {
fmt.Printf("first is between 0 and 5\n")
} else {
fmt.Printf("first is 5 or greater\n")
}
if cond = 5; cond > 10 {
fmt.Printf("cond is greater than 10\n")
} else {
fmt.Printf("cond is not greater than 10\n")
}
first is 5 or greater
cond is not greater than 10
Code executed
Flow of Control of Execution
13 of 14
first is 5 or greater
Code executing
Code skipped
var first int = 10
var cond int
if first <= 0 {
fmt.Printf("first is less than or equal to 0\n")
} else if first > 0 && first < 5 {
fmt.Printf("first is between 0 and 5\n")
} else {
fmt.Printf("first is 5 or greater\n")
}
if cond = 5; cond > 10 {
fmt.Printf("cond is greater than 10\n")
} else {
fmt.Printf("cond is not greater than 10\n")
}
first is 5 or greater
cond is not greater than 10
Code executed
Flow of Control of Execution
14 of 14
That’s it about how control is transferred using the if-else construct. The next
lesson describes how to test for errors on built-in functions.
Testing for Errors on Functions
In this lesson, you'll learn about errors that originate from functions and how to resolve them.
WE'LL COVER THE FOLLOWING
• Testing support
• Tracking error in a function
Testing support #
Sometimes, functions in Go are defined so that they return
two results. One is the value and the other is the status of the
execution. For example, the function will return a value and
true in case of successful execution. Whereas, it will return a
value (probably nil) and false in case of an unsuccessful
execution.
Instead of true and false, an error-variable can be returned.
In the case of successful execution, the error is nil.
Otherwise, it contains the error information. It is then
obvious to test the execution with an if statement because
of its notation; this is often called the comma, ok pattern.
Consider the following example:
v, ok = sample_function(parameter)
Here, the comma, ok pattern is being followed. The value goes to v , and the
ok parameter holds the status of the error during the execution. If there
would be no error, then sample_function will return true to ok otherwise, it
will return an error value.
Tracking error in a function #
In the previous chapter while converting to and from a string, the function
strconv.Atoi converts a string to an integer. There, we discarded a possible
error-condition with:
anInt, _ = strconv.Atoi(origStr)
If origStr cannot be converted to an integer, the function returns 0 for anInt ,
and the _ absorbs the error, and the program continues to run.
A program should test for every occurring error and behave accordingly by, at
least informing the user (or world) of the error-condition and returning from
the function or even halting the program.
Let’s write another program to implement testing for errors.
package main
import (
"fmt"
"strconv"
)
func main() {
var orig string = "ABC"
var an int
var err error
// storing integer and error information
an, err = strconv.Atoi(orig)
if err != nil { // if it was an error, discontinue
fmt.Printf("orig %s is not an integer - exiting with error\n", orig)
return
}
fmt.Printf("The integer is %d\n", an)
// rest of the code
}
String Conversion with Error Testing
In the above code, at line 9 and line 10, we declare two variables an for value
and err for error information. Then, we call the function strconv.Atoi for
string orig . At line 14, we make a condition if err != nil . If this condition is
true it means there was an error during the execution of the function, and due
to the return statement, the program will terminate. If the condition is false,
then line 18 and the rest of the code will be executed. In this case, the
program will terminate with an error display message because orig isn’t an
integer.
We could also have used the form of return which returns a variable like
return err . In this case, the calling function can examine the error err .
value, err := pack1.Function1(param1)
if err != nil {
fmt.Printf("An error occurred in pack1.Function1 with parameter %v", p
aram1)
return err
}
// normal case, continue execution:
In the above program, it was main() executing so the program stops. If we do
want the program to stop in case of an error, we can use the function Exit
from package os instead of return :
if err != nil {
fmt.Printf("Program stopping with error %v", err)
os.Exit(1)
}
Look at the following code where we tried to open a file:
f, err := os.Open(fname)
if err !=nil {
return err
}
doSomething(f) // In case of no error, the file f is passed to a functio
n doSomething
Sometimes, the above idiom is repeated a number of times in succession. No
else branch is written. If there is no error-condition, the code simply
continues execution after the if { } .
Remark: More information on os package can be found here.
Now you know how to handle errors. In the next lesson, you’ll study another
control structure called switch case.
The switch-case Construct
This lesson discusses the switch-case construct in detail.
WE'LL COVER THE FOLLOWING
• Introduction
• switch statement with values
• switch statement with conditions
• Initialization within the switch statement
Introduction #
In the last two lessons, we studied the if-else construct. There is another
control structure known as the switch-case structure.
The keyword switch is used instead of long if statements that compare a
variable to different values. The switch statement is a multiway branch
statement that provides an easy way to transfer flow of execution to different
parts of code based on the value. The following figure explains the basic
structure of the switch-case construct.
Expression
Case 1
Code in
Case1 Block
Case 2
Code in
Case2 Block
Case 3
.
Code in
Case3 Block
.
.
Default
Code in
Default Block
Switch Case Construct
switch statement with values #
Compared to the C and Java languages, switch in Go is considerably more
flexible. It takes the general form:
switch var1 {
case val1:
...
case val2:
...
default:
...
}
Where var1 is a variable which can be of any type, and val1 , val2 , … are
possible values of var1 . These don’t need to be constants or integers, but they
must have the same type, or expressions evaluating to that type. The opening {
has to be on the same line as the switch . The ellipses ... here means that
after the case statement, multiple statements can follow without being
surrounded by { }, but braces are allowed.
When there is only 1 statement: it can be placed on the same line as case ...
: . The last statement, in any case, can also be a return with or without an
expression. When the case statements end with a return statement, there
also has to be a return statement after the } of the switch .
More than one value can be tested in a case. For this, the values are presented
in a comma-separated list like:
case val1, val2, val3:
Each case -branch is exclusive. They are tried first to last. We should place the
most probable values first, to save the time of computation. The first branch
that is correct is executed, and then the switch statement is complete.
Compare the following two cases:
switch i {
case 0: //empty case body, nothing is executed when i==0
case 1:
f() // f is not called when i==0!
}
And:
switch i {
case 0: fallthrough
case 1:
f() // f is now also called when i==0!
}
In the first case, if i has the value 0, no code will be executed because case 0
has no code body, and the switch terminates immediately. To obtain the same
behavior in C, you need to add a break after case 0 . If, on the other hand, you
explicitly want to execute the code from case 1 , when i has the value 0, you
need to add the keyword fallthrough at the end of the case 0 branch. This is
illustrated in the second case. With fallthrough , all the remaining case
branches are executed until a branch is encountered, which does not contain
a fallthrough .
Fallthrough can also be used in a hierarchy of cases where at each level
something has to be done in addition to the code already executed in the
higher cases, and a default action also has to be executed. The (optional)
default branch is executed when no value is found to match var1 with; it
resembles the else clause in if-else statements. It can appear anywhere in
the switch (even as the first branch), but it is best written as the last branch.
Let’s write a simple program to see how the switch statement works.
package main
import "fmt"
func main() {
var num1 int = 100
// Adding switch on num1
switch num1 {
case 98, 99:
// first case: num1 = 98 or 99
fmt.Println("It's equal to 98")
case 100:
// second case: num1 = 100
fmt.Println("It's equal to 100")
default:
// optional/ default case
fmt.Println("It's not equal to 98 or 100")
}
}
Switch Case Construct with Values
In the above code:
We declare a variable num1 and give a value 100 to it. Now we use a
switch statement against num1 value, which means different cases will
be written on num1 . We made a total of 3 cases, including the default
case.
The first case is for two values: 98 and 99. If this case is true, then line 9
will be executed. If not, then control will be transferred directly to line 10
for the second case .
The second case is for the value of 100. If this case is true, then line 11
will be executed. If not, then control will be transferred directly to line 12
for the third case .
The third case is the default case. If none of the above cases are
executed, then this case will be true in any case, and line 13 will be
executed.
For the above program, the second case: case 100 is true, and It’s equal to
100 will be printed on the screen.
switch statement with conditions #
In this form of the switch statement, no variable is required (this is, in fact, a
switch true), and the cases can test different conditions. The first true
condition is executed. It looks very much like if-else chaining and offers a
more readable syntax if there are many branches. The syntax is as follows:
switch {
case condition1:
...
case condition2:
...
default:
...
}
For example:
switch {
case i < 0:
f1() // function call
case i == 0:
f2() // function call
case i > 0:
f3() // function call
}
Any type that supports the equality comparison operator, such as ints, strings
or pointers, can be used in these conditions. Look at the following program to
see how switch works with conditions instead of values.
package main
import "fmt"
func main() {
var num1 int = 100
switch {
case num1 < 0:
fmt.Println("Number is negative")
case num1 > 0 && num1 < 10:
fmt.Println("Number is between 0 and 10")
default:
fmt.Println("Number is 10 or greater")
}
}
Switch Case Construct with Conditions
In the above code, we declare a variable num1 and give it a value 100. We can
use a switch statement without any value, which means different cases will
be based on conditions. We made a total of three cases, including the default
case.
The first case is for numbers less than 0: case num1 < 0: . If this case is true,
then line 8 will be executed. If not, then control will be transferred directly to
line 10 for the second case.
The second case is for the condition: case num1 > 0 && num1 < 10: If this case
is true, then line 11 will be executed. If not, then control will be transferred
directly to line 13 for the third case.
The third case is the default case. If none of the above cases are executed,
then this case will be true in any case, and line 14 will be executed. For the
above program, the third( default ) case is true, and Number is 10 or greater
will be printed on the screen.
Initialization within the switch statement #
A switch can also contain an initialization statement, like the if construct:
switch initialization; {
case val1:
...
case val2:
...
default:
...
}
After writing the switch keyword, we can do initialization and add a ; at the
end:
switch a, b := x[i], y[j]; {
case a < b: t = -1
case a == b: t = 0
case a > b: t = 1
}
Here, a and b are retrieved in the parallel initialization, and the cases are
conditions. According to the above code, a is equal to x[i] and b is equal to
y[j] . Let’s write a program that covers the concept of initialization within a
switch statement.
package main
import "fmt"
func main() {
// initialization within switch block
switch num1 := 100;{
case num1 < 0:
fmt.Println("Number is negative")
case num1 > 0 && num1 < 10:
fmt.Println("Number is between 0 and 10")
default:
fmt.Println("Number is 10 or greater")
}
}
Switch Case Construct with Conditions and Initialization
It is the same program we wrote previously, but with a little modification. Can
you notice the difference? Previously, we declared num1 separately. But now,
we declare the variable in the switch block. Notice line 6 where we declare
and initialize num1 in the same line as switch : switch num1 := 100; . The rest
of the program is the same and will produce the same output.
That’s it about how control is transferred using a switch-case construct. In the
next lesson, you’ll have to write a function to solve a problem.
Challenge: Season of a Month
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Input
• Output
• Sample input
• Sample output
Problem statement #
Write a function Season that has as input-parameter of a
month number and which returns the name of the season to
which this month belongs (disregard the day in the month).
Make sure you follow the following criteria of month names
and their values:
January: 1
February: 2
March: 3
April: 4
May: 5
June: 6
July: 7
August: 8
September: 9
October: 10
November: 11
December: 12
Another thing to note is the seasons of the months. Look at the following
mapping:
Winter: 1, 2, and 12
Spring: 3, 4, and 5
Summer: 6, 7, and 8
Autumn: 9, 10, and 11
Input #
An int variable
Output #
A string variable
Sample input #
3
Sample output #
Spring
Think about what would happen if the user enters the wrong month
value(less than 1 and greater than 12). Try to implement the function below.
Feel free to view the solution, after giving it a few shots. Good Luck!
package main
import "fmt"
import "strconv"
import "encoding/json"
func Season(month int) string {
return ""
}
Season of a Month(switch-case)
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Season of a Month
This lesson discusses the solution to the challenge given in the previous lesson.
WE'LL COVER THE FOLLOWING
• Using switch-case construct
• Using if-else construct
There are two methods to solve this problem:
One is using the switch-case construct, which is the requirement of the
problem statement
The other is using the if-else construct.
We’ll first discuss the switch-case solution.
Using switch-case construct #
package main
import "fmt"
func main() {
fmt.Printf(Season(3))
}
// calling function to find the season
func Season(month int) string {
switch month { // switch on the basis of
case 12,1,2: return "Winter" //
case 3,4,5:
return "Spring"
case 6,7,8:
return "Summer" //
case 9,10,11: return "Autumn" //
default: return "Season unknown"
}
}
value of the months(1-12)
Jan, Feb and Dec have winter
// March, Apr and May have spring
June, July and Aug have summer
Sept, Oct and Nov have autumn
//value outside [1,12], then season
Season of a Month(switch-case)
As you can see, we declare a function Season at line 9, which takes an integer
value that represents a month as an input parameter. That parameter is the
value of month . As per requirement, we add switch month to switch on the
basis of the value of the month. There are five cases in total along with the
default case.
We make the first case at line 11 for the season of Winter. We return
Winter for months of Jan, Feb, and Dec by using values 1,2, and 12
respectively.
We make the second case at line 12 for the season of Spring. We return
Spring for months of Mar, Apr, and May by using values 3,4, and 5,
respectively.
We make the third case at line 13 for the season of Summer. We return
Summer for months of June, July, and Aug by using values 6,7, and 8,
respectively.
We make the fourth case at line 14 for the season of Autumn. We return
Autumn for months of Sept, Oct, and Nov by using values 9,10, and 11
respectively.
Up till now, we have written 4 cases that cover all the seasons. But still,
we need one case more. What if the user enters the wrong value for the
month, less than 1 or greater than 12? In such a scenario, we have to
have a default case: return "Season Unknown" . If the user inputs an
invalid value for the month, the season should be unknown.
The main function is at line 5, in which we have called the Season function to
view the results. We pass 3 as a parameter to the function, which means the
second case will be executed and Spring will be printed on the screen.
Using if-else construct #
package main
import "fmt"
func main() {
fmt.Printf(Season(3))
// calling function to find the season
}
func Season(month int) string {
if (month == 12) || (month == 1) || (month ==2) { // Jan, Feb and Dec have w
return "Winter"
}else if (month == 3) || (month == 4) || (month ==5) { // March, Apr and May
return "Spring"
}else if (month == 6) || (month == 7) || (month ==8) { // June, July and Aug
return "Summer"
}else if (month == 9) || (month == 10)|| (month ==11){ // Sept, Oct and Nov h
return "Autumn"
}else{ //value outside [1,12], then season is unkown
return "Season unknown" }
}
Season of a Month (if-else)
As you can see, we declare a function Season at line 9, which takes an integer
value that represents a month as an input parameter. That parameter is the
value of month . There are five conditions in total.
We make the first condition at line 11 for the season of Winter. We
return Winter for months of Jan, Feb, and Dec by using values 1,2, and
12 respectively.
We make the second condition at line 13 for the season of Spring. We
return Spring for months of Mar, Apr, and May by using values 3,4, and
5, respectively.
We make the third condition at line 15 for the season of Summer. We
return Summer for months of June, July, and Aug by using values 6,7, and
8, respectively.
We make the fourth condition at line 17 for the season of Autumn. We
return Autumn for months of Sept, Oct, and Nov by using values 9,10, and
11 respectively.
Up till now, we have written 4 conditions that cover all the seasons, but
there is still, an edge case. What if the user enters the wrong value for the
month, less than 1 or greater than 12? In such a scenario, we have to
have an else part: return "Season Unknown" . If the user inputs an invalid
value for the month, the season should be unknown.
The main function is at line 5, in which we have called the Season function to
view the results. We pass 3 as a parameter to the function, which means the
second condition is true and Spring will be printed on the screen.
That’s it about the solution. In the next lesson, you’ll study another control
construct in Go called the for construct.
The for Construct
This lesson discusses the for construct in detail.
WE'LL COVER THE FOLLOWING
• Introduction
• Types of for loops
• Counter-controlled iteration
• Condition-controlled iteration
Introduction #
So far, we have studied two types of constructs; the if-else and the switch
construct. Another very important and widely used control structure in
programming is the for construct.
In Go, the for statement exists to repeat a body of statements a number of
times. One pass through the body is called an iteration.
Remark: There is no for match for the do-while statement found in
most other languages. It was probably excluded because the use case for
it was not that important.
Types of for loops #
There are two methods to control iteration:
Counter-controlled iteration
Condition-controlled iteration
Let’s study them one by one.
Counter-controlled iteration #
The simplest form is the counter-controlled iteration. The general format is:
for initialization; condition; modification { }
For example:
for i := 0; i < 10; i++ {}
The body { } of the for-loop is repeated a known number of times; this is
counted by a variable i . The loop starts with an initialization for i as: i := 0
( this is performed only once). This is shorter than a declaration beforehand,
and it is followed by a conditional check on i : i < 10 , which is performed
before every iteration. When the condition is true, the iteration is done.
Otherwise, the for-loop stops when the condition becomes false. Then comes a
modification of i : i++ , which is performed after every iteration, at which
point the condition is checked again to see if the loop can continue. This
modification could, for example, also be a decrement, or + or –, using a step.
The following is a figure that explains the for construct.
Start
Initialization
False
Condition
True
Code Inside
Loop
End
For Construct
Update
Counter
Run the following program and see how a for loop is implemented.
package main
import "fmt"
func main() {
// for loop with 5 iterations
for i := 0; i < 5; i++ {
fmt.Printf("This is the %d iteration\n", i)
}
}
Counting Iterations in a For Loop
As you can see, we initialize i:=0 for a for loop at line 6. We set the condition
as i<5 , which means the loop will run for 5 times (as there are 5 numbers
from 0 to 4 inclusively). As a modification, we set i++ . After every iteration, i
will be incremented by 1. Inside for loop’s body (at line 7), we are just printing
the iteration number, which is the value of i for a certain iteration.
According to the output, you can see that there are 5 iterations in total,
ranging from 0 to 4 iterations.
These are 3 separate statements which form the header of the loop. They are
separated by ;, but there are no ( ) surrounding the header:
for (i = 0; i < 10; i++) { } //is invalid Go-code!
Again the opening { has to be on the same line as the for keyword. The
counter-variable ceases to exist after the } of the for; always use short names
for it like i , j , z , or x . Never change the counter-variable in for loop itself;
this is considered bad practice.
More than 1 counter can also be used as in:
for i, j := 0, N; i < j; i, j = i+1, j-1 { }
This method is often the preferred way in Go as we can use parallel
assignment. Here, we have two counters i and j . Counter i is
incrementing by 1, and counter j is decrementing by 1.
For-loops can be nested, like this:
for i:=0; i<5; i++ {
for j:=0; j<10; j++ {
println(j)
}
}
Here, for every iteration of i , j will run 10 times. This means, that the outer
loop: for i:=0; i<5; i++ will run 5 times and inner loop : for j:=0; j<10;
j++ will run 50 times in total.
What happens if we use for-loop for a general Unicode-string? Let’s run a
program and find out.
package main
import "fmt"
func main() {
str := "Go is a beautiful language!"
fmt.Printf("The length of str is: %d\n", len(str))
// for loop to find character at each position
for ix :=0; ix < len(str); ix++ {
fmt.Printf("Character on position %d is: %c \n", ix, str[ix])
}
str2 := "Chinese: β½‡ζœ¬θͺž"
fmt.Printf("The length of str2 is: %d\n", len(str2))
// for loop to find character at each position
for ix :=0; ix < len(str2); ix++ {
fmt.Printf("Character on position %d is: %c \n", ix, str2[ix])
}
}
Unicode String with For
As you can see at a line 5, we declare string str and initialize it with “Go is a
beautiful language!”. Then, at the next line, we find the length of str and
print it via function len(str) . We make a for loop at line 9: for ix :=0; ix <
len(str); ix++ . Here, ix is a counter whose initial value is 0 and will
increment after every iteration by 1. The loop will run len(str) times. Inside
its body, at line 10, we are printing every instance of the string line by line by
indexing as str[ix] .
Similarly, we declare another string at line 12 as str2 and initialize it with
“Chinese: β½‡ζœ¬θͺž”. Then, at the next line, we find the length of str and print it
via function len(str2) . We made a for loop at line 16: for ix :=0; ix <
len(str2); ix++ . Here, ix is a counter whose initial value is 0, and it will
increment after every iteration by 1. The loop will run len(str2) times. Inside
its body at line 17, we are printing every instance of the string line by line by
indexing as str2[ix] . The output will show characters of each string line by
line.
If we print out the length of strings str and str2 , we get 27 and 18,
respectively. We see that for normal ASCII-characters using 1 byte, an indexed
character is a full character. Whereas, for non-ASCII characters (who need 2
to 4 bytes), the indexed character is no longer correct! We can solve this
problem through for range, which we will cover later in this lesson.
Condition-controlled iteration #
The 2nd form contains no header and is used for condition-controlled iteration
(known as the while-loop in other languages) with the general format:
for condition { }
You could also argue that it is a for without the init and modif sections, so
that the semicolons (; ;) are superfluous.
Run the following program and see how this for loop does the required task by
only using a condition.
package main
import "fmt"
func main() {
var i int = 0
// condition controlled for loop with 5 iterations
for i < 5 {
fmt.Printf("This is the %d iteration\n", i)
i = i + 1
}
}
Counting Iterations in a Condition Controlled For Loop
We had written a similar program previously, but this time, we use conditioncontrolled iterations except for the counter-controlled iteration. At line 5, we
declare a new variable var i int =0 instead of doing it in init part of for loop.
At line 7, you can see a condition: for i<5 , which means that loop will run
until i is less than 5. You may notice that the modification part is missing. In a
condition-controlled iteration, we modify the counter in the body of the loop.
At line 8, we are printing the iteration number of the loop, and at line 9 is the
modification of counter: i=i+1 . As the counter i increases by 1 in each
iteration, it means that the loop will run 5 times only. The output is the same
as when dealing with counter-controlled iterations.
That’s it about how control is transferred using the for construct. The next
lesson focuses on a variation for running loops called for range .
The for range Construct
This lesson discusses another variation for running loops in Go, i.e., the for range construct
WE'LL COVER THE FOLLOWING
• In nite loop
• Use of for range
In nite loop #
The condition can be absent like in:
for i:=0; ; i++ or for { }
This is the same as for ;; { } but the ; ; is removed by gofmt. These are
infinite loops. The latter could also be written as:
for true { }
But the normal format is:
for { }
If a condition check is missing in a for-header, the condition for looping is and
remains always true. Therefore, in the loop-body, something has to happen in
order for the loop to be exited after a number of iterations. Always check that
the exit-condition will evaluate to true at a certain moment in order to avoid
an endless loop! This kind of loop is exited via a break statement, which we’ll
study in the next lesson, or a return statement. But, there is a difference.
Break only exits from the loop while return exits from the function in which
the loop is coded!
Use of for range #
The for range is the iterator construct in Go, and you will find it useful in a
lot of contexts. It is a very elegant variation used to make a loop over every
item in a collection. The general format is:
for ix, val := range coll { }
The range keyword is applied to an indexed collection coll . Each time range
is called, it returns an index ix and the copy of value val at that index in the
collection coll . So the first time range is called on coll , it returns the first
element; that is ix==0 and val==coll[0]==coll[ix] . This statement is similar
to a foreach statement in other languages, but we still have the index ix at
each iteration in the loop.
Be careful! Here, val is a copy of the value at that index ix in the collection
coll , so it can be used only for read-purposes. The real value in the collection
cannot be modified through val .
Now, let’s solve the issue we encountered when printing characters of a
Unicode string.
package main
import "fmt"
func main() {
str := "Go is a beautiful language!"
// for range
for pos, char := range str {
fmt.Printf("Character on position %d is: %c \n", pos, char)
}
fmt.Println()
str2 := "Chinese: β½‡ζœ¬θͺž"
// for range
for pos, char := range str2 {
fmt.Printf("Character %c starts at byte position %d\n", char, pos)
}
}
Unicode String with For Range
As you can see at a line 5, we declare a string str and initialize it with “Go is
a beautiful language!”. Then at line 8, we made a for range loop: for pos,
char := range str . Here, pos is the position of a character char in string str .
The loop will run len(str) times. Inside its body, at line 9, we are printing
every instance of the string along with its position line by line. Similarly, we
declare another string at line 12 as str2 and initialize it with “Chinese: β½‡ζœ¬
θͺž”. At line 15, we made a for range loop: for pos, char := range str2 . Here,
pos is the position of a character char in string str2 . The loop will run until
the whole str2 isn’t traversed index by index. Inside the loop’s body at line
16, we are printing every instance of the string line by line along with its
position. The output will show characters of each string line by line. We see
that the normal English characters are represented by 1 byte, and the Chinese
characters by 3 bytes.
That’s it about how control is transferred using the for range construct. The
next lesson describes another control construct in Go, also known as the break
and continue construct.
Break and Continue
This lesson discusses the break and continue construct in detail.
WE'LL COVER THE FOLLOWING
• Introduction
• break statement
• continue statement
Introduction #
Sometimes, we may want to skip the execution of a loop for a certain
condition or terminate it immediately without checking the condition. To
specifically change the flow of execution, we have another two statements,
break and continue .
break statement #
In every iteration, a condition has to be checked to see whether the loop
should stop. If the exit-condition becomes true, the loop is left through the
break statement. A break statement always breaks out of the innermost
structure in which it occurs; it can be used in any for-loop (counter, condition,
and so on), but also in a switch , or a select statement. Execution is continued
after the ending } of that structure. The following figure explains the break
statement.
Start of
Loop
Loop Condition
false
true
break
yes
no
Body of Loop
Exit of
Loop
Break Statement
Run the following program to understand how break works.
package main
import "fmt"
func main() {
for i:=0; i<3; i++ {
// outer loop
for j:=0; j<10; j++ {
// inner loop
if j>5 {
break
// breaking inner loop
}
fmt.Printf("%d",j)
}
fmt.Printf(" ")
}
}
Break statement in Nested Loop
The program implements the break statement within the nested loop. At line
5, we made an outer counter-controlled loop: for i:=0; i<3; i++ which will
iterate three times. Then at line 6, we made an inner counter-controlled loop:
for j:=0; j<10; j++ that will run 10 times for each i . You may have noticed
that we added a break statement at line 8 for a condition j>5 at line 7. It
means that the inner loop will always break when j is greater than 5. Control
will transfer to the outer loop. That’s why the output is 012345 012345 012345.
The pattern 012345 is printed thrice because the outer loop runs three times
only. Additionally, numbers ranging from 0 to 5 inclusively are printed
because the inner loop breaks after 5.
continue statement #
The keyword continue skips the remaining part of the loop but then continues
with the next iteration of the loop after checking the condition. The following
is a figure that explains the continue statement.
Start of
Loop
Loop Condition
true
yes
continue
no
Body of Loop
Exit of
Loop
false
Continue Statement
Run the following program to understand how continue works.
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
if i == 5 {
continue
}
fmt.Printf("%d",i)
fmt.Print(" ")
}
}
// for loop
// continuing the loop
Continue Statement in For Loop
This program is an implementation of the continue statement within a loop.
At line 5, we made a counter-controlled loop: for i:=0; i<10; i++ which will
iterate 10 times. You may have noticed that we added a continue statement at
line 7 for a condition i==5 at line 6. This means that the loop will iterate
again for a new i , leaving further statements in that loop when i is equal to
5. That’s why the output is 0 1 2 3 4 6 7 8 9. Numbers up to 9 are printed
because the loop runs ten times only. And the number 5 is missing because the
iteration starts again leaving line 9 and line 10 unexecuted.
Remember that the keyword continue can only be used within a for-loop.
That’s it about how control is transferred using the break and continue
construct. The next lesson describes the use of labels in Go to control the flow
of execution.
Use of Labels - goto
In this lesson you'll study how a program execution can be controlled with labels.
WE'LL COVER THE FOLLOWING
• Introduction
• Use of labels in Go
• Use of goto keyword
Introduction #
A label is a sequence of characters that identifies a location
within a code. A code line which starts with a for, switch, or
select statements can be preceded with a label of the form:
IDENTIFIER:
This is the first word ending with a colon : and preceding the code (gofmt puts
it on the preceding line).
Use of labels in Go #
Let’s look at an example of using a label in Go:
package main
import "fmt"
func main() {
LABEL1:
for i := 0; i <= 5; i++ {
for j := 0; j <= 5; j++ {
if j == 4 {
continue LABEL1
}
// adding a label for location
// outer loop
// inner loop
// jump to the label
fmt.Printf( i is: %d, and j is: %d\n , i, j)
}
}
}
Control using Labels
As you can see, we made a label LABEL1 at line 5. At line 6, we made an outer
loop: for i := 0; i <= 5; i++ that will iterate 5 times. At line 7, we made an
inner loop: for j := 0; j <= 5; j++ that will iterate 5 times for each i . You
may have noticed that we added continue LABEL1 at line 9. The execution will
jump to the label LABEL1 if condition j ==4 is satisfied at line 8. You can see
that cases j == 4 and j == 5 do not appear in the output because the label
precedes the outer loop, which starts i at its next value continuing the outer
loop and causing the j in the inner for loop to reset to 0 at its initialization.
The continue can be very handy when dealing with nested loops. If we
replace continue with break in our program above, our output is only:
i is: 0, and j is: 0
i is: 0, and j is: 1
i is: 0, and j is: 2
i is: 0, and j is: 3
We see that break jumps out of and effectively stops both loops here. break
LABEL can be used, not only from a for- loop, but also to break out of a switch .
The name of a label is case-sensitive, it can be put in uppercase to increase
readability, but this is merely a convention.
Use of goto keyword #
In Go, there is a goto keyword, which has to be followed by a label name:
goto IDENTIFIER
Let’s look at an example:
package main
import "fmt"
func main() {
i:=0
HERE:
// adding a label for a location
fmt.Printf("%d",i)
i++
if i==5 {
return
}
goto HERE
// goto HERE
}
Control using goto
As you can see in the above code, we made a label HERE at line 6. At line 7, we
are printing the value of i and then incrementing i by 1. Next, we have a
condition at line 9: i==5 . If this condition is true, we’ll return from main.
Otherwise, control will go at line 12, which states: goto HERE , and from there,
control will transfer from line 12 to line 6. It’s evident that i will reach the
maximum value of 5 because, at i==5 , the program’s execution ends due to
the return statement.
See the following program and notice the problem:
func main() {
LABEL1:
// adding a label for location
for i := 0; i <= 5; i++ {
// outer loop
for j := 0; j <= 5; j++ {
// inner loop
if j == 4 {
goto LABEL1
// jump to the label
}
fmt.Printf("i is: %d, and j is: %d\n", i, j)
}
}
}
If you use goto you get an infinite loop that is stopped by the runtime after a
number of iterations. However, a backward goto quicky leads to unreadable
spaghetti-code and should not be used. There is always a better alternative.
Use the goto LABEL only with LABEL defined after the goto line! The use of
labels, and certainly goto, is discouraged because it can quickly lead to bad
program design, and the code can almost always be written more readable
without using them.
That’s it about how control is transferred using labels. There is a quiz in the
next lesson for you to solve.
Exercise: Deduce the Outputs
In this lesson, your concepts will be evaluated through a quiz.
Choose one correct answer.
1
What will the following function return for x=2 and y=5 ?
func isGreater(x, y int) bool {
if x > y {
return true
}
return false
}
COMPLETED 0%
1 of 15
We hope that you performed well in the quiz. That’s it about functions and
their uses in Golang. In the next chapter, we’ll discuss functions.
Introduction to Functions
This lesson discusses the introductory concepts of functions in Go before going into detail.
WE'LL COVER THE FOLLOWING
• Basics of a function in Go
• Purpose of a function
• Execution of a function
• Function call within a function call
• Function overloading
• Declaring function type
Functions are the basic building blocks of the Go code. They are very versatile,
so Go can be said to have a lot of characteristics of a functional language.
Functions are a kind of data because they are themselves values and have
types. Let’s start this chapter by elaborating on the elementary functiondescription, which we discussed briefly in Chapter 2.
Basics of a function in Go #
Every program consists of several functions. It is the basic code block. The
order in which the functions are written in the program does not matter.
However, for readability, it is better to start with main() and write the
functions in a logical order (for example, the calling order).
Purpose of a function #
The main purpose of functions is to break a large problem, which requires
breaking many code lines into a number of smaller tasks. Also, the same task
can be invoked several times, so a function promotes code reuse. In fact, a
good program honors the Don’t Repeat Yourself principle, meaning that the
code which performs a certain task may only appear once in the program.
Execution of a function #
A function ends when it has executed its last statement before }, or when it
executes a return statement, which can be with or without argument(s). These
arguments are the values that a function returns from their computation.
There are 3 types of functions in Go:
Normal functions with an identifier
Anonymous or lambda functions
Methods
Any of these can have parameters and return values.
The definition of all the function parameters and return values together with
their types is called the function signature. As a reminder, a syntax
prerequisite like:
func g()
{ // INVALID
...
}
is invalid Go-code. It must be:
func g() { // VALID
...
}
A function is called or invoked in code in a general format like:
pack1.Function(arg1,arg2,...,argn)
Function is a function in package pack1 , and arg1 , and so on are the
arguments. When a function is invoked, copies of the arguments are made,
and these are then passed to the called function.
The invocation happens in the code of another function called the calling
function. A function can call other functions as much as needed, and these
functions, in turn, can call other functions. This can go on with theoretically
no limit (unless the stack upon which these function calls are placed is
exhausted).
Here is the simplest example of a function calling another function without
needing arguments:
package main
import "fmt"
func main() { // main function started
fmt.Println("In main before calling greeting")
greeting() // greeting function invoked
fmt.Println("In main after calling greeting") // executed after greeting function
} // main function ended
// greeting function declared
func greeting() {
fmt.Println("In greeting: Hi!!!!!")
}
Calling Functions with no Parameters
As seen in the above code, the main function started on line 4. Line 5 will be
executed, and the message In main before calling greeting will be printed on
the screen. At line 7, a function greeting() is invoked. Now, control will
transfer to line 13, where the function greeting is created. Now, line 14 will
be executed, and the message In greeting: Hi!!! will be printed on the screen.
From line 15, control will move to line 9 right after the statement, where we
call the function greeting . Then, the message In main after calling greeting
will be printed on the screen.
Here is a slight variant of this example:
package main
import "fmt"
func main() {
lastName := "John"
fmt.Println("In main before calling greeting")
greeting(lastName) // greeting function invoked
fmt.Println("In main after calling greeting")
fmt.Println("variable lastName is still: ", lastName)
}
// greeting function declared
func greeting(name string) {
fmt.Println("In greeting: Hi!!!!!", name)
name = "Johnny"
fmt.Println("In greeting: Hi!!!!!", name)
}
Calling Functions with Parameters
As seen in the above code, the main function started on line 4. At line 5,
variable lastName is declared and initialized with a value John. Line 6 will be
executed, and the message In main before calling greeting will be printed on
the screen. At line 7, function greeting(lastName) is invoked.
Now control will transfer to line 13, where the function greeting is created.
Now, line 14 will be executed and message In greeting: Hi!!! John will be
printed on the screen. At line 15, the name is reassigned a value of Johnny.
Now, line 16 will be executed, and the message In greeting: Hi!!! Johnny will
be printed on the screen.
After line 16, control will move to line 8, right after the statement, where we
call the function greeting . The message In main after calling greeting will
be printed on the screen. Now, line 9 is executed, and the message variable
lastName is still: John will be printed because the value Johnny was assigned
to the copy of lastName in greeting scope. After exiting from greeting , the
variable lastName still has the value John.
Function call within a function call #
A function call can have another function call as its argument, provided that
the latter has the same number and types of arguments in the correct order
that the first function needs. For example, suppose f1 needs 3 int
arguments:
f1(a, b, c int)
and f2 returns 3 arguments:
f2(a, b int) (int, int, int)
then, this can be a call to f1 :
f1(f2(a, b))
Let’s implement this concept in the code below:
package main
import "fmt"
func f1(a,b,c int)int{
return a+b+c
}
// taking three parameters and returning their sum
func f2(a,b int)(int, int , int){ // taking two parameters and returning their sum, differenc
n1 := a+b
n2 := a-b
n3 := a*b
return n1,n2,n3
}
func main(){
fmt.Print(f1(f2(20,10)))
}
// function call within a function call
Function Call within a Function Call
In the code above, in the main function at line 16, due to the function call,
control will first transfer to the function f2() with a as 20 and b as 10. The
function f2 will return three arguments, which are sum, difference, and the
product of a and b . Now, control will transfer back to line 16. The values
returned by f2() are now the arguments of f1() . The function f1() will gain
control and return the sum of the values returned from f2() .
Function overloading #
Coding two or more functions in a program with the same function name but
a different parameter list and/or a different return-type(s) is called function
overloading. It is not allowed in Go. It gives the compiler error:
<funcName>redeclared in this block, previous declaration at <lineno> . The
main reason for this is that overloading functions force the runtime to do
additional type matching, which reduces performance. No overloading means
only a simple function dispatch is needed. Therefore, you need to give your
functions appropriate unique names, probably according to their signature.
To declare a function implemented outside Go, such as an assembly routine,
you simply give the name and signature with no body:
func flushICache(begin, end uintptr) // implemented externally
Functions can also be used in the form of a declaration, as a function type like
:
type binOp func(int, int) int
Declaring function type #
In that case, the body { } is also omitted. Functions are first-class values. They
can be assigned to a variable, like in:
add := binOp
The variable add gets a reference (points) to the function, and it knows the
signature of the function it refers to. It is not possible to assign a function to a
variable with a different signature. Like variables, functions have a zero
value, which is nil. Function values can be compared. They are equal if they
refer to the same function or if both are nil. A function cannot be declared
inside another function (no nesting), but this can be mimicked by using
anonymous functions, which we will study later.
That’s it about the introduction to functions. Now in the next lesson, we’ll see
how Golang, being a functional language, handles parameters and return
values.
Parameters and Return Values
This lesson will discuss the elements of functions like parameters and return values in detail.
WE'LL COVER THE FOLLOWING
• Introduction
• Parameters of a function
• Return from a function
• Call by value or call by reference
• Named return variables
• Blank identi er
• Changing an outside variable
• Passing a variable number of parameters
Introduction #
A function can take parameters to use in its code, and it can return zero or
more values (when more values are returned, one of the values often speaks
of a tuple of values). This is also a great improvement compared to C, C++,
Java, and C#, and it is particularly handy when testing whether or not a
function execution has resulted in an error (as we studied in Chapter 3).
Parameters of a function #
Parameters can be actual parameters or formal parameters. The key
difference between them is that actual parameters are the values that are
passed to the function when it is invoked, while formal parameters are the
variables defined by the function that receives values when the function is
called. Remember the greeting function from the previous lesson. We called
it using the following statement:
greeting(lastName)
And we implemented the function as:
func greeting(name string) {
println("In greeting: Hi!!!!!", name)
name = "Johnny"
println("In greeting: Hi!!!!!", name)
}
Here, lastName is the actual parameter, and name is the formal parameter.
Return from a function #
Returning value(s) is done with the keyword return . In fact, every function
that returns at least 1 value must end with return or panic (see Chapter 11).
The code after return in the same block is not executed anymore. If return is
used, then every code-path in the function must end with a return statement.
Note: A function with no parameters is called a niladic function, like
main.main() .
Call by value or call by reference #
The default way to call a function in Go is to pass a variable as an argument to
a function by value. A copy is made of that variable (and the data in it). The
function works with and possibly changes the copy; the original value is not
changed:
Function(arg1)
If you want Function to be able to change the value of arg1 itself (in place),
you have to pass the memory address of that variable with &; this is call
(pass) by reference:
Function(&arg1)
Effectively, a pointer is then passed to the function. If the variable that is
passed is a pointer, then the pointer is copied, not the data that it points to.
However, through the pointer, the function can change the original value.
Passing a pointer (a 32-bit or 64-bit value) is in almost all cases cheaper than
making a copy of the object. Reference types like slices (Chapter 5), maps
(Chapter 6), interfaces (Chapter 9) and channels (Chapter 12) are pass by
reference by default (even though the pointer is not directly visible in the
code).
Some functions just perform a task and do not return values. They perform
what is called a side-effect, like printing to the console, sending a mail, logging
an error, and so on. However, most functions return values, which can be
named or unnamed.
The following is a program of a simple function that takes 3 parameters and
returns a single value.
package main
import "fmt"
func main() {
fmt.Printf("Multiply 2 * 5 * 6 = %d\n", MultiPly3Nums(2, 5, 6))
// var i1 int = MultiPly3Nums(2, 5, 6)
// fmt.Printf("Multiply 2 * 5 * 6 = %d\n", i1)
}
func MultiPly3Nums(a, b, c int) int {
// var product int = a * b * c
// return product
return a * b * c
}
Simple Function
As you can see, we declare a function Multiply3Nums(a,b,c int) at line 9. The
function should return the product of three variables: a , b , and c . Now,
there are 2 ways to return the product. One way is to declare a variable
product and initialize it with a*b*c and then return the product . The second
way is to simply return a*b*c without using any variables. You may have
noticed, we implemented both methods but commented out the first method (
from line 10 to line 11). The second method is a one-line implementation (at
line 12).
Now, look at the main function. In main we have to print the product of the
numbers by calling the Multiply3Nums(a,b,c) function. There are 2 methods to
do so. One way is to declare a variable i1 , initialize it with
Multiply3Nums(a,b,c) and then print i1 . The second way is to simply print
Multiply3Nums(a,b,c) without using any variables. You may have noticed, we
implemented both methods but commented out the first method( from line 6
to line 7). The second method is a one-line implementation (at line 5).
Named return variables #
When there is more than 1 unnamed return variable, they must be enclosed
within (), like (int, int). Named variables used as result parameters are
automatically initialized to their zero-value, and once they receive their value,
a simple (empty) return statement is sufficient. Furthermore, even when
there is only 1 named return variable, it has to be put inside ().
package main
import "fmt"
var num int = 10
var numx2, numx3 int
func main() {
numx2, numx3 = getX2AndX3(num) // function call
PrintValues()
numx2, numx3 = getX2AndX3_2(num) // function call
PrintValues()
}
func PrintValues() {
fmt.Printf("num = %d, 2x num = %d, 3x num = %d\n", num, numx2, numx3)
}
func getX2AndX3(input int)(int, int) {
return 2 * input, 3 * input
}
func getX2AndX3_2(input int)(x2 int, x3 int) {
x2 = 2 * input
x3 = 3 * input
//return x2, x3
return
}
Multiple Returns
In the above program, there are two functions getX2AndX3 and getX2AndX3_2
that are enough to get the concepts of named return values. Look at line 17.
The return statement of getX2AndX3 is: return 2*input,3*input . This is an
example of the unnamed return values because we haven’t named any value.
Now look at line 19: func getX2AndX3_2(input int) (x2 int, x3 int) . Return
values ( x2 and x3 ) are explicitly named. Such naming allows you to use the
return statement simply (as we did at line 23). Or, you can use line 22 instead
of line 23; that is also a method to return named values.
Even with named return variables, you can still ignore the names and return
explicit values. When any of the result variables have been shadowed (not a
good practice!), the return-statement must contain the result variable names.
Use named return variables; they make the code clearer, shorter, and selfdocumenting.
Blank identi er #
The blank identifier _ can be used to discard values, effectively assigning the
right-hand-side value to nothing. Look at the following program to see how a
blank identifier works.
package main
import "fmt"
func main() {
var i1 int
var f1 float32
i1, _, f1 = ThreeValues() // blank identifier
fmt.Printf("The int: %d, the float; %f\n", i1, f1)
}
func ThreeValues()(int, int, float32) {
return 5, 6, 7.5
}
Blank Identifier
As you can see in the above code, there is a function at line 10 threeValues ,
that takes three parameters and returns them. Now let’s suppose we need two
return variables, not three. What to do now? The answer lies in using the
blank identifier. See line 7. We made three identifiers: i1 , _ , and f1 . This
means we need the first and last return value, and we want to ignore the
second return value. You can’t use - anywhere in the code. That is why we
only print i1 and f1 at line 8.
Changing an outside variable #
Passing a pointer to a function not only conserves memory because no copy of
the value is made, but it also has as a side-effect. The variable or object can be
changed inside the function so that the changed object doesn’t have to be
returned back from the function. See this in the following program. A pointer
to an integer is being changed in the function.
package main
import (
"fmt"
)
// this function changes reply:
func Multiply(a, b int, reply * int) {
* reply = a * b // side-effect(changing n)
}
func main() {
n := 0
reply := & n
fmt.Println("Before Multiplication:", n)
fmt.Println("Before Multiplication:", * reply)
Multiply(10, 5, reply)
fmt.Println("Multiply:", * reply) // Multiply: 50
fmt.Println("Multiply:", n) // Multiply: 50
}
Side-Effect
As you can see, at line 12 we declare a variable n and initialize with value 0.
In the next line, we declare a new pointer variable reply and store the
address of n in it. In the next two lines, we are printing the value of n using
value type(line 14) and reference type(line 15). You notice that both lines will
print 0. Let’s see side-effect.
At line 17, the Multiply function is called. Look at function header of
Multiply at line 7. It has three parameters; a , b and reply . The type of
reply is int* , which means it is a pointer variable. At line 8, we multiply a
and b and change the value at the address stored in reply . This is the sideeffect. Now, we have no need to return any variable from this function.
Control will transfer to line 18. In this line, we are printing value at the
address stored in reply . Similarly, at the next line, we are printing the value
n . Both the values are the same because n is the value that was stored at the
address placed in reply . The value of n is changed as a side-effect.
This is only a didactic example, but it is much more useful to change a large
object inside a function. However, this technique obscures a bit of what is
going on. The programmer should be very much aware of this side-effect and
if necessary, make it clear to users of the function through a comment.
Passing a variable number of parameters #
If the last parameter of a function is followed by …type, this indicates that the
function can deal with a variable number of parameters of that type, possibly
also 0, a so-called variadic function:
func myFunc(a, b, arg ...int) {}
Consider the function:
func Greeting(prefix int, who ...string)
and the function call:
Greeting(4, "Joe", "Anna", "Eileen")
Here, Greeting takes an input of the integer 4 and a slice (which we’ll cover in
the next chapter) who that is equal to []string{"Joe", "Anna", "Eileen"} .
The following is an example of a program implementing a function that takes
a variable number of parameters.
package main
import "fmt"
func main() {
Print(1, 3, 2, 0)
}
func Print(a...int) { // variable number of parameters
if len(a) == 0 {
return
}
for _, v := range a {
fmt.Printf("The number is: %d\n", v)
}
return
}
Variable Number of Parameters
As you can see, we implemented a function at line 8 called Print that takes
the variable number of parameters to get printed. Now, there can be a case
where the user doesn’t pass any parameter. For this, we simply return from
the function at line 10. If the parameters list is not empty, we print the
parameters using the range function at line 14.
Now, you are familiar with arguments and how functions return values. In
the next lesson, you have to write a function to solve a problem.
Challenge: Multiple Return Values
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Input
• Output
• Sample input
• Sample output
Problem statement #
Write a function that accepts two integers and returns their sum, product and
difference (in the same order). Make a version with named return variables.
Input #
Two numbers of type int
Output #
Sum, product, and difference between two numbers
Sample input #
3, 4
Sample output #
7,12,-1 // 3+4=7, 3*4=12, and 3-4=-1
Try to implement the function below. Feel free to view the solution, after
giving some shots. Good Luck!
package main
import "fmt"
import "strconv"
import "encoding/json"
func SumProductDiff(i, j int)(s int, p int, d int) {
// named version
return
}
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Multiple Return Values
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"fmt"
)
func SumProductDiff(i, j int)(s int, p int, d int) {
s, p, d = i + j, i * j, i - j
return
}
func main() {
sum, prod, diff := SumProductDiff(3, 4)
fmt.Println("Sum:", sum, "| Product:", prod, "| Diff:", diff)
}
Multiple Return Values
In the code above, at line 6, there is a function header: SumProductDiff(i, j
int)(s int, p int, d int) . It means that SumProductDiff takes two integer
parameters i and j and returns three values: s (sum of i and j ), p
(product of i and j ), and d (difference between i and j ). At line 7, we are
calculating the sum as s=i+j , product as p=i*j and difference as d=i-j . Now,
look at the main . At line 12, we are calling SumProductDiff for values 3 and 4,
and storing return values in sum , product and difference variables. At line
13, we are printing the variables to verify the results.
That’s it about the solution. In the next lesson, you’ll be attempting another
challenge.
Challenge: Variable Number of Arguments
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Input
• Output
• Sample input
• Sample output
Problem statement #
Make a function that returns the sum of variable number of integer
arguments.
Input #
Series of n integers (n
= 0, 1, 2, 3, ...)
Output #
Sum of the given n integers
Sample input #
5,-2,0,9
Sample output #
12
Try to implement the function below. Feel free to view the solution, after
giving some shots. Good Luck!
package main
import "strconv"
import "encoding/json"
import "math"
import "fmt"
func sumInts(list ...int) (sum int){
return
}
Variable Arguments
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Variable Number of Arguments
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import "fmt"
func main() {
// function calls for multiple arg.
fmt.Println(sumInts())
fmt.Println(sumInts(2, 3))
fmt.Println(sumInts(0, 1, 2, 3, 4, 5, 6, 7, 8, 9))
}
func sumInts(list ...int) (sum int){
for _, v := range list {
sum = sum+v
}
return
}
// function to calculate sum of variable arg.
Variable Arguments
In the code above, at line 11 there is a function header: sumInts(list ...int)
(sum int) which means that sumInts takes a variable number of parameters
and returns an integer sum . Then, we have a for loop at line 12, where we
iterate over the list of these parameters and start adding them together as:
sum=sum+v and returning sum at line 15. Now, look at the main . At line 6, line
7 and line 8 we are calling sumInts for different values and printing the
output to verify the results.
That’s it about the solution. In the next lesson, you’ll study the concept of
deferring and tracing functions.
Defer and Tracing
This lesson discusses important concepts of deferring and tracing functions in Go.
WE'LL COVER THE FOLLOWING
• The defer keyword
• Tracing with defer
• Logging parameters with defer
The defer keyword #
The defer keyword allows us to postpone the execution of a statement or a
function until the end of the enclosing (calling) function. Defer executes
something (a function or an expression) when the enclosing function returns.
This happens after every return, even when an error occurs in the midst of
executing the function, not only a return at the end of the function, but before
the }. But why after every return? This happens because the return statement
itself can be an expression that does something instead of only giving back 1
or more variables. The defer resembles the finally-block in OO-languages as
Java and C#; in most cases, it also serves to free up allocated resources.
However, keep in mind that a defer-call is function scoped, while a finally-call
is block scoped.
Run the following program to see how defer works.
package main
import "fmt"
func main() {
Function1()
}
func Function1() {
fmt.Printf("In Function1 at the top\n")
defer Function2() // function deferred, will be executed after Function1 is done.
fmt.Printf("In Function1 at the bottom!\n")
}
func Function2() {
fmt.Printf("Function2: Deferred until the end of the calling function!")
}
Deferring
As you can see in the above code, in main we call Function1 at line 5, so
control will transfer to line 8. Line 9 will be executed, and In Function1 at
the top will be printed on the screen. At line 10, we defer calling Function2()
with the statement: defer Function2() . Due to the defer keyword, control will
not transfer to Function2 . Then, line 11 will be executed, and In Function1 at
the bottom! will be printed. Now, all the statements other than defer
Function2() are executed, and Function1 will return. Function2 will be
executed, and Function2: Deferred until the end of the calling function!
will be printed. Now, control will go back to main at line 6, and the program
will terminate.
When many defer’s are issued in the code, they are executed at the end of the
function in the inverse order (like a stack or LIFO), which means the last defer
is first executed, and so on. This is illustrated in the following code:
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
}
Deferred Order of Execution
The defer allows us to guarantee that certain clean-up tasks are performed
before we return from a function, for example:
Closing a file stream
Unlocking a locked resource (a mutex)
Printing a footer in a report
Closing a database connection
The defer can be helpful to keep the code cleaner and often shorter.
Tracing with defer #
A primitive but sometimes effective way of tracing the execution of a program
is printing a message when entering and leaving certain functions. This can
be done with the following 2 functions:
func trace(s string) { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }
The following program demonstrates how the untrace function is called with
the defer keyword.
package main
import "fmt"
func trace(s string) {
fmt.Println("entering:", s)
} // entering func.
func untrace(s string) {
fmt.Println("leaving:", s)
} // leaving func.
func a() {
trace("a")
defer untrace("a") // untracing via defer
fmt.Println("in a")
}
func b() {
trace("b")
defer untrace("b") // untracing via defer
fmt.Println("in b")
a()
}
func main() {
b()
}
Tracing with defer
In the above code, the entry point is at line 24, where the main function is
written. We are calling b at line 25. Now control will transfer to line 17. The
trace function will be called at line 18, and entering: b will be printed on
screen. Now, we are deferring the untrace function at line 19, which means
that line 19 will be executed after the b function returns. Line 20 will gain
control, and in b will be printed. At line 21, we are calling the function a .
Control will transfer to line 11. The trace function will be called at line 12,
and entering: a will be printed on screen. Now, we are deferring untrace
function at line 13, which means that line 13 will be executed after a returns.
Now, function a will return, and the untrace function will gain control. The
message leaving: a will be printed. Control will go back to b , and it will
return and untrace will gain control. The message leaving: b will be printed.
Finally, from there, control will go back to main at line 26, and the program
will terminate.
Logging parameters with defer #
This is another possible use of defer , which might come in handy while
debugging. Look at the following program.
package main
import (
"log"
"io"
)
func func1(s string)(n int, err error) {
defer func() {
log.Printf("func1(%q) = %d, %v", s, n, err)
}()
return 7, io.EOF
}
func main() {
func1("Go")
}
Logging Parameters with defer
As you can see, in main at line 17, we call a function func1 with a parameter
Go. Control will go to line 7. Then at line 9, we defer func function so that the
execution of this line will be suspended. Control will transfer directly to line
13, and func1 will return named values: n of type int and err of type error .
Now again, control will go to line 9. Where line func1(“Go”)=7, EOF will be
printed because s is Go, n is 7, and err is EOF (End Of File). Finally, the
control will go back to main at line 18, and the program will terminate.
That’s it about defer and tracing. Now, you’ll study some built-in functions.
Built-in Functions
This lesson describes some built-in functions of Go and explains their tasks.
WE'LL COVER THE FOLLOWING
• Introduction
• Explanation
• close
• len and cap
• new and make
• copy and append
• panic and recover
• print and println
• complex , real , and imag
Introduction #
The predefined functions, which can be used without having to import a
package to get access to them, are called built-in functions. They sometimes
apply to different types, e.g. len , cap , and append , or they have to operate
near system level like panic . That’s why they need support from the compiler.
Explanation #
Note: We’ll only list the name of the functions and their functionalities in
this lesson. Some of the functions are covered before in detail and used
multiple times, so we won’t run them from scratch. Functions that were
not discussed previously, their running examples will be provided later
in the course in detail, section by section. So don’t panic if you don’t have
a clear idea.
The following are some common and important built-in functions provided by
Go:
close #
It is used in channel communication.
len and cap #
The function len gives the length of a number of types (strings, arrays, slices,
maps, channels). Whereas, cap is the capacity, the maximum storage (only
applicable to slices and maps).
new and make #
Both new and make are used for allocating memory. The function new is used
for value types and user-defined types like structs. Whereas, make is used for
built-in reference types (slices, maps, channels). They are used like functions
with the type as its argument:
new(type)
make(type)
new(T) allocates zeroed storage for a new item of type T and returns its
address. It returns a pointer to the type T (details are in Chapter 8), and it can
be used with primitive types as well:
v := new(int) // v has type *int
The function make(T) returns an initialized variable of type T , so it does more
work than new .
Remark: new() is a function; don’t forget its parentheses.
copy and append #
These are used for copying and concatenating slices (see Chapter 5).
panic and recover #
These both are used in a mechanism for handling errors (see Chapter 11).
print and println #
These are low-level printing functions. Use the fmt package in production
programs.
complex , real , and imag #
These are used for making and manipulating complex numbers.
Note: We won’t be covering complex() , real() , and imag() anywhere
because they don’t fall within the scope of this course. If you want to
study them in detail, you can follow their documentation. For complex()
click here. For real() click here. For imag() click here.
Built-in functions are never hard to understand. We can always learn them
when we feel a need to use them. You just need to call them and see what they
do. The next lesson brings an important function type known as recursive
function.
Recursive Functions
WE'LL COVER THE FOLLOWING
• Recursion
• Recursive functions in Go
• Mutually recursive function
Recursion #
Sometimes, the solution for bigger instances depends on the solution of
smaller instances. A function calls itself for smaller instances until the
problem is solved. This approach is called recursion.
Recursive functions in Go #
A function that calls itself in its body is called a recursive function. The
proverbial example is the calculation of the numbers of the Fibonacci
sequence, in which each number is the sum of its two preceding numbers. The
sequence starts with:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584,
4181, 6765, 10946, ...
How about programming this problem? Consider a number 5, let’s see how
recursive functions solve this problem. When we break the problem, we can
visualize that:
Fi(5) = Fib(4)+Fib(3)
Fi(4) = Fib(3)+Fib(2)
Fib(3) = Fib(2)+Fib(1)
Fib(2) = Fib(1)+Fib(0)
Fib(1) = 1
Fib(0) = 1
This is a recursive operation. Before coding, look at the illustration to
understand in a better way.
Fib(5)
Fib(4)
Fib(3)
Calculating Fibonacci Sequence
1 of 22
Fib(5)
Fib(4)
Fib(3)
Fib(2)
Fib(1)
Calculating Fibonacci Sequence
2 of 22
Fib(5)
Fib(4)
Fib(3)
Fib(2)
1
Calculating Fibonacci Sequence
3 of 22
Fib(5)
Fib(4)
Fib(3)
Fib(2)
Fib(1)
1
Fib(0)
Calculating Fibonacci Sequence
4 of 22
Fib(5)
Fib(4)
Fib(3)
Fib(2)
Fib(1)
1
1
Calculating Fibonacci Sequence
5 of 22
Fib(5)
Fib(4)
Fib(3)
Fib(2)
1
1
1
Calculating Fibonacci Sequence
6 of 22
Fib(5)
Fib(4)
Fib(3)
2
1
1
1
Calculating Fibonacci Sequence
7 of 22
Fib(5)
Fib(4)
3
2
1
1
1
Calculating Fibonacci Sequence
8 of 22
Fib(5)
Fib(3)
Fib(4)
3
Fib(2)
2
1
1
1
Calculating Fibonacci Sequence
9 of 22
Fib(5)
Fib(4)
3
Fib(3)
Fib(2)
2
1
Fib(1)
Fib(0)
1
1
Calculating Fibonacci Sequence
10 of 22
Fib(5)
Fib(3)
Fib(1)
Fib(4)
3
Fib(2)
2
1
1
1
1
Calculating Fibonacci Sequence
11 of 22
Fib(5)
Fib(3)
1
Fib(4)
3
Fib(2)
2
1
1
1
1
Calculating Fibonacci Sequence
12 of 22
Fib(5)
Fib(4)
3
Fib(3)
2
2
1
1
1
1
1
Calculating Fibonacci Sequence
13 of 22
Fib(5)
Fib(4)
3
2
2
1
1
1
Fib(3)
Fib(2)
Fib(1)
1
1
Calculating Fibonacci Sequence
14 of 22
Fib(5)
Fib(4)
3
2
2
1
1
1
Fib(3)
Fib(2)
1
1
1
Calculating Fibonacci Sequence
15 of 22
Fib(5)
Fib(4)
3
2
2
1
1
1
Fib(3)
Fib(2)
Fib(1)
1
1
1
Fib(0)
Calculating Fibonacci Sequence
16 of 22
Fib(5)
Fib(4)
3
2
2
1
1
1
Fib(3)
Fib(2)
Fib(1)
1
1
1
1
Calculating Fibonacci Sequence
17 of 22
Fib(5)
Fib(4)
3
2
2
1
1
1
Fib(3)
Fib(2)
1
1
1
1
1
Calculating Fibonacci Sequence
18 of 22
Fib(5)
Fib(4)
3
2
2
1
1
1
Fib(3)
2
1
1
1
1
1
Calculating Fibonacci Sequence
19 of 22
Fib(5)
Fib(4)
3
2
2
1
1
1
3
2
1
1
1
1
1
Calculating Fibonacci Sequence
20 of 22
Fib(5)
3
2
1
1
1
5
3
2
2
1
1
1
1
1
Calculating Fibonacci Sequence
21 of 22
8
3
2
1
1
1
5
3
2
2
1
1
1
1
1
Calculating Fibonacci Sequence
22 of 22
Now, we are ready to write a program. See the following implementation.
package main
import "fmt"
func main() {
result := 0
for i := 0; i <= 10; i++{
result = fibonacci(i) // function call
fmt.Printf("fibonacci(%d) is: %d\n", i, result)
}
}
// Function to calculate the nth fibonacci number
func fibonacci(n int)(res int) {
if n <= 1 {
res = 1
} else {
res = fibonacci(n - 1) + fibonacci(n - 2) // recursive call
}
return
}
Fibonacci Sequence
In main , we are calling function fibonacci for numbers less than and equal to
10 using a for loop at line 6 and printing the result for every number. See the
fibonacci function. We always need a base and a recursive case to implement
any recursive function. From the illustration above, we know that when the
number is 1 or 0, we stop the calls and set the value to 1. This is the base case.
The following code implementats the base case. :
if n <= 1 {
res = 1
}
Then, we have a recursive case. You may have noticed the pattern. The nth
number in the Fibonacci sequence is equal to:
fibonacci(n) = fibonacci(n-1) + fibonacci(n-2)
This pattern is implemented at line 17. We start solving this problem from
smaller instances, which in turn solve problems for bigger instances.
An important problem when using recursive functions is stack overflow. This
can occur when a large number of recursive calls are needed, and the
program runs out of allocated stack memory. This can be solved by using a
technique called lazy evaluation, implemented in Go with a channel, and a
goroutine (see Chapter 12).
Mutually recursive function #
Mutually recursive functions can also be used in Go. These are functions that
call one another. Because of the Go compilation process, these functions may
be declared in any order. Here is a simple example:
package main
import (
"fmt"
)
func main() {
fmt.Printf("%d is even: is %t\n", 16, even(16)) // 16 is even: is true
fmt.Printf("%d is odd: is %t\n", 17, odd(17)) // 17 is odd: is true
fmt.Printf("%d is odd: is %t\n", 18, odd(18)) // 18 is odd: is false
}
func even(nr int) bool {
if nr == 0 {
return true
}
return odd(RevSign(nr) - 1) // even calls odd
}
func odd(nr int) bool {
if nr == 0 {
return false
}
return even(RevSign(nr) - 1) // odd calls even
}
func RevSign(nr int) int {
if nr < 0 {
return -nr
}
return nr
}
Mutually Recursive Functions
To find out whether a number is even or odd, we can use the modulus
operator, but we are instead solving the problem through mutually recursive
functions.
As you can see in the above code, at line 7 within main , we are calling a
function even , and passing 16 as a parameter. In function even , we check that
the number nr is 0 or not at line 13. If it’s 0, then we return true . Otherwise,
we call the RevSign function. RevSign returns the absolute value of the
number.
Control goes back to line 16. Now, we subtract 1 from the value returned by
RevSign . The result will go as a parameter to the odd function now. In
function odd , we check that the number nr is 0 or not at line 20. If it’s 0, then
we return false. Otherwise, we call the RevSign function. RevSign checks that
if nr is less than 0, it returns -nr . Otherwise, it returns nr .
Control goes back to line 23. Now, the result will go as a parameter to the
even function. The same process will go on and on until nr is 0. The moment
nr is zero, we can make a decision whether the number was even or not.
Now that you are familiar with recursion in Go, attempt a challenge to
evaluate your understanding.
Challenge: Compute Factorial of a Number
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Input
• Output
• Sample input
• Sample output
Problem statement #
Write a function that returns the factorial (!) of any n number. The factorial
n! of a number n is defined as:
n! = n * (n – 1)! , where 0!=1 and 1!=1
For example: 5!
= 5 ∗ 4 ∗ 3 ∗ 2 ∗ 1 = 120
Input #
A number of type uint64, n
Output #
A number of type uint64 (because the number increases drastically due to
multiplication)
Results will be correct until only 21! . After that, the overflow-error will occur.
Remark: When using type int the calculation is only correct up until
12! . This is, of course, because an int can only contain integers that fit in
32 bits. And, Go doesn’t usually warn against this overflow-error!
Sample input #
10
Sample output #
3628800
Try to implement the function below. Feel free to view the solution, after
giving some shots. Good Luck!
package main
import "fmt"
import "strconv"
import "encoding/json"
func Factorial(n uint64) (fac uint64) {
return
}
Factorial of a Number
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Compute Factorial of a Number
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"fmt"
)
func main() {
for i := uint64(0); i < uint64(22); i++ {
fmt.Printf("Factorial of %d is %d\n", i, Factorial(i)) // calculating factori
}
}
// named return variables:
func Factorial(n uint64) (fac uint64) {
if n<=1{
//base case
return 1
}
fac = n * Factorial(n-1)
// recursive case
return
}
Factorial of a Number
In main , we are calling function Factorial for numbers between 0 and 21
inclusively using for loop at line 7, and printing the result for every
number. See the Factorial function. We always need a base and recursive
case to implement the recursive function. We know that when the number is
less than or equal to 1, we stop the cycle and set the value to 1. This is the base
case. The following code implements the base case:
if n<=1{ //base case
return 1
}
Next, we have a recursive case (for n >1). You may have noticed the pattern.
The factorial of a number n is equal to :
Factorial(n) = n * Factorial(n-1)
This pattern is implemented at line 17. We start solving this problem from
smaller instances, which in turn solve problems for bigger instances.
That’s it about the solution. In the next lesson, you’ll study higher-order
functions.
Higher-Order Functions
This lesson discusses how to use functions as parameters and values.
WE'LL COVER THE FOLLOWING
• Function used as a value
• Function used as a parameter
• Function used as a lter
Function used as a value #
Functions can be used as values just like any other value in Go. In the
following code, f1 is assigned a value, the function inc1 :
func inc1(x int) int { return x+1 }
f1 := inc1(2) // f1 := func (x int) int { return x+1 }
Function used as a parameter #
Functions can be used as parameters in another function. The passed function
can then be called within the body of that function; that is why it is commonly
called a callback. To illustrate, here is a simple example:
package main
import (
"fmt"
)
func main() {
callback(1, Add) // function passed as a parameter
}
func Add(a, b int) {
fmt.Printf("The sum of %d and %d is: %d\n", a, b, a + b)
}
func callback(y int, f func(int, int)) {
f(y, 2) // this becomes Add(1, 2)
}
Functions as a Parameter
To understand the code, look at line 10 at the function header of the function
Add . It takes two parameters a and b and prints the sum of the parameters.
In the main , we are just calling the callback function as: callback(1, Add) .
Here, we have two parameters: the first is 1 used as int and the second is Add
function passed as a parameter. You can verify it through the header of the
callback function: func callback(y int, f func(int, int)) at line 14. At line
15, we call the function f from the header of callback , and treat it like the
Add function. So y in callback is a in Add , and 2 is b in Add .
A good example of the use of a function as a parameter is the
strings.IndexFunc() function. It has the signature:
func IndexFunc(s string, f func(c int) bool) int
and returns the index into s of the first Unicode character for which f(c) is
true, or -1 if none will do.
For example, strings.IndexFunc(line, unicode.IsSpace) will return the index
of the 1st whitespace character in line.
package main
import "fmt"
import "strings"
import "unicode"
func main(){
s := "Hello! Let's run Go lang"
//finding index of first space from s string
fmt.Print(strings.IndexFunc(s, unicode.IsSpace))
}
Index of First Whitespace
Function used as a lter #
In the following program, we have a filter function which takes a slice of
integers and a function that takes an integer and returns a bool. Slices are a
key data type in Go, giving a more powerful interface to sequences than
arrays. You’ll study them in detail in Chapter 5. For a brief introduction of
slices, you can visit A Tour of Go.
package main
import "fmt"
type flt func(int) bool
// isOdd takes an int slice and returns a bool set to true if the
// int parameter is odd, or false if not.
// isOdd is of type func(int) bool which is what flt is declared to be.
func isOdd(n int) bool {
if n % 2 == 0 {
return false
}
return true
}
// Same comment for isEven
func isEven(n int) bool {
if n % 2 == 0 {
return true
}
return false
}
func filter(sl[] int, f flt)[] int {
var res[] int
for _, val := range sl {
if f(val) {
res = append(res, val)
}
}
return res
}
func main() {
slice := [] int {1, 2, 3, 4, 5, 7}
fmt.Println("slice = ", slice)
odd := filter(slice, isOdd)
fmt.Println("Odd elements of slice are: ", odd)
even := filter(slice, isEven)
fmt.Println("Even elements of slice are: ", even)
}
Function Filter
The program has two basic functions. The first function, isOdd takes n as a
parameter and returns a boolean value (see its header at line 9). If n is odd, it
will return true. Otherwise, it will return false. Similarly, the second function,
isEven takes n as a parameter and returns a boolean value (see its header at
line 17). If n is even, it will return true. Otherwise, it will return false.
At line 4, we are aliasing a type. A function that takes a single integer as a
parameter and returns a single boolean value is given a type flt . Now,
moving towards a major part of the program: the filter function. See its
header at line 24. It takes a slice of integers (that are to be judged as even or
odd) as a first parameter, and function f of type flt (either isEven or
isOdd ). The function filter returns a slice of integers res , for which the f
returns true.
Let’s see the main function now. At line 35, we declare a slice of integers
named slice . Then at line37, we call the filter function with slice as the
first parameter and isOdd as the second parameter and store the result in odd
slice. Similarly, at line39, we call the filter function with slice as the first
parameter and isEven as the second parameter and store the result in the
even slice. Printing odd and even slices at line 38 and line 40, respectively,
verifies the result. The same principle can be applied to construct filters for
any type.
This is how you use a function as a value or a parameter. The next lesson
brings a challenge for you to solve.
Challenge: Filter Even and Odd Numbers
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Input
• Output
• Sample input
• Sample output
Problem statement #
Rework the example from the previous lesson so that you only need the
isEven function and the filter function that returns the even and odd slices
together.
Input #
A single slice of integers.
Output #
Two slices of integers: one having only odd numbers and second having only
even numbers
Sample input #
{1,2,3,4,5,7}
Sample output #
[1,3,5,7] // odd integers
[2,4]
// even integers
Try to implement the function below. Feel free to view the solution, after
giving some shots. Good Luck!
package main
import "fmt"
import "strconv"
import "encoding/json"
type flt func(int) bool // aliasing type
func isEven(n int) bool { // check if n is even or not
return true
}
func filter(sl[] int, f flt)(yes, no[] int) { // split s into two slices: even and odd
return
}
Filter Even and Odd
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Filter Even and Odd Numbers
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import "fmt"
type flt func(int) bool // aliasing type
func isEven(n int) bool { // check if n is even or not
if n % 2 == 0 {
return true
}
return false
}
func filter(sl[] int, f flt)(yes, no[] int) { // split s into two slices: even and odd
for _, val := range sl {
if f(val) {
yes = append(yes, val)
} else {
no = append(no, val)
}
}
return
}
func main() {
slice := [] int {1, 2, 3, 4, 5, 7}
fmt.Println("slice = ", slice)
even,odd := filter(slice, isEven)
fmt.Println("The even elements of slice are: ", even)
fmt.Println("The odd elements of slice are: ", odd)
}
Solution
The program above has one basic function. The function isEven takes n as a
parameter and returns a boolean value (see its header at line6). If n is even, it
will return true; otherwise, it will return false. At line 4, we are aliasing a
type. A function that takes a single integer as a parameter and returns a single
boolean value is given a type flt .
Now, we move towards a major part of the program: filter function. See its
header at line 13. It takes a slice of integers (that are to be judged as even or
odd) as a first parameter and function f of type flt ( isEven ). The function
filter returns two slices of integers yes (integers that are even) and no
(integers that are odd).
Let’s see the main function now. At line 25, we declare a slice of integers
named slice . Then, at line27, we call the filter function with slice as the
first parameter and isEven as the second parameter and store the result in
the even and odd slices. Printing even and odd slices at line 28 and line 29,
respectively, verifies the result.
That’s it about the solution. In the next lesson, you’ll study how to write a
closure in a program.
Closures
This lesson covers the important concept of naming functions via closures.
WE'LL COVER THE FOLLOWING
• Using function literals
• The defer keyword and lambda functions
Using function literals #
Sometimes, we do not want to give a function a name. Instead, we can make
an anonymous function (also known as a lambda function, a function literal,
or a closure), for example:
func(x, y int) int { return x + y }
Such a function cannot stand on its own (the compiler gives the error: nondeclaration statement outside function body ), but it can be assigned to a
variable which is a reference to that function:
fplus := func(x, y int) int { return x + y }
Then it can be invoked as if fplus was the name of the function:
fplus(3,4)
or it can be invoked directly:
func(x, y int) int { return x + y } (3, 4)
Here is a call to a lambda function, which calculates the sum of integer floats
till 1 million. The gofmt reformats the lambda function in this way:
func() {
sum = 0.0
for i := 1; i<= 1e6; i++ {
sum += i
}
}()
The first ( ) is the parameter-list, and it follows immediately after the keyword
func because there is no function name. The { } comprise the function body,
and the last pair of ( ) represent the call to the function.
Here is an example of assigning a function literal to a variable:
package main
import "fmt"
func main() {
f()
}
func f() {
for i := 0; i < 4; i++{
g := func(i int) {fmt.Printf("%d ", i)} // g assigned to the function literal
g(i) // g used as a function
fmt.Printf(" - g is of type %T and has value %v\n", g, g)
}
}
Function Literal Assignment
In the above code, in main at line 5, we call a function f . At line 8, we declare
f . There is a for loop iterating 4 times. In that loop, we declare an anonymous
function taking the i iterator as a parameter and printing the value. The
function is assigned to variable g . In the next line (line 11) we called g as the
function literal we declared at line 10. This g will now perform the task
expected by the closure to perform at line 10. This makes it clear that the type
of g is func(int) , and its value is the address where it’s placed in memory.
You can verify the type of such a function too. Look at the following program.
package main
import (
"fmt"
)
func main() {
fv := func() {
fmt.Println("Hello World!")
}
fv()
fmt.Printf("The type of fv is %T", fv)
}
Lambda Value
In the above code, in main() , line 7 defines a lambda function and assigns it
to a variable fv , which prints Hello World!. Line 10 calls the function with
fv , causing Hello World! to be printed on screen. In the next line (line 11),
type of fv ( func() ) using %T format specifier is printed.
Anonymous functions, like all functions, can be with or without parameters.
In the following example, there is a value v passed as a parameter to u :
func (u string) {
fmt.Println(u)
...
}(v)
The defer keyword and lambda functions #
The defer keyword is often used with lambda functions. It can then also
change return values, provided that you are using named result parameters.
Lambda functions can also be launched as goroutines with go (see Chapter
12).
Lambda functions are also called closures (a term from the theory of
functional languages). They may refer to variables defined in a surrounding
function. A closure is a function that captures some external state, for
example, the state of the function in which it is created. Another way to
formulate this is by using a closure that inherits the scope of the function in
which it is declared. That state (those variables) is then shared between the
surrounding function and the closure, and the variables survive as long as
they are accessible.
Closures are often used as wrapper functions. They predefine 1 or more of the
arguments for the wrapped function. Another useful application is using
closures in performing clean error-checking.
Now that we know what closures are, let’s see how a function can be returned
from another function using closures.
Functions as Return Variables
This lesson discusses how to use a function as a return variable or value.
WE'LL COVER THE FOLLOWING
• Returning a function using closures
• Factory Function
Returning a function using closures #
Just like we return a variable or a value from a function, we can return a
function too. The following function genInc returns a function:
// genInc creates an "increment n" function
func genInc(n int) func(x int) int {
return func(x int) int {
return x+n
}
}
It’s obvious from the header of genInc , that it’s returning a function that takes
a parameter x of type int , and that function returns an int value. The
function returned by genInc returns x+n . The following program is an
implementation of returning a function.
package main
import "fmt"
func main() {
// make an Add2 function, give it a name p2, and call it:
p2 := Add2()
fmt.Printf("Call Add2 for 3 gives: %v\n", p2(3))
// make a special Adder function, a gets value 3:
TwoAdder := Adder(2)
fmt.Printf("The result is: %v\n", TwoAdder(3))
}
func Add2() (func(b int) int) {
// return a function
return func(b int) int {
return b + 2
}
}
func Adder(a int) (func(b int) int) {
return func(b int) int {
return a + b
}
}
// return a function
Return Function
In the above program, we implement two functions Add2 and Adder , which
return another lambda function. There is a header of Add2 at line 13. You can
notice that it returns an anonymous function that takes b (of type int ) as a
parameter and returns an int value. That anonymous function is returning
b+2 . Similarly, there is a header Adder at line 18. You can notice that it takes a
parameter a (of type int ), and it also returns a closure that takes b (of type
int ) as a parameter and returns an int value. That closure is returning a+b .
Now, look at line 6 in main . We are calling Add2 and setting it equal to p2 .
Then, we call p2(3) at line 7, which calls the closure returned by Add2 . So b
of Add2 is equal to 3. On returning b+2 , 5 will be printed. Similarly, look at
line 9 in main . We are calling Adder(2) and setting it equal to TwoAdder . Then,
we call TwoAdder(3) at line 10, which calls the closure returned by Adder . So
b of Adder is equal to 3, and a of Adder is equal to 2. On returning a+b , 5 will
be printed.
Here is (nearly) the same function used in a slightly different way:
package main
import "fmt"
func main() {
var f = Adder()
fmt.Print(f(1), " , ")
fmt.Print(f(20), " , ")
fmt.Print(f(300))
}
func Adder() func(int) int {
var x int
return func(delta int) int {
x += delta
return x
}
}
Function Closure
In the above program, we implement function Adder which returns another
lambda function. There is a header of Adder at line 11. You can notice that it
returns an anonymous function that takes an int parameter and returns an
int value. We declare an int variable x in Adder at line 12, and that
anonymous function returns the modified value of x , after adding its
parameter delta into x .
Now, look at line 5 in main . Adder() is now assigned to the variable f (which
is then of type func(int) int ). In the calls to f, delta in Adder() gets the
values 1, 20 and 300 from line 6, line 7 and line 8, respectively. We see that
between the calls of f the value of x is retained, first it is: 0 + 1 = 1, then it
becomes 1 + 20 = 21, then 21 is added to 300 to give the result 321. The lambda
function stores and accumulates the values of its variables. It still has access
to the (local) variables defined in the current function.
Closures help in returning a function as a variable. This approach can also
convert a recursive approach to a non-recursive one. How about writing a
non-recursive version of the Fibonacci program using a function as a closure?
package main
import "fmt"
// fib returns a function that returns
// successive Fibonacci numbers.
func fib() func() int {
a, b := 1, 1
return func() int {
a, b = b, a + b
return b
}
}
func main() {
f := fib()
// Function calls are evaluated left-to-right.
// fmt.Println(f(), f(), f(), f(), f())
for i := 0; i <= 9; i++{
fmt.Println(i + 2, f())
}
}
Fibonacci Closure
In the program above, we implement function fib , which returns another
lambda function. In the header of fib at line 6, notice that it returns an
anonymous function that takes nothing as a parameter and returns an int
value. We declare an int variable a and b in fib at line 7 and initialize
both of them with 1. That anonymous function returns the modified value of
b , after adding a into b . Now, look at line 15 in main . The function fib() is
now assigned to the variable f (which is then of type func() int ). At line 18,
there is a for loop that iterates 10 times. In each iteration, we call f() . This
means the first 10 Fibonacci values will be printed, excluding values of 0 and
1. Let’s follow the first 5 iterations.
In the first iteration, a and b are declared and initialized with 1. Now,
the lambda function makes b equal to a+b (which is 2) and a equal to b
(which is 1). The value 2 will be returned as b holds the Fibonacci value
at a position and a holds previous Fibonacci value.
In the second iteration, a and b will hold their values from the previous
iteration. Now, the function makes b equal to a+b (which is 3) and a
equal to b (which is 2). The value b will be returned which is 3.
In the third iteration, the function makes b equal to a+b (which is 5) and
a equal to b (which is 3). The value b will be returned which is 5.
In the fourth iteration, the function makes b equal to a+b (which is 8) and
a equal to b (which is 5). The value b will be returned which is 8.
In the fifth iteration, the function makes b equal to a+b (which is 13) and
a equal to b (which is 8). The value b will be returned which is 13.
The same pattern will be followed for the rest four iterations.
Factory Function #
A function that returns another function can be used as a factory function.
This can be useful when you have to create a number of similar functions:
write 1 factory function instead of writing them all individually. The following
function illustrates this:
func MakeAddSuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
MakeAddSuffix is returning functions that add a suffix to a filename when this
is not yet present. Now, we can make functions like:
addBmp := MakeAddSuffix(".bmp")
addJpeg := MakeAddSuffix(".jpeg")
And you can call them as:
addBmp("file") // returns: file.bmp
addJpeg("file") // returns: file.jpeg
Here is an example of a factory function that takes a function, and creates
another one of a completely different type.
package main
import "fmt"
type flt func(int) bool
type slice_split func([] int)([] int, [] int)
func isOdd(integer int) bool {
if integer % 2 == 0 {
return false
}
// check if integer is odd
return true
}
func isBiggerThan4(integer int) bool {
if integer > 4 {
return true
}
return false
}
// check if integer is greater than 4
func filter_factory(f flt) slice_split {
return func(s[] int)(yes, no[] int) {
for _, val := range s {
if f(val) {
yes = append(yes, val)
} else {
// split the slice on basis of func
no = append(no, val)
}
}
return
}
}
func main() {
s := [] int {1, 2, 3, 4, 5, 7}
fmt.Println("s = ", s)
odd_even_function := filter_factory(isOdd)
odd,even := odd_even_function(s)
fmt.Println("odd = ", odd)
fmt.Println("even = ", even)
//separate those that are bigger than 4 and those that are not.
bigger,smaller := filter_factory(isBiggerThan4)(s)
fmt.Println("Bigger than 4: ", bigger)
fmt.Println("Smaller than or equal to 4: ", smaller)
}
Filter Factory
The above program has two basic functions. The first function, isOdd takes an
integer as a parameter and returns bool value (see its header at line 7). If the
integer is odd, it will return true, otherwise false. Similarly, the second
function isBiggerThan4 takes an integer as a parameter and returns bool
value (see its header at line 15). If the integer is greater than 4, it will return
true, otherwise false.
At line 4, we are aliasing a type. A function that takes a single integer as a
parameter and returns a single boolean value is given a type flt . Similarly, at
line 5, we are aliasing a type. A function that takes a slice of integers as a
parameter and returns two slices of integers is given a type slice_split .
Now moving towards a major part of the program that is filter_factory
function, see its header at line 22. It takes a function f of type flt (either
isOdd or isBiggerThan4 ). The function filter_factory returns a lambda
function of type split_slices that takes s as a parameter and returns two
integer slices yes and no (see line 23). From line 25 to line 30, we are
evaluating s slice on the basis of f function given to filter_factory . If the f
function returns true for an integer, it becomes part of yes , otherwise no .
Let’s see the main function now. At line 36, we declare a slice of integers
named s . Then at line38, we call the filter_factory function with isOdd as
the parameter and store the result in odd_even_function of type slice_split .
Now, the odd_even_function is called with s as a parameter, which returns
two slices odd and even that contain odd and even numbers from s ,
respectively. Similarly at line 43, we call the filter_factory function with
isBiggerThan4 as the parameter. You may have noticed we pass s on the
same line as: bigger, smaller: = filter_factory(isBiggerThan4)(s) , rather
than making a separate split_slices variable and then passing s to it. The
result is stored in the bigger and smaller slices. Printing odd and even slices
at line 40 and line 41, respectively, verifies the result. Similarly, printing
bigger and smaller slices at line 44 and line 45 respectively verifies the
result.
Now, you are familiar enough with the use of closures. You’ll study how to
debug using closures in the next lesson.
Debugging with Closures
This lesson discusses how debugging can be made easy with Go using closures.
WE'LL COVER THE FOLLOWING
• Debugging using runtime
• Debugging using log
When analyzing and debugging complex programs with myriads of functions
in different code-files calling one another, it can often be useful to know
which file is executing at certain points in the program and the line number of
it. This can be done by using special functions from the packages runtime or
log .
Debugging using runtime #
In runtime , the function Caller() provides this information, so the closure
where() could be defined, and then be invoked wherever it is needed:
package main
import "fmt"
import "runtime"
import "log"
func main() {
where := func() {
// debugging function
_, file, line, _ := runtime.Caller(1)
log.Printf("%s:%d", file, line)
}
where()
// some code
a := 2*5
where()
// some more code
b := a+100
fmt.Println("a: ",a)
fmt.Println("b: ",b)
where()
}
Debugging using runtime
Debugging using log #
The same can be achieved by setting a flag in the log package:
log.SetFlags(log.Llongfile)
log.Print("")
or if you like the brevity of where :
package main
import "fmt"
import "log"
func main() {
var where = log.Print
where()
// some code
a := 2*5
where()
// some more code
b := a+100
fmt.Println("a: ",a)
fmt.Println("b: ",b)
where()
}
Debugging using log
That’s how Go uses closures to make debugging less tricky and provides
efficiency. In the next lesson, you’ll learn how to time a function and optimize
the program.
Optimizing Programs
This lesson focuses on how to optimize a program.
WE'LL COVER THE FOLLOWING
• Timing a function
• Using memoization for performance
Timing a function #
Sometimes it is interesting to know how long a certain computation took, e.g.
for comparing and benchmarking calculations. A simple way to do this is to
record the start-time before the calculation, and the end-time after the
calculation by using the function Now() from the time package. The
difference between them can then be calculated with Sub() . In code, this goes
like:
package main
import "fmt"
import "time"
func Calculation(){
for i := 0; i<10000; i++{
//do something
}
}
func main(){
start := time.Now()
Calculation()
end := time.Now()
delta := end.Sub(start)
fmt.Printf("Calculation took this amount of time: %s\n", delta)
}
Timing a function
If you have optimized a piece of code, always time the former version and the
optimized version to see that there is a significant (enough) advantage.
Using memoization for performance #
When doing heavy computations, one thing that can be done to increase
performance is not to repeat any calculation that has already been done and
that must be reused. Instead, cache the calculated value in memory; this is
called memoization. A great example of this is the Fibonacci program. To
calculate the n-th Fibonacci number, you need the 2 preceding ones, which
normally have already been calculated. If you do not stock the preceding
results, every higher Fibonacci number results in an even greater avalanche
of recalculations.
package main
import "fmt"
func main() {
// as the fabonacci for 0 and 1 is 1 so, setting variables expilcitly as 1
result := 1
a := 1
b := 1
// calculating fabonacci seq for first 10 numbers
for i := 0;i <= 10;i++{
if i>1 {
// no need to add preceding values for first two case
result = a+b
}
fmt.Printf("fibonacci(%d) is: %d\n", i, result)
// modifying 2 values for next iteration
a = b
// a should have the fibonacci for i-1th number for next iteration
b = result // b should have the fibonacci for ith number for next iteration
}
}
Fibonacci using Memoization
Memoization is useful for relatively expensive functions (not necessarily
recursive as in the example) that are called lots of times with the same
arguments. It can also only be applied to pure functions; these are functions
that always produce the same result with the same arguments, and have no
side-effects.
If you write a function keeping in view the timing and memoization, you can
optimize the program in a good manner. That’s it about how to optimize
programs using timing and memoization approaches. There is a quiz in the
next lesson for you to solve.
Quiz: Deduce the Outputs
In this lesson, your concepts will be evaluated through a quiz.
Choose one correct answer
1
Which of the following is the true statement?
func DoSomething1(a *A) {
b = a
}
func DoSomething2(a A) {
b = &a
}
COMPLETED 0%
1 of 8
We hope that you performed well in the quiz. That’s it about the functions. In
the next chapter, we’ll discuss arrays and slices in Go.
Declaration and Initialization
This lesson describes the important concepts of array, i.e., how to use, declare and initialize them.
WE'LL COVER THE FOLLOWING
• Introduction
• Concept
Introduction #
In this chapter, we start by examining data-structures that contain a number
of items, called collections, such as arrays (slices) and maps. Here, the Python
influence is obvious. The array-type indicated by the [] notation is well-known
in almost every programming language as the basic workhorse in
applications.
The Go array is very much the same but has a few peculiarities. It is not as
dynamic as in C, but to compensate, Go has the slice type. This is an
abstraction built on top of Go’s array type. So to understand slices, we must
first understand arrays. Arrays have their place, but they are a bit inflexible.
Therefore, you don’t see them too often in Go code. Slices, though, are
everywhere, and they are built on arrays to provide greater power and
convenience.
Concept #
An array is a numbered and fixed-length sequence of data items (elements) of
the same type (it is a homogeneous data structure). This type can be anything
from primitive types like integers or strings to self-defined types. The length
must be a constant expression, that must evaluate to a non-negative integer
value. It is part of the type of the array, so arrays declared as [5]int and
[10]int differ in type. The items can be accessed and changed through their
index (their position). The index starts from 0, so the 1st element has index 0,
the 2nd index 1, and so on (arrays are zero-based as usual in the C-family of
languages).
The number of items, also called the length (called len) or size of the array, is
fixed and must be given when declaring the array (length has to be
determined at compile time in order to allocate the memory).
Remark: The maximum array length is 2Gb.
The format of the declaration is:
var identifier [len]type
For example:
var arr1 [5]int
Let’s visualize arr1 in memory
Array in Memory
1 of 6
index 0
Array in Memory
2 of 6
index 1
Array in Memory
3 of 6
index 2
Array in Memory
4 of 6
index 3
Array in Memory
5 of 6
index 4
Array in Memory
6 of 6
Each compartment contains an integer. When declaring an array, each item in
it is automatically initialized with the default zero-value of the type. Here, all
the items default to 0. The length of arr1 is 5, and the index ranges from 0 to
len(arr1)-1 (4 in this case). The first element is given by arr1[0] , and the 3rd
element is given by arr1[2] ; in general, the element at index i is given by
arr1[i] . The last element is given by:
arr1[len(arr1)-1]
Assigning a value to an array-item at index i , is done with:
arr[i] = value
Arrays are mutable, which means that they can be changed.
Only valid indexes can be used. When using an index equal to or greater than
len(arr1) and if the compiler can detect this, the message “index out of
bounds” is given; otherwise, the code compiles just fine. Executing the
program will give the runtime error: “index out of range” . A runtime error
means the program will crash and give a certain message. In Go terminology,
this is called a panic. We will discuss these in much more detail in Chapter 11.
Because of the index, a natural way to loop over an array is to use the forconstruct:
for initializing the items of the array
for printing the values or in general
for processing each item in succession
The following program demonstrates the basic use of for loop with an array.
package main
import "fmt"
func main() {
var arr1 [5]int
// declaring an array
for i:=0; i < len(arr1); i++ {
arr1[i] = i * 2 // initializing items of array
}
for i:=0; i < len(arr1); i++ {
fmt.Printf("Item at index %d is %d\n", i, arr1[i])
}
}
// printing items of array
Arrays in Go
In the above code, at line 5 in main , we declare an array arr1 of length 5,
which will contain integral values. Then, we have a for loop at line 6 that will
iterate over the arr1 len(arr1) times and will initialize each item in an array
as arr1[i]=i*2 at line 7. Again, we have a for loop at line 9 that will iterate
over arr1 len(arr1) times and will print the items in array at line 10.
We can also use the range function and a single for loop to write the same
program. Run the following program.
package main
import "fmt"
func main() {
var arr1 [5]int
// declaring an array
for i:= range arr1 {
arr1[i] = i * 2 // initializing items of array
fmt.Printf("Item at index %d is %d\n", i, arr1[i])
}
}
// printing items of array
Arrays in Go (Range)
In the above code, at line 5 in main , we declared an array arr1 of length 5,
which will contain integer values. Then, we have a for loop at line 6 that will
iterate over the array len(arr1) times using the range function. It will
initialize the items in an array as arr1[i]=i*2 at line 7 and will print the
items in array at line 8.
An array in Go is a value type (not a pointer to the first element like in C/C++).
Therefore, it can be created with new():
var arr2 = new([5]int)
But what is the difference with:
var arr1 [5]int
Recall that new(T) allocates zeroed storage for a new item of type T and
returns to its address. Therefore, it returns a pointer to the type T. Thus, arr2
is of type *[5]int, whereas arr1 is of type [5]int. The consequence is that,
when assigning an array to another array, a distinct copy in the memory of the
array is made. For example, when we say:
arr3 := arr1
arr3[2] = 100
Then, the arrays have different values. Changing arr3 after the assignment
does not change arr1 . If you want the change to be possible, use:
arr3 := &arr1
Then, *arr3 will always have the same value as arr1 .
When an array is passed as an argument to a function like in func1(arr1) , a
copy of the array is made, and func1 cannot change the original array arr1 . If
this is possible, then arr1 has to be passed by reference with the &-operator,
as in func1(&arr1) . Look at the following implementation to understand this
concept clearly.
package main
import "fmt"
func f(a [3]int) { fmt.Println(a) }
// accepts copy
func fp(a *[3]int) { fmt.Println(a) } // accepts pointer
func main() {
var ar [3]int
f(ar) // passes a copy of ar
fp(&ar) // passes a pointer to ar
}
Pointer Array
The above code has two basic functions: f and fp . The function f is
accepting a copy of the array passed to it as a parameter. See its header at line
4: func f(a [3]int) . The second function, fp accepts a pointer to the array
that is passed to it as a parameter. See its header at line 6: func fp(a *
[3]int) . Now, look at the main . We declare an array ar at line 9 as var ar
[3]int . In the next line (line 10), we pass ar to f , which prints [0,0,0] as it
was the copy of ar . At line 11, we pass &ar as a pointer variable to fp , which
prints &[0,0,0] since a pointer variable contains address.
That’s it for the brief introduction to arrays in Golang. In the next lesson, you
will learn about further variations and how to handle arrays within functions.
Array Literals and Parameters
This lesson explains array literals and handling arrays as parameters to functions in detail.
WE'LL COVER THE FOLLOWING
• Array literals
• 1st variant
• 2nd variant
• 3rd variant
• 4th variant
• Passing an array to a function
Array literals #
When the values (or some of them) of the items are known beforehand, a
simpler initialization exists using the { , ,} notation called array literals (or
constructors) instead of initializing every item in the [ ]= way. Let’s see some
variants.
1st variant #
Specify explicitly the size n of the array with [n] . For example:
var arrAge = [5]int{18, 20, 15, 22, 16}
Here is another example:
var arr = [10]int{ 1, 2, 3 }
This is an array of 10 elements with the 1st three different from 0.
2nd variant #
The size of the array is determined by the number of elements between curly
brackets. For example:
var arrAge2 = []int{18, 20, 15, 22, 16}
This array has a length of 5. So, you see the size can be omitted. In that case,
the number of items between curly brackets becomes the size of the array.
3rd variant #
The […] notation, for example:
var arrLazy = [...]int{5, 6, 7, 8, 22}
... indicates the compiler has to count the number of items to obtain the
length of the array. However, [...]int is not a type, so this is illegal:
var arrLazy [...]int = [...]int{5, 6, 7, 8, 22}
If the ... is omitted then a slice is created.
4th variant #
The index: value syntax can be followed. For example:
var arrKeyValue = [5]string{3: "Chris", 4: "Ron"}
Passing an array to a function #
Passing big arrays to a function quickly eats up a lot of memory because
arrays are copied when passing. There are 2 ways to prevent this:
Pass a pointer to the array.
Use a slice of the array (we’ll discuss slices in the next section).
The following example illustrates the first solution:
package main
import "fmt"
func main() {
array := [3]float64{7.0, 8.5, 9.1}
x := Sum(&array) // Note the explicit address-of operator
// to pass a pointer to the array
fmt.Printf("The sum of the array is: %f", x)
}
func Sum(a *[3]float64) (sum float64) {
for _, v := range a { // dereferencing *a to get back to the array is not necessary!
sum += v
}
return
}
Pointer to Array in Function Call
In the above code, there is a function Sum that takes an array and calculates
the sum of its elements and returns their sum. Look at its header at line 11:
func Sum(a *[3]float64) (sum float64) . The function is taking the pointer to
the array a of length 3, whose elements are of type float64, and returning a
float64 number sum . Now, look at the main , where we declared an array
called array as: array := [3]float64{7.0, 8.5, 9.1} at line 5. In the next line,
we call the Sum function and pass &array because Sum accepts a pointer to the
array and stores the result in x . At line 8, we are printing x to verify the
result.
Now, you are familiar with the variations and use of arrays in functions. In
the next lesson, you have to write a program to solve a problem.
Challenge: Filling Array with Loop Counter
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Output
Problem statement #
Write a loop that fills an array with the loop-counter (from 0 to 14), and then
print that array to the screen.
Output #
Array filled with values from 0 to 14
[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14] // filled array
Try to implement the function below. Good luck!
package main
func main() {
// implement your loop here
}
Filling Array with Counter Loop
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Filling Array with Loop Counter
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import "fmt"
func main() {
var arr [15]int
for i:=0; i < 15; i++ {
// counter-controlled loop
arr[i] = i
}
fmt.Println(arr)
// [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14]
}
Filling Array with Loop Counter
As you can see, in main at line 5 we declare an array arr with length 15. We
set the length to 15 because the loop is meant to iterate 15 times. Then, in the
next line, we made a counter-controlled loop, with the iterator i that starts
from 0 and exits the loop when it reaches 15. At line 7, we set the value of
each element at index i equal to the value of i in any iteration. After the
loop, in the last line, we are printing the array to verify the result. On printing
(at line 9), we see that the array arr has values from 0 to 14, inclusively.
That’s it about the solution. In the next lesson, you’ll be attempting another
challenge.
Challenge: Finding Fibonacci Numbers with Array
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Output
• Sample output
Problem statement #
In the last chapter, we saw a recursive solution for calculating Fibonacci
numbers. However, Fibonacci numbers can also be calculated in an iterative
way, using a simple array. Do this for the first 10 Fibonacci numbers (using
an array) and then return the array.
Output #
An array
Sample output #
[1 1 2 3 5 8 13 21 34 55]
Try to implement the function below. Feel free to view the solution, after
giving some shots. Good Luck!
package main
import "fmt"
import "strconv"
import "encoding/json"
var fib[10]int64
func fibs() [10]int64{
return fib
}
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Finding Fibonacci Numbers with
Array
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import "fmt"
var fib [10]int64
func fibs() [10]int64{
fib[0] = 1
fib[1] = 1
// global array containing Fibonacci values
// base cases for 0
// base case for 1
for i:= 2; i <10; i++ {
fib[i] = fib[i-1] + fib[i-2]
}
return fib
// recursive case without recursion using arr
}
func main() {
arr := fibs()
for i:=0; i < 10; i++ {
fmt.Printf("The %d-th Fibonacci number is: %d\n", i, arr[i])
}
}
Finding Fibonacci Numbers with Array
In the code above, at line 4, we create a global variable of type integer array
called fib with length 10. This array will contain the first 10 Fibonacci
numbers. Now, look at the header of the function fibs at line 6. It returns the
Fibonacci sequence in an array of type int64. We know that Fibonacci values
for 0 and 1 both are 1. So, we set the first two indexes of fibs array to 1. Now,
we have a for loop at line 10, which starts from 2 and ends at the 9 index. At
line 11, we calculate the Fibonacci number for any value at index i as:
fibs[i] = fibs[i-1] + fibs[i-2] . In the main function at line 18, we call fibs
and store the result in separate array arr . Then, at the last, we have another
for loop at line 19, which prints all the Fibonacci values from arr .
That’s it about the solution. In the next lesson, you’ll study slices.
Slices
This lesson describes some important concepts about slices, i.e., how to use, declare and initialize them.
WE'LL COVER THE FOLLOWING
• Concept
Concept #
A slice is a reference to a contiguous segment (section) of an array (which we
will call the underlying array, and which is usually anonymous), so a slice is a
reference type (more like the array type in C/C++, or the list type in Python).
This section can be the entire array or a subset of the items indicated by a
start and an end index (the item at the end index is not included in the slice).
Slices provide a dynamic window to the underlying array.
A slice in memory is a structure with 3 fields:
a pointer to the underlying array
the length of the slice
the capacity of the slice
Slices are indexable and have a length given by the len() function. The sliceindex of a given item can be less than the index of the same element in the
underlying array. Unlike an array, the length of a slice can change during the
execution of the code, minimally 0 and maximally the length of the
underlying array, which means a slice is a variable-length array.
The built-in capacity function cap() of a slice is a measure of how long a slice
can become. It is the length of the slice + the length of the array beyond the
slice. If s is a slice, cap is the size of the array from s[0] to the end of the
array. A slice’s length can never exceed its capacity, so the following statement
is always true for a slice s :
0 <= len(s) <= cap(s)
This is illustrated in the following figure, where the slice x has a capacity and
length of 5. Slice y , on the other hand, is of length 2 and capacity 4. The y
slice takes its value from slice x . The slice y is equal to x[1:5] and contains
the elements 3, 5, 7 and 11.
x := []int {2,3,5,7,11}
ptr
5
5
len
cap
2
3
[] int
5
7
11
[5] int
y := x[1:5]
4
4
[] int
Slice in Memory
Multiple slices can share data if they represent pieces of the same array, but
multiple arrays can never share data. A slice, therefore, shares storage with its
array and with other slices of the same array. In contrast, distinct arrays
always represent distinct storage. Arrays are in fact building blocks for slices.
Because slices are references, they don’t use up additional memory and they
are more efficient to use than arrays. That’s the reason why they are used
much more than arrays in Go-code. The format of the declaration is:
var identifier []type //no length is specified.
A slice that has not yet been initialized is set to nil by default and has length 0.
Here is the format of the initialization of a slice:
var slice1 []type = arr1[start:end]
This represents the subarray of arr1 (slicing the array, start:end is called a
slice-expression) composed of the items from index start to index end-1 . So
the following statement is true:
slice1[0] == arr1[start]
This can be defined even before the array arr1 is populated.
If one writes:
var slice1 []type = arr1[:]
then, slice1 is equal to the complete array arr1 (this is a shortcut for
arr1[0:len(arr1)] ). Another way to write this is:
slice1 = &arr1
arr1[2:] is the same as arr1[2:len(arr1)] , so it contains all the items of the
array from the 2nd element until the last. Additionally, arr1[:3] is the same as
arr1[0:3] , they both contain the array-items from the 1st until (not including)
the 3rd element. If you need to cut the last element from slice1 , use:
slice1 = slice1[:len(slice1)-1]
A slice of the array with elements 1, 2 and 3 can be made as follows:
s := [3]int{1,2,3}[:]
or
s := [...]int{1,2,3}[:]
or even shorter
s := []int{1,2,3}
This means that slices can also be initialized like arrays:
var x = []int{2, 3, 5, 7, 11}
s2 := s[:] is a slice made out of a slice, having identical elements, but it still
refers to the same underlying array. A slice s can be expanded to its
maximum size with s = s[:cap(s)] ; any greater expansion gives a runtime
error. For every slice (also for strings), the following is always true:
s == s[:i] + s[i:] // i is an int: 0 <= i <= len(s)
What this does is create an array of length 5 and then a slice to refer to it. Run
the following program to see how slices work.
package main
import "fmt"
func main() {
var arr1 [6]int
var slice1 []int = arr1[2:5] // item at index 5 not included!
// load the array with integers: 0,1,2,3,4,5
for i := 0; i < len(arr1); i++ {
arr1[i] = i
}
// print the slice:
for i := 0; i < len(slice1); i++ {
fmt.Printf("Slice at %d is %d\n", i, slice1[i])
}
fmt.Printf("The length of arr1 is %d\n", len(arr1))
fmt.Printf("The length of slice1 is %d\n", len(slice1))
fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))
// grow the slice:
slice1 = slice1[0:4]
for i := 0; i < len(slice1); i++ {
fmt.Printf("Slice at %d is %d\n", i, slice1[i])
}
fmt.Printf("The length of slice1 is %d\n", len(slice1))
fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))
// grow the slice beyond capacity:
// . slice1 = slice1[0:7] // panic: runtime error: slice bounds out of range
}
Arrays and Slices
Let’s study the code above line by line. In main at line 5, we make an array
arr of length 6. Now, before even initializing the values of arr , we made a
slice slice1 out of arr at line 6: var slice1 []int = arr1[2:5] . The slice1
contains three elements from arr (from index 2 to index 4). Then, we are
initializing elements in the array arr with for loop at line 8. The element at
index i is given the value of the iterator i . So arr holds [0,1,2,3,4,5]. The for
loop at line 12 is printing all the elements from the slice1 . You’ll note that
although we make slice1 when values are not given to array arr , slice1
still holds the value from 2nd index to 4th index of arr . So slice1 is [2,3,4].
Line 15 is printing the length of arr , which is 6. Line 16 is printing the length
of slice1 , which is 3 (as three elements are present), and line 17 is printing
the capacity of slice1 , which is 4 (since three elements are present in slice1
and only one element of arr is beyond the slice1 ).
At line 19, we are growing the slice1 as: slice1 = slice1[0:4] . This means
that the one last element of arr1 which was not part of slice1 initially, now
is a part of slice1 . The for loop at line 20 is printing all the elements from the
updated slice1 . Line 23 is printing the length of slice1 , which is 4 (since
four elements are present), and line 24 is printing the capacity of slice1 ,
which is 4 (since four elements are present in slice1 and no element of arr1
is beyond the slice1 ).
If s2 is a slice, then you can move the slice forward by one with:
s2 = s2[1:]
But, the end is not moved. Slices cannot be re-sliced below zero to access
earlier elements in the array, they can only move forward:
s2 = s2[-1:] // results in a compile error.
That’s it about the brief introduction to slices in Golang. In the next lesson,
you will learn about slices in further detail and learn how to handle slices
within functions.
Slices with Functions
This lesson explains handling slices as parameters to the functions in detail.
WE'LL COVER THE FOLLOWING
• Passing a slice to a function
• Creating a slice with make()
• Difference between new() and make()
• new(T)
• make(T)
Passing a slice to a function #
If you have a function that must operate on an array, you probably always
want to declare the formal parameter to be a slice. When you call the
function, slice the array to create (efficiently) a slice reference and pass that.
For example, here is a program that sums all elements in an array:
package main
import "fmt"
func sum(a []int) int {
// function that sums integers
s := 0
for i := 0; i < len(a); i++ {
s += a[i]
}
return s
}
func main() {
var arr = [5]int{0,1,2,3,4} // declare an array
fmt.Println(sum(arr[:]))
// passing slice to the function
}
Passing Slice to a Function
In the above program, we have a function sum , that takes an array a as a
parameter and returns the sum of the elements present in a . Look at its
header at line 4. In the main function, at line 13, we declare an array arr of
length 5 and pass it to the sum function on the next line. We write arr[:] ,
which is enough to pass the array arr as the copy.
Creating a slice with make() #
Often the underlying array is not yet defined. In that case, we can make the
slice together with the array using the make() function:
var slice1 []type = make([]type, len)
which can be shortened to:
slice1 := make([]type, len)
where len is the length of the array and also the initial length of the slice.
Therefore, for the slice s2 made with:
s2 := make([]int, 10)
the following is true:
cap(s2) == len(s2) == 10
The function make takes 2 parameters:
the type to be created
the number of items in the slice. If you want slice1 not to occupy the
whole array (with length cap) from the start, but only a number len of
items, use the form:
slice1 := make([]type, len, cap)
So make has the signature:
func make([]T, len, cap) // []T with type T and optional parameter cap
And the following statements result in the same slice:
make([]int, 50, 100)
new([100]int)[0:50]
The following figure depicts the making of a slice in memory:
make([]int,2,5)
ptr
2
5
len
cap
0
0
[] int
0
0
0
[5] int
Slice in Memory
The following program is an implementation of making slices through the
make function.
package main
import "fmt"
func main() {
var slice1 []int = make([]int, 10)
// load the array/slice:
for i := 0; i < len(slice1); i++ {
slice1[i] = 5 * i
}
// print the slice:
for i := 0; i < len(slice1); i++ {
fmt.Printf("Slice at %d is %d\n", i, slice1[i])
}
fmt.Printf("\nThe length of slice1 is %d\n", len(slice1))
fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))
}
Slice via make()
In the program above, we make a slice via the make function. Look at line 5 in
main . We make an integer type slice slice1 with the length 10. The loop at
line 7 is populating the values in slice1 . Each element at index i is given a
value of 5*i at line 8. The next for loop at line 11 is printing the slice
elements. Then, at line 14 and line 15, we are printing the length and capacity
of slice1 using the len() and cap() functions, which are both 10.
Difference between new() and make() #
This is often confusing at first sight: both allocate memory on the heap, but
they do different things and apply to different types.
new(T) #
The function new(T) allocates zeroed storage for a new item of type T and
returns its address as a value of type *T. It applies to value types like arrays
and structs (see Chapter 8), and it is equivalent to &T{ }
make(T) #
The function make(T) returns an initialized value of type T. It applies only to
the 3 built-in reference types: slices, maps, and channels (see Chapters 6 and
Chapter 12).
In other words, new allocates and make initializes; the following figure
illustrates this difference:
new([]int)
*[] int
nill
0
0
ptr
len
cap
0
0
len
cap
[] int
make ([]int,0)
ptr
[] int
[0] int
Difference b/w new() and make()
The function new works as:
var p *[]int = new([]int) // *p == nil; with len and cap 0
or
p := new([]int)
Whereas, make works as:
p := make([]int, 0)
Here, our slice is initialized, but it points to an empty array. Both these
statements aren’t very useful, but the following is:
var v []int = make([]int, 10, 50)
or v := make([]int, 10, 50)
This allocates an array of 50 integers and then creates a slice v with length 10
and the capacity 50, which points to the first 10 elements of the array.
Now, that you are familiar with the use of slices, try your hand at writing a
function to solve a problem in the next lesson.
Challenge: Finding Fibonacci Numbers with Slices
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Input
• Sample input
• Output
• Sample output
Problem statement #
In the last challenge you calculated Fibonacci numbers with arrays. Now,
write a program in which main calls a function with the number of terms in
the series n as a parameter. The function returns a slice with the Fibonacci
numbers up to that number n .
Input #
A positive integer
Sample input #
5
Output #
A slice
Sample output #
[1 1 2 3 5]
Try to implement the function below. Feel free to view the solution, after
giving some shots. Good Luck!
package main
import "fmt"
import "strconv"
import "encoding/json"
func fibarray(term int) []int {
return nil
}
Finding Fibonacci Numbers with Slices
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Finding Fibonacci Numbers with
Slices
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import "fmt"
func main() {
result := fibarray(5)
for ix, fib := range result {
fmt.Printf("The %d-th Fibonacci number is: %d\n", ix,
}
}
fib)
func fibarray(term int) []int { // calculating Fibonacci for first term numbers
farr := make([]int, term)
farr[0], farr[1] = 1, 1
// base case
for i:= 2; i < term; i++ {
farr[i] = farr[i-1] + farr[i-2] // calculating sequence for i index
}
return farr
}
Finding Fibonacci Numbers with Slices
In the code above, look at the header of the function fibarray at line 11,
which takes term as the input and returns the Fibonacci sequence until term
in an array of type int. We make a slice farr of size term at line 12. Since we
know that the first and second Fibonacci numbers are both 1, we set the first
two indexes of the array farr to 1. Now, we have a for loop at line 15, which
starts from 2 and ends at the index term-1. At line 16, we calculate the
Fibonacci value for any value at index i as: farr[i] = farr[i-1] + farr[i-2] ,
and return farr while exiting from the function.
In main at line 5, we called fibarray to store the result in a separate slice
result . Then, at the end, we have another for loop at line 6, which prints all
the Fibonacci values from the result .
That’s it about the solution. In the next lesson, you’ll study a concept of
multidimensionality.
Multidimensionality
This lesson discusses how to handle multidimensional sequences of data in Go.
WE'LL COVER THE FOLLOWING
• Multidimensional arrays
• Multidimensional slices
Multidimensional arrays #
Arrays are always 1-dimensional, but they may be composed to form
multidimensional arrays, like:
[3][5]int
[2][2][2]float64
The inner arrays always have the same length. Go’s multidimensional arrays
are rectangular. Here is a code snippet which uses such an array:
package main
const (
WIDTH = 1920
HEIGHT = 1080
)
// columns of 2D array
// rows of 2D array
type pixel int // aliasing int as pixel
var screen [WIDTH][HEIGHT]pixel
// global 2D array
func main() {
for y := 0; y < HEIGHT; y++ {
for x := 0; x < WIDTH; x++ {
screen[x][y] = 0
// initializing value to 2D array
}
}
}
Multidimensional Array
In the code above, we set two constants WIDTH and HEIGHT at line 4 and line 5,
which determine columns and rows of a 2D array in our program. Then, at
line 8, we alias the type int as pixel , which means every integer in our
program is actually a pixel. In the next line, we make a global 2D array screen
of type pixel . In the main function, we have two nested for loops (because
screen is 2-D array). The outer loop at line 12 controls the rows and the inner
loop at line 13 controls the columns. The pixel of each column for every row is
assigned 0 value at line 14.
That’s how we can work with a multidimensional array. For an n-D array, n
for loops will be used.
Multidimensional slices #
Like arrays, slices are always 1-dimensional but may be composed to
construct higher-dimensional objects. With slices of slices (or arrays of slices),
the lengths may vary dynamically, so Go’s multidimensional slices can be
jagged. Moreover, the inner slices must be allocated individually (with make).
Here is a simple example of where the inner slices are created literally:
package main
import "fmt"
func main() {
values := [][]int{} // multidimensional slice
// These are the first two rows.
row1 := []int{1, 2, 3}
row2 := []int{4, 5, 6}
// Append each row to the two-dimensional slice.
values = append(values, row1)
values = append(values, row2)
// Display first row.
fmt.Println("Row 1")
fmt.Println(values[0])
// Display second row.
fmt.Println("Row 2")
fmt.Println(values[1])
// Access an element.
fmt.Println("First element")
fmt.Println(values[0][0])
// Display entire slice.
fmt.Println("Values")
fmt.Println(values)
}
Multidimensional Slices
In the program above, in main at line 5, we made a multidimensional slice
called values . In the next two lines, we made two more slices called row1 and
row2 , which are rows of the values . We append these two rows in values at
line 11 and line 12. We print the first row: values[0] at line 15, and the
second row: values[1] at line 18. What if we want to access the first element
of the first row? Look at line 21. The line values[0][0] is used to access the
first element present in the first row, which is 1. Whole 2D slice can also be
printed by just typing values, as we did in line 24.
Now that you know about multidimensional arrays and slices, let’s study the
concepts of bytes and slices.
Bytes and Slices
This lesson provides necessary information about the bytes package provided by Golang.
WE'LL COVER THE FOLLOWING
• The bytes package
• Concatenating strings by using a buffer
The bytes package #
Slices of bytes are so common that Go has a bytes package with manipulation
functions for that kind of type. It is very analogous to the strings package.
Moreover, it contains a very handy type Buffer :
import "bytes"
type Buffer struct {
...
}
It is a variable-sized buffer of bytes with Read and Write methods because
reading and writing an unknown number of bytes is best done buffered.
A Buffer can be created as a value, like:
var buffer bytes.Buffer
or as a pointer with new as:
var r *bytes.Buffer = new(bytes.Buffer)
or created with the function:
func NewBuffer(buf []byte) *Buffer
This creates and initializes a new Buffer using buf as its initial contents. It is
best to use NewBuffer only to read from buf . Here, is a simple example of
code writing to a buffer:
package main
import (
"bytes"
"fmt"
)
func main() {
// New Buffer.
var b bytes.Buffer
// Write strings to the Buffer.
b.WriteString("ABC")
b.WriteString("DEF")
// Use Fprintf with Buffer.
fmt.Fprintf(&b, " A number: %d, a string: %v\n", 10, "bird")
b.WriteString("[DONE]")
// Convert to a string and print it.
fmt.Println(b.String())
}
Writing to a Buffer
In the code above, in main at line 9, we make a buffer as: var b bytes.Buffer .
Now, we start writing to the buffer b . Look at line 12 and line 13, where we
write two strings ABC and DEF to the buffer b . At line 15, we are printing the
value placed at address &b, which will print ABCDEF although written to b
initially. Then at line 16, we write [DONE] to the buffer b and print it by
b.String() , which returns string written to the buffer at line 18.
Concatenating strings by using a buffer #
This works analogous to Java’s StringBuilder class. Make a buffer, append
each string s to it with the buffer.WriteString(s) method, and convert back
to a string with buffer.String() at the end, as in the following code snippet:
var buffer bytes.Buffer
for {
if s, ok := getNextString(); ok { //method getNextString() not shown her
e
buffer.WriteString(s)
} else {
break
}
}
fmt.Print(buffer.String(), "\n")
This method is much more memory and CPU-efficient than +=, especially if the
number of strings to concatenate is large.
That’s it about the bytes package and some of its important functions. In the
next lesson, you’ll learn how to use the for construct on the slices.
The for-range Construct
This lesson helps you learn how to run a loop on a slice using the for-range construct.
WE'LL COVER THE FOLLOWING
• Introduction
• Implementation of range on slices
• For range with multidimensional slices
Introduction #
This construct can be applied to arrays and slices:
for ix, value := range slice1 {
...
}
The first value ix is the index in the array or slice, and the second is the
value at that index. They are local variables only known in the body of the
for-statement, so value is a copy of the slice item at index ix and cannot be
used to modify the slice!
Implementation of range on slices #
The following is a simple program that iterates over a slice using the range
construct.
package main
import "fmt"
func main()
slice1 :=
slice1[0]
slice1[1]
slice1[2]
slice1[3]
{
make([]int, 4)
= 1
= 2
= 3
= 4
for ix, value := range slice1 {
fmt.Printf("Slice at %d is: %d\n", ix, value)
}
}
For Range on Slice
In this program, we make a slice, slice1 , of length 4 at line 5 using the make
function. Then, in the next four lines, from line 6 to line 9, we initialize the
elements with some values. At line 10, we have a for loop managed by the
range function, that will iterate len(slice1) times. In each iteration, it prints
the values present in slice1 at each index.
The following snippet presents an example with strings here:
seasons := []string{"Spring","Summer","Autumn","Winter"}
for ix, season := range seasons {
fmt.Printf("Season %d is: %s\n", ix, season)
}
var season string
for _, season = range seasons {
fmt.Printf("%s\n", season)
}
The above for loop iterates until len(seasons) and prints every season from
seasons as:
Season
Season
Season
Season
0
1
2
3
is:
is:
is:
is:
Spring
Summer
Autumn
Winter
And the second for loop prints every season from seasons by using _ to
discard the index:
Spring
Summer
Autumn
Winter
Use this version if you want to modify seasons, as shown in the following
example:
package main
import (
"fmt"
"strings"
)
func main() {
seasons := []string{"Spring","Summer","Autumn","Winter"}
for ix, season := range seasons { // printing seasons
fmt.Printf("Season %d is: %s \n", ix, season)
}
for ix := range seasons {
seasons[ix] = strings.ToUpper(seasons[ix]) // modifying the seasons
}
for ix, season := range seasons {
fmt.Printf("Season %d is: %s \n", ix, season) // printing modified seasons
}
}
Modifying Slices of Strings Using range
In the code above, we make a slice seasons at line 8 in main function as:
seasons := []string{"Spring","Summer","Autumn","Winter"} . Then, we have a
for loop at line 9, which prints every season from seasons along with their
index. At line 12, we have a for loop managed with range that will iterate
len(seasons) time and change the seasons by capitalizing them as:
seasons[ix] = strings.ToUpper(seasons[ix]) at line 13. Then, we have a for
loop at line 15, which prints every modified season from seasons along with
their index.
For range with multidimensional slices #
It can be convenient to write the nested for-loops as ranges over the rows and
row values (columns) of a matrix, like:
package main
import "fmt"
func main(){
value := 0 // used to set values to 2D array
screen := [2][2]int{} // will contain 4 values
// asssigning values in 2D-array
for row := range screen {
for column := range screen[row] {
screen[row][column] = value
value = value+1
}
}
// printing 2D-array
for row := range screen {
for column := range screen[0] {
fmt.Print(screen[row][column], " ") // separates columns
}
fmt.Print("\n") //separates rows
}
}
Range on a 2D array
In the code above, at line 6, we make a 2D array of integers called screen with
2 rows and 2 columns. Right now, the screen is empty. To assign values, we
need nested-loops. One loop will control the rows and the other one will
control the columns. See line 9, where we make an outer loop controlling
rows of the screen . In the next line, there is an inner loop that is controlling
the columns. Here, the for is ranging over screen[row] , which means that
this loop will cover all the columns for a row (the specific row) of the screen
in each iteration. At line 11, the value is assigned to screen[row][column]
(which is a cell of the specific row and column). Before moving to the next
iteration, the value is incremented by 1. Let’s break down the loops:
In the first iteration of the outer loop, the first row will be considered.
Inner loop will begin its first iteration and will select the first column
of the first row. Value 0 will be placed in the cell.
Inner loop will begin its second iteration and will select the second
column of the first row. Value 1 will be placed in the cell.
As all the columns of the first row are filled, the outer loop will begin its
second iteration.
Inner loop will begin its first iteration and will select the first column
of the second row. Value 2 will be placed in the cell.
Inner loop will begin its second iteration and will select the second
column of the second row. Value 3 will be placed in the cell.
As all the columns of the second row are filled, the outer loop will get the
control. However, all the rows are traversed now, so the control will exit
from the outer loop.
Now, you know how to iterate through a slice. In the next lesson, you’ll learn
how to reslice a slice.
Reslicing
This lesson discusses an important concept called reslicing.
WE'LL COVER THE FOLLOWING
• Introduction
• Explanation
Introduction #
We saw that a slice is often made smaller than the underlying array initially,
like this:
slice1 := make([]type, start_length, capacity)
Here, start_length is the length of slice and capacity is the length of the
underlying array. This is useful because now our slice can grow until
capacity . Changing the length of the slice is called reslicing, it is done like:
slice1 = slice1[0:end]
where end is another end-index (length) than before.
Resizing a slice by 1 can be done as follows:
sl = sl[0:len(sl)+1] // extend length by 1
A slice can be resized until it occupies the whole underlying array.
Explanation #
The following program is a detailed implementation of reslicing a slice:
package main
import "fmt"
func main() {
slice1 := make([]int, 0, 10)
// load the slice, cap(slice1) is 10:
for i := 0; i < cap(slice1); i++ {
slice1 = slice1[0:i+1] // reslice
slice1[i] = i
fmt.Printf("The length of slice is %d\n", len(slice1))
}
// print the slice:
for i := 0; i < len(slice1); i++ {
fmt.Printf("Slice at %d is %d\n", i, slice1[i])
}
}
Reslicing
In the code above, at line 5 in main , we make a slice slice1 via the make
function. Its length is 0 and its capacity is 10. At line 7 is the for loop that is
iterating capacity times. At line 8, we are reslicing slice1 as: slice1 =
slice1[0:i+1] . The length of slice1 is initially 0. However, reslicing makes
the length of slice1 i+1 , in every iteration. So in the first iteration, the length
of slice1 is 1. In the second iteration, the length of slice1 is 2, and so on. At
line 9, we are initializing each element at index i with the value i . The
length of slice1 is printed in each iteration at line 11. Finally, the loop at line
14 prints each element of slice1 .
Another example is:
package main
import "fmt"
func main(){
var ar = [10]int{0,1,2,3,4,5,6,7,8,9}
var a = ar[5:7]
a = a[0:4] // ref of subarray {5,6,7,8}
fmt.Println(a)
}
Creating a slice of the array ar as ar[5:7] makes a equal to {5,6}. Here, the
length of a is 2 and the capacity of a is 5. In the last line, we are reslicing a
as a=a[0:4] . Now, a equals to {5,6,7,8} with a length of 4 and a capacity of 5.
To get a piece of information from a slice and expand or contract a slice,
reslicing works perfectly. Golang also provides the functionality to copy a slice
or append a slice in another slice. Move on to the next lesson to see how this
works.
Copying and Appending Slices
This lesson describes the method of copying a slice or appending a slice to provide exibility.
WE'LL COVER THE FOLLOWING
• Modifying slices
• Use of the copy function
• Use of the append function
Modifying slices #
To increase the capacity of a slice one must create a new and larger slice and
copy the contents of the original slice into it.
Use of the copy function #
Its syntax is as:
func copy(dst, src []T) int
The function copy copies slice elements of type T from a source src to a
destination dst , overwriting the corresponding elements in dst , and it
returns the number of elements copied. Source and destination may overlap.
The number of arguments copied is the minimum of len(src) and len(dst) .
When src is a string, the element type is byte.
Use of the append function #
Its syntax is:
func append(s[]T, x ...T) []T
The function append appends zero or more values to a slice s and returns the
resulting slice with the same type as s . The values, of course, have to be of the
same type as the element-type T of s . If the capacity of s is not large enough
to fit the additional values, append allocates a new, sufficiently large slice that
fits both the existing slice elements and the additional values. Thus, the
returned slice may refer to a different underlying array. The append always
succeeds unless the computer runs out of memory.
If you want to append a slice y to a slice x , use the following form to expand
the second argument to a list of arguments:
x = append(x, y...)
The following code illustrates the functions copy for copying slices, and
append for appending new values to a slice.
package main
import "fmt"
func main() {
sl_from := []int{1,2,3}
sl_to := make([]int,10)
n := copy(sl_to, sl_from)
fmt.Println(sl_to) // output: [1 2 3 0 0 0 0 0 0 0]
fmt.Printf("Copied %d elements\n", n) // n == 3
sl3 := []int{1,2,3}
sl3 = append(sl3, 4, 5, 6)
fmt.Println(sl3) // output: [1 2 3 4 5 6]
}
Copy and Appending
In the code above, we make two slices: sl_from at line 5 and sl_to at line 6.
The length of sl_from is 3 and the length of sl_to is 10 (all zeros). At line 7,
we copy the contents of sl_from to sl_to and store total number of elements
copied in n . Printing sl_to at line 8 gives [1 2 3 0 0 0 0 0 0 0]. The variable n
here is 3 because only three elements (1,2,3) were copied from sl_from to
sl_to . At line 10, we make another slice called sl3 and initialize it to {1,2,3}.
Line 11 shows how to use the append function. The elements 4, 5 and 6 are
further appended at the end of slice sl3 . Now, the modified value of sl3 is
{1,2,3,4,5,6}.
The flexibility of slices and especially the append method makes slices
comparable to the ArrayList from Java/C#.
The copy and append functions make it easy for programmers to copy and
append slices when needed without having to write loops. In the next lesson,
you have to write a function to solve a problem.
Challenge: Magnify a Slice
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Input
• Sample input
• Output
• Sample output
Problem statement
Write a function that takes a slice s of integers and an integer
factor , and enlarges s so that its new length is len(s) * factor.
Input #
A slice of integer and an integer
Sample input #
[1 2 3] // slice of length 3
5
// factor to increase length
Output #
Enlarged slice
Sample output #
[1 2 3 0 0 0 0 0 0 0 0 0 0 0 0] // slice of length 15(3*5)
Try to implement the function below. Feel free to view the solution, after
giving some shots. Good Luck!
package main
import "fmt"
import "strconv"
import "encoding/json"
func enlarge(s []int, factor int) []int {
return nil
}
Magnify a Slice
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Magnify a Slice
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import "fmt"
var s []int
func main() {
s = []int{1, 2, 3}
fmt.Println("The length of s before enlarging is:", len(s))
fmt.Println(s)
s = enlarge(s, 5)
// calling function to magnify
fmt.Println("The length of s after enlarging is:", len(s))
fmt.Println(s)
}
func enlarge(s []int, factor int) []int {
ns := make([]int, len(s) * factor)
// making a new slice of length len(s
copy(ns, s)
// copying contents from s to new slice
return ns
}
Magnify a Slice
In the code above, look at the header for function enlarge at line 15: func
enlarge(s []int, factor int) []int . It takes a slice s that needs to be
magnified, and a factor to decide the length of a magnified slice as
len(s)*factor. In the next line, we make a new slice ns with the make function
of length len(s)*factor as required. The slice ns will contain the
len(s)*factor number of zeros. At line 17, we copy all the contents from the
original slice s to ns . That means the first len(s) number of zeros in ns will
be replaced by values in s , index by index. At last, we are returning ns , the
magnified slice.
Now, look at the main function. At line 7, we declare a slice s with some
values in it. At line 8, we print the length of s before magnifying it, and in the
next line, we print s . Now, we call the enlarge function for s at line 10, and
store the result in s . At line 11, we print the length of magnified s , and in the
next line, we print magnified s , to verify that s is magnified by
len(s)*factor.
That’s it about the solution. In the next lesson, you’ll attempt another
challenge.
Challenge: Inserting Slice in a Slice
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Input
• Sample input
• Output
• Sample output
Problem statement #
Make a function that inserts a string slice into another string slice at a certain
index.
Input #
Two Slices and an integer for index
Sample input #
["M", "N", "O", "P", "Q", "R"] // slice in which another slice will be add
ed
["A" "B" "C"] // slice to be appended
0
// index
Output #
Slice
Sample output #
["A" "B" "C" "M" "N" "O" "P" "Q" "R"]
Try to implement the function below. Feel free to view the solution, after
giving some shots. Good Luck!
package main
import "fmt"
import "strconv"
import "encoding/json"
// slice is the slice to which another slice will be added
// insertion is the slice that needs to be inserted.
// index is the position for insertion
func insertSlice(slice, insertion []string, index int) []string {
return nil
}
Inserting Slice in a Slice
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Inserting Slice in a Slice
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"fmt"
)
func main() {
s := []string{"M", "N", "O", "P", "Q", "R"}
in := []string{"A", "B", "C"}
res := insertSlice(s, in, 0) // at the front
fmt.Println(res) // [A B C M N O P Q R]
res = insertSlice(s, in, 3) // [M N O A B C P Q R]
fmt.Println(res)
}
func insertSlice(slice, insertion []string, index int) []string {
result := make([]string, len(slice) + len(insertion))
at := copy(result, slice[:index])
at += copy(result[at:], insertion)
copy(result[at:], slice[index:])
return result
}
Insert Slice in a Slice
In the code above, look at the header for the function insertSlice at line 15:
insertSlice(slice, insertion []string, index int) []string . This function
takes two slices slice (the slice in which another slice will be inserted) and
insertion (the slice that is to be inserted in slice) and an integer parameter
index that defines the point of insertion. The function returns an updated
slice after insertion.
We make a slice called result at line 16 with the make function. The length of
the result should be a sum of len(slice) (the length of the original slice )
and len(insertion) (the length of insertion ). Inserting insertion at an
index means that elements of slice from and after index will move
len(insertion) indexes forward.
At line 17, we first copy all the contents from the 0 index until the index
before index to result and store the number of elements copied in at . Then,
on the next line (line 18), we are copying insertion into result . The contents
from slice insertion must be copied in the result after the contents from
slice . To handle this, we make the variable at . The statement at +=
copy(result[at:], insertion) will place the insertion after the elements of
slice in result , and add the copied elements in at .
Now, we have to copy the remaining elements from slice if any to result .
We’ll copy the elements starting from index from slice to the result starting
from at index. At last, we’ll return the result . Let suppose the slice is {“M”,
“N”, “O”, “P”, “Q”, “R”}, the insertion is {“A”,“B”,“C”}, and the index is 3.
Visualize the concept with these slides.”
index = 3
slice
M
N
O
insertion
A
B
C
P
Q
R
result
Inserting Slice in a Slice
1 of 7
index = 3
slice
M
N
O
insertion
A
B
C
P
Q
R
M
N
O
slice[:index]
result
copy
Inserting Slice in a Slice
2 of 7
index = 3
result
M
slice
M
N
O
insertion
A
B
C
N
P
Q
R
O
Inserting Slice in a Slice
3 of 7
index = 3
at = 3
slice
M
N
O
insertion
A
B
C
P
Q
R
copy
result
M
N
O
result[at:]
Inserting Slice in a Slice
4 of 7
index = 3
at = 6
slice
M
N
O
P
Q
R
result
M
N
O
A
B
C
Inserting Slice in a Slice
5 of 7
index = 3
at = 6
slice
result
M
N
O
P
Q
R
M
N
O
A
B
C
P
Q
R
slice[index:]
result[at:]
copy
Inserting Slice in a Slice
6 of 7
index = 3
at = 9
result
M
N
O
A
B
C
P
Q
R
Inserting Slice in a Slice
7 of 7
Now, look at the main function. We made two string slices: s (the slice in
which another slice will be inserted) and in (the slice that is to be inserted in
s ) at line 7 and line 8, respectively. Then at line 9, we call insertSlice for s ,
in and index 0, and store the result in res .The result of res is printed in the
next line. To test another case, at line 11, we call insertSlice for s , in and
index 3, and store the result in res . The result of res is printed in the next
line.
That’s it about the solution. In the next lesson, you’ll attempt another
challenge.
Challenge: Filtering with Higher-Order Functions
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Input
• Sample input
• Output
• Sample output
Problem statement #
Make a function Filter which accepts s as the first parameter and a function
fn func(int) bool as the second parameter. The Filter should return a slice
of the elements of s which fulfill the function fn (make it true). Test this out
with fn testing if the integer is even.
Input #
A slice and the function name(use function that checks if the number is even
or not)
Sample input #
[0 1 2 3 4 5 6 7 8 9] // slice
even
//function that checks if the number is even or not
Output #
Slice containing even numbers only
Sample output #
[0 2 4 6 8]
Try to implement the function below. Feel free to view the solution, after
giving some shots. Good Luck!
package main
import "fmt"
import "strconv"
import "encoding/json"
func Filter(s []int, fn func(int) bool) []int {
return nil
}
func even(n int) bool {
if n%2 == 0 {
return true
}
return false
}
Filtering with Higher Order Functions
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Filtering with Higher-Order Functions
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"fmt"
)
func main() {
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s = Filter(s, even)
fmt.Println(s)
}
// Filter returns a new slice holding only
// the elements of s that satisfy fn()
func Filter(s []int, fn func(int) bool) []int {
var p []int // == nil
for _, i := range s {
if fn(i) {
p = append(p, i)
}
}
return p
}
func even(n int) bool {
if n%2 == 0 {
return true
}
return false
}
Filtering with Higher Order Functions
The program above has one basic function. The function even takes n as a
parameter and returns a boolean value (see its header on line 24). If n is
even, it will return true. Otherwise, it will return false. See the header of the
Filter function on line 14. It takes a slice of integers as a first parameter, and
a function fn of type func as a second parameter. The function func takes an
int type variable as a parameter and returns a bool value.
The function Filter returns a slice of integers that are even. We declare a
new slice of type int p , which will contain the filtered values (that are even)
from s at line 15. Then, in the next line, we have a for loop that will iterate
through all of the elements of s using range . For each element i of s , the
function fn will be called. If fn returns true, it means i is even; otherwise it
is false. If true is returned, we use the append function at line 18 to append i
to p . Once the loop is over, p is returned from the Filter function.
Let’s see the main function now. At line 7, we declare a slice of integers named
s . Then at line 8, we call the filter function with s as the first parameter
and even as the second parameter and store the result back in s . Printing s
at line 9 verifies the result.
That’s it about the solution. In the next lesson, you’ll study how strings, arrays,
and slices work together.
Applying Strings, Arrays and Slices
This lesson discusses relationship of slices with arrays and strings.
WE'LL COVER THE FOLLOWING
• Making a slice of bytes or runes from a string
• Making a substring of a string
• Changing a character in a string
• Searching and sorting slices and arrays
• Simulating operations with append
• Slices and garbage collection
Making a slice of bytes or runes from a string #
A string in memory is, in fact, a 2 word-structure consisting of a pointer to the
string data and the length. The pointer is completely invisible in Go-code. For
all practical purposes, we can continue to see a string as a value type, namely
its underlying array of characters. If s is a string (so also an array of bytes), a
slice of bytes c can immediately be made with
c:=[]byte(s)
This can also be done with the copy -function:
copy(dst []byte, src string)
The for range can also be applied as in the following program:
package main
import "fmt"
func main() {
s := "\u00ff\u754c"
for i, c := range s {
fmt.Printf("%d:%c ", i, c)
}
}
Strings with For range
We see that with range at line 6 the returned character c from s is in
Unicode, so it is a rune. Unicode-characters take 2 bytes; some characters can
even take 3 or 4 bytes. If erroneous UTF-8 is encountered, the character is set
to U+FFFD and the index advances by one byte.
In the same way as above, the conversion:
c:=[]int(s)
is allowed. Then, each int contains a Unicode code point, meaning that every
character from the string corresponds to one integer. Similarly, the
conversion to runes can be done with:
r:=[]rune(s)
The number of characters in a string s is given by len([]int(s)) . A string
may also be appended to a byte slice, like this:
var b []byte
var s string
b = append(b, s...)
Making a substring of a string #
The following line:
substr := str[start:end]
takes the substring from str from the byte at index start to the byte at index
end– 1 . Also, str[start:] is the substring starting from index start to
len(str) – 1, and str[:end] is the substring starting from index 0 to end – 1.
Changing a character in a string #
Strings are immutable. This means when str denotes a string then
str[index] cannot be the left side of an assignment:
str[i] = 'D'
Where i is a valid index, it gives the error cannot assign to str[i] .
To change a character, you first have to convert the string to an array of bytes.
Then, an array-item of a certain index can be changed, and the array must be
converted back to a new string. For example, change “hello” to “cello”:
s := "hello"
c := []byte(s) // converting string s to array of bytes `c`.
c[0] = 'c'
// modifying the c
s2 := string(c) // Converting c to string, s2 == "cello"
It is clear that string-extractions are very easy with the slice-syntax.
Searching and sorting slices and arrays #
Searching and sorting are very common operations, and the standard library
provides these in the package sort . To sort a slice of ints, import the package
sort and simply call the function func Ints(a []int) as:
sort.Ints(arr)
where arr is the array or slice to be sorted in ascending order. To test if an
array is sorted, use:
func IntsAreSorted(arr []int) bool
This returns true or false whether or not the array arr is sorted. Similarly,
for float64 elements, you use the following function from sort package :
func Float64s(a []float64)
and for strings, there is a function provided by sort package:
func Strings(a []string)
To search an item in an array or slice, the array must first be sorted (the
reason is that the search-functions are implemented with the binary-search
algorithm). Then you can use:
func SearchInts(a []int, n int) int
This searches for n in the slice a , and returns its index. Of course, the
equivalent functions for float64s and strings exist also:
func SearchFloat64s(a []float64, x float64) int // search for float64
func SearchStrings(a []string, x string) int // search for strings
Further details can be found in the official information.
Simulating operations with append #
The append method, as we studied previously, is very versatile and can be
used for all kinds of manipulations:
Append a slice b to an existing slice a :
a = append(a, b...)
Delete item at index i :
a = append(a[:i], a[i+1:]...)
Cut from index i till j out of slice a :
a = append(a[:i], a[j:]...)
Extend slice a with a new slice of length j :
a = append(a, make([]T, j)...)
Insert item x at index i :
a = append(a[:i], append([]T{x}, a[i:]...)...)
Insert a new slice of length j at index i :
a = append(a[:i], append(make([]T, j), a[i:]...)...)
Insert an existing slice b at index i :
a = append(a[:i], append(b, a[i:]...)...)
Pop highest element from stack:
x, a = a[len(a)-1], a[:len(a)-1]
Push an element x on a stack:
a = append(a, x)
So to represent a resizable sequence of elements use a slice and the append
function. A slice is often called a vector in a more mathematical context. If
this makes code clearer, you can define a vector alias for the kind of slice you
need.
Slices and garbage collection #
A slice points to the underlying array. This array could potentially be much
bigger than the slice. As long as the slice is referred to, the full array will be
kept in memory until it is no longer referenced. Occasionally, this can cause
the program to hold all the data in memory when only a small piece of it is
needed. For example, the following FindDigits function loads a file into
memory and searches it for the first group of consecutive numeric digits,
returning them as a new slice:
var digitRegexp = regexp.MustCompile("[0-9]+")
func FindDigits(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
return digitRegexp.Find(b)
}
This code works as described, but the returned [ ]byte points into an array
containing the entire file. Since the slice references the original array, as long
as the slice is kept around, the garbage collector can’t release the array; the
few useful bytes of the file keep the entire contents in memory.
To fix this problem one can copy the interesting data to a new slice before
returning it:
func FindDigits(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
b = digitRegexp.Find(b)
c := make([]byte, len(b))
copy(c, b) // copying b to c
return c
}
Now, the copy function is copying b to c ,which means slice is not referring
original array. In this case, garbage collector can release the array if needed.
As an example, look at the following program that traverses an array of
characters, and copies them to another array only if the character is different
from that which precedes it.
package main
import (
"fmt"
)
var arr []byte = []byte{'a','b','a','a','a','c','d','e','f','g'}
func main() {
arru := make([]byte,len(arr)) // this will contain consecutive non-repeating items
ixu := 0 // index in arru
tmp := byte(0)
for _, val := range arr {
if val!=tmp {
arru[ixu] = val
fmt.Printf("%c ", arru[ixu])
ixu++
}
tmp = val
}
}
Removing Consecutive Repeating Characters
In the code above, at line 6, we make a global array arr of type byte and
initialize it with some values. In main , we make an array arru of type byte
with the same length as arr via the make function at line 9. Now, we maintain
an index ixu to traverse through the array arr at line 10. In the next line, we
declare a byte type variable tmp to keep track of the previously visited
element of arr .
Now, we have a for loop at line 12 that will iterate len(arr) times. We add a
condition: if val!=tmp at line 13. The val contains the recently visited
variable and tmp contains the element visited before val . We set arr[uix] to
val because val holds a unique element. Then, it increments the index iux if
the condition is satisfied. Whether the condition is satisfied or not, we set val
to tmp to keep a record of the previously visited value before moving forward.
So, in the end, you’ll have all the unique values in arru .
Slices are interactive and flexible. We can use arrays and strings to make
slices work as explained in detail in this lesson. In the next lesson, you will
write a function to solve a coding problem.
Challenge: Bubble Sort the Slice
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Input
• Sample input
• Output
• Sample output
Problem statement #
Sort a slice of integers through a function that implements the Bubble sort
algorithm. Look up the definition of this algorithm here.
Input #
Unsorted slice
Sample input #
[4 5 2 1 3]
Output #
Sorted slice
Sample output #
[1 2 3 4 5]
Try to implement the function below. Feel free to view the solution, after
giving some shots. Good Luck!
package main
import "fmt"
import "strconv"
import "encoding/json"
func bubbleSort(sl []int) []int{
return nil
}
Bubble Sort
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Bubble Sort the Slice
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"fmt"
)
func main() {
sla := []int{2, 6, 4, -10, 8, 89, 12, 68, -45, 37}
fmt.Println("before sort: ",sla)
// sla is passed via call by value, but since sla is a reference type
// the underlying array is changed (sorted)
bubbleSort(sla)
fmt.Println("after sort: ",sla)
}
func bubbleSort(sl []int) {
// passes through the slice:
for pass:=1; pass < len(sl); pass++ {
// one pass:
for i:=0; i < len(sl) - pass; i++ {
// the bigger value 'bubbles
if sl[i] > sl[i+1] {
sl[i], sl[i+1] = sl[i+1], sl[i]
}
}
}
}
Bubble Sort
In the program above, in main at line 7, we make a slice sla of integers and
assign random integers to it in an unsorted manner. Now, at line 11, we call
the function bubbleSort and pass sla to it. Look at the header for bubbleSort
at line 15, it accepts a slice sl .
Now, according to the bubble sort algorithm, we have to pass through the slice
until it is sorted. In every pass, we take the element at index i and compare it
with the element at index i+1 . If the element at index i is greater than the
element at index i+1 , we swap them. Every pass makes sure that the greatest
value, not in its correct place, is placed at its correct position. That’s why loop
in every pass managing the iterator i should run len(sl)-pass times. Doing
this means that we need len(sl)-1 number of passes in maximum to sort the
slice. The last pass will sort two values after swap.
Look at line 17, where we write a for loop controlling the passes required to
sort the slice. We need len(sl)-1 maximum passes. For each pass, we’ll
iterate the slice until len(sl)-pass times (see line 19). During the iteration, if
the element at index i is greater than the element at index i+1 , we swap
them according to condition at line 20. Results are verified through line 8 and
line 12 in main by printing sla before sorting and after sorting respectively.
That’s it about the solution. In the next lesson, you’ll attempt another
challenge.
Challenge: Reverse a String
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Input
• Sample input
• Output
• Sample output
Problem statement #
Write a function that takes a string and reverses it.
Input #
A string
Sample input #
"Google"
Output #
Reversed string
Sample output #
"elgooG"
Try to implement the function below. Feel free to view the solution, after
giving some shots. Good Luck!
package main
import "fmt"
import "encoding/json"
func reverse(s string) string {
return ""
}
Reverse a String
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Reverse a String
This lesson discusses the solution to the challenge given in the previous lesson.
WE'LL COVER THE FOLLOWING
• 1st Variant
• 2nd Variant
• 3rd Variant
package main
import "fmt"
// variant: use of slice of byte and conversions
func reverse1(s string) string {
sl := []byte(s)
var rev [100]byte
j := 0
for i:=len(sl)-1; i >= 0; i-- {
rev[j] = sl[i]
j++
}
strRev := string(rev[:len(sl)])
return strRev
}
// variant: "in place" using swapping _one slice
func reverse2(s string) string {
sl2 := []byte(s)
for i, j := 0, len(sl2) - 1; i < j; i, j = i+1, j-1 {
sl2[i], sl2[j] = sl2[j], sl2[i]
}
return string(sl2)
}
//variant: using [] int for runes (necessary for Unicode-strings!):
func reverse3(s string) string {
runes := []rune(s)
n, h := len(runes), len(runes)/2
for i:= 0; i < h; i ++ {
runes[i], runes[n-1-i] = runes[n-1-i], runes[i]
}
return string(runes)
}
func main() {
// reverse a string:
str := "Google"
fmt.Printf("The reversed string using variant 1 is -%s-\n", reverse1(str))
fmt.Printf("The reversed string using variant 2 is -%s-\n", reverse2(str))
fmt.Printf("The reversed string using variant 3 is -%s-\n", reverse3(str))
}
Reverse a String
In the above code, there are three basic functions: reverse1 , reverse2 , and
reverse3 . Let’s study them one by one.
1st Variant #
Look at the header of function reverse1 at line 5: func reverse1(s string)
string . It takes a string s as a parameter to reverse it and returns the
reversed string. This variant is the implementation of a slice of bytes,
converting it later into a string.
At line 6, we make a slice of bytes sl , containing all the elements from string
s passed to the function reverse1 . Next, we make an array rev of byte type
of length 100 (assuming that the length of the string s doesn’t exceed 100). At
line 8, we make an iterator j that we will use to traverse through the rev
array as we move forward, filling rev with values.
Then we have a for loop at line 9. You may have noticed that the iterator i of
this loop is initialized with len(sl)-1 . It will move in a backward direction
until we reach the 0th index. This is because we have to reverse the string; the
idea is to read sl in a backward direction and store each character in rev in
the forward direction. That’s the reason we have a separate iterator j for rev
that starts from 0. At line 10, we set rev[j] as sl[i] for each iteration and
increment j by 1 at line 11 to move one index forward in rev . By the end of
the for loop, we’ll have the reversed string in rev .
But, there is one problem. Remember we set the length of rev to 100 bytes.
What if all the bytes are not filled with values and are nill? For this, you have
to reslice rev as: rev[:len(sl)] (see line 13) because it is obvious that the
actual length of rev is equal to sl . Don’t forget to convert the rev type to
string, before returning it. The reverse1 returns a string type variable.
2nd Variant #
Now, look at the header of the second variant reverse2 at line 18: func
reverse2(s string) string . It takes a string s as a parameter to reverse it and
returns the reversed string. This variant is the implementation of using just
one slice of bytes, and it does in-place swapping, unlike reverse1 , converting
it later into a string.
At line 19, we make a slice of bytes sl2 , containing all the elements from
string s passed to the function reverse2 . Then, we have a for loop at line 20.
As we are doing in-place swapping, we make two iterators i and j in a single
for loop. The idea is the same. One iterator will move in the forward direction
where the second iterator will move in the backward direction. You may have
noticed that iterator i of this loop is initialized with 0 which means it will
move in the forward direction and will change the values of sl2 . The iterator
j is initialized with len(sl2)-1 and will move in the backward direction
because it will read the values from sl2 , which are to be placed by i in the
opposite direction.
We’ll do this in-place swapping until i and j don’t coincide which means:
i<j . Because when i is equal to j , it means the string is reversed, as i and
j are incremented and decremented by 1 respectively. However, the question
is how to do in-place swapping? Look at line 21, we set sl2[i] as sl2[j] , and
sl2[j] as sl2[i] . Let’s visualize it. Suppose we have a string of length 5. Let’s
do some in-place swapping:
i=0
j=4
H
e
l
l
o
In place Swapping to Reverse
1 of 6
i=0
j=4
H
e
l
l
o
In place Swapping to Reverse
2 of 6
i=0
j=4
o
e
l
l
H
In place Swapping to Reverse
3 of 6
i=1
j=3
o
e
l
l
H
In place Swapping to Reverse
4 of 6
i=1
j=3
o
l
l
e
H
In place Swapping to Reverse
5 of 6
i=3
j=3
o
l
l
e
H
In place Swapping to Reverse
6 of 6
By the end of the for loop, we’ll have the reversed string in sl2 . Don’t forget
to convert the sl2 's type from byte to string before returning it because
reverse2 returns a string type variable.
3rd Variant #
Now, look at the header of the third variant reverse3 at line 27: func
reverse3(s string) string . It takes a string s as a parameter to reverse and
returns the reversed string. This variant is the implementation of using []int
for runes , in case the string is a Unicode string, converting rune later into a
string.
At line 28, we make a slice of runes called runes , containing all the elements
from string s passed to the function reverse3 . In the next line (line 29), we
declare two variables n and h . We set n as the length of runes: len(runes)
and h as half the length of runes : len(runes)/2 . Now, we’ll conduct in-place
swapping just like we did in the case of reverse2 but with a single iterator. We
make a for loop at line 30. We make an iterator i that starts with 0 and will
move until the h-1 index (half the length of runes ).
You may have noticed that in in-place swapping we only have to forward half
the length of the string. So the iterator i will be incremented by 1 after each
iteration. However, the question is how to do in-place swapping with just one
iterator? Remember we made a variable n at line 29 previously. We’ll use a
combination of i and n in this variant. Look at line 31. It’s obvious that after
reversing a string the first character comes in the last place and the last
character comes in the first place. We set runes[i] to runes[n-1-i] , and
runes[n-1-i] to runes[i] .
Let’s suppose we have a string of length 5, so n would be 5. In the first
iteration, i would be 0. So, runes[0] would be equal to runes[4] , which
means that the first element now has the value of the last element, and
runes[4] would be equal to runes[0] , which means that the last element now
has the value of the first element. Line 31 will run for each iteration until i
equals h . By the end of the for loop, we’ll have the reversed string in runes .
Don’t forget to convert runes’ type to string before returning it because
reverse3 returns a string type variable.
Let’s see main now. In main , at line 38, we made a string str and set its value
to Google. We’ll reverse this string, to see whether our three variants work or
not. We call each of the variants in the main to verify the results. Line 40
prints the string returned by reverse1 . Line 41 prints the string returned by
reverse2 . Line 42 prints the string returned by reverse3 . These three
statements print elgooG, which is the reversed form of Google.
That’s it about the solution. There is a quiz in the next lesson for you to solve.
Quiz: Deduce the Outputs
In this lesson your concepts will be evaluated through a quiz.
Choose one correct answer
1
What is the output of the following code snippet?
a := [...]string{"a", "b", "c", "d"}
for i := range a {
fmt.Println("Array item", i, "is", a[i])
}
COMPLETED 0%
1 of 12
We hope that you performed well in the quiz. That’s it about the arrays and
slices. In the next chapter, we’ll discuss maps in Go.
Declaration and Initialization
This lesson describes the important concepts regarding maps, i.e., how to use, declare and initialize them.
WE'LL COVER THE FOLLOWING
• Introduction
• Concept
• Map capacity
• Slices as map values
Introduction #
Maps are a special kind of data structure: an unordered collection of pairs of
items, where one element of the pair is the key, and the other element,
associated with the key, is the data or the value. Hence they are also called
associative arrays or dictionaries. They are ideal for looking up values, and
given the key, the corresponding value can be retrieved very quickly. This
structure exists in many other programming languages under other names
such as Dictionary ( dict in Python), hash , HashTable and so on.
Concept #
A map is a reference type and declared in general as:
var map1 map[keytype]valuetype
For example:
var map1 map[string]int
(A space is allowed between [keytype] and valuetype , but gofmt removes it.)
The length of the map doesn’t have to be known at the declaration, which
means a map can grow dynamically. The value of an uninitialized map is nil.
The key type can be any type for which the operations == and != are defined,
like string, int, and float. For arrays and structs, Go defines the equality
operations, provided that they are composed of elements for which these
operations are defined using element-by-element comparison. So arrays,
structs, pointers, and interface types can be used as key type, but slices cannot
because equality is not defined for them. The value type can be any type.
Maps are cheap to pass to a function because only a reference is passed (so 4
bytes on a 32-bit machine, 8 bytes on a 64-bit machine, no matter how much
data they hold). Looking up a value in a map by key is fast, much faster than a
linear search, but still around 100x slower than direct indexing in an array or
slice. So, if performance is very important try to solve the problem with slices.
If key1 is a key value of map map1 , then map1[key1] is the value associated
with key1 , just like the array-index notation (an array could be considered as
a simple form of a map, where the keys are integers starting from 0). The
value associated with key1 can be set to (or if already present changed to)
val1 through the assignment:
map1[key1] = val1
The assignment:
v:= map1[key1]
stores the value in v corresponding to key1 . If key1 is not present in the map,
then v becomes the zero-value for the value type of map1 .
As usual len(map1) gives the number of pairs in the map, which can grow or
diminish because pairs may be added or removed during runtime.
Maps are reference types, as memory is allocated with the make function.
Initialization of a map is done like:
var map1 map[keytype]valuetype = make(map[keytype]valuetype)
or shorter with:
map1 := make(map[keytype]valuetype)
Run the following program to see how maps are made in Go.
package main
import "fmt"
func main() {
var mapLit map[string]int
// making map
var mapAssigned map[string]int
mapLit = map[string]int{"one": 1, "two": 2}
// adding key-value pair
mapCreated := make(map[string]float32)
// making map with make()
mapAssigned = mapLit
mapCreated["key1"] = 4.5
// creating key-value pair for map
mapCreated["key2"] = 3.14159
mapAssigned["two"] = 3
// changing value of already existing key
fmt.Printf("Map literal at \"one\" is: %d\n", mapLit["one"])
fmt.Printf("Map created at \"key2\" is: %f\n", mapCreated["key2"])
fmt.Printf("Map assigned at \"two\" is: %d\n", mapLit["two"])
fmt.Printf("Map literal at \"ten\" is: %d\n", mapLit["ten"])
}
Make Maps
In the code above, in main at line 5, we make a map mapLit . The declaration
of mapLit shows that its keys will be of string type and the values associated
with its keys will be of int type. Similarly, at line 6, we make a map
mapAssigned . The declaration of mapAssigned shows that its keys will also be of
string type and the values associated with its keys will be of int type too. In the
next line, we assign values to mapLit . You can see we make two keys named
one and two . The values associated with one is 1 and with two is 2.
At line 8, we create a map mapCreated using the make function, which is
equivalent to mapCreated := map[string]float{} . The declaration of
mapCreated shows that its keys will be of string type and the values associated
with its keys will be of float32 type. Then in the next line, we assign
mapAssigned with the map of mapLit , which means that mapLit and
mapAssigned now possess the same pairs.
At line 10 and line 11, we are making key-value pairs (each pair line by line)
for mapCreated . At line 10 we create key key1 and give the value 4.5 to it. At
line 11 we create the key key2 and give the value 3.14159 to it. Now at line 12,
we are changing the value associated with the key of mapAssigned , already
present before. We are giving the key two a new value of 3.
Now, from line 13 to line 16, we are printing certain values to verify the maps’
behaviors. At line 13, we are printing the value associated with key one of
mapLit which is 1. In the next line, we are printing the value associated with
key key2 of mapCreated , which is 3.14159. At line 15, we are printing the value
associated with key two of mapLit , which is 3 (because of reference, as the
value changes at line 12). In the last line, we are trying to print the value
associated with the key ten of mapLit , which doesn’t even exist. So 0 will be
printed because mapLit contains int value types, and the default value for an
integer is 0.
Note: Don’t use new , always use make with maps.
If you by mistake allocate a reference object with new() , you receive a pointer
to a nil reference, equivalent to declaring an uninitialized variable and taking
its address:
mapCreated := new(map[string]float)
Then we get in the statement:
mapCreated["key1"] = 4.5
the compiler error: invalid operation: mapCreated["key1"] (index of type
*map[string] float) .
To demonstrate that the value can be any type, here is an example of a map
that has a func() int as its value:
package main
import "fmt"
func main() {
mf := map[int]func() int{ // key type int, and value type func()int
1: func() int { return 10 },
2: func() int { return 20 },
5: func() int { return 50 },
}
fmt.Println(mf)
}
Value Type of Maps
In the code above, in main at line 5, we make a map mf . The declaration of mf
shows that its keys will be of int type and the values associated with its keys
will be of func() int type. From line 6 to line 8, we are making key-value pairs
for mf . At line 6 we create a key 1 (of type int) and give the value: func() int
{ return 10 } ( a function returning 10) to it. At line 7, we create the key 2 (of
type int) and give the value: func() int { return 20 } ( a function returning
20) to it. At line 8, we create key 5 (of type int) and give the value: func() int
{ return 50 } ( a function returning 50) to it. On printing the map at line 10,
you’ll notice that int type keys are mapped to the address of the functions
which are assigned to them.
Map capacity #
Unlike arrays, maps grow dynamically to accommodate new key-values that
are added; they have no fixed or maximum size. However, you can optionally
indicate an initial capacity cap for the map, as in:
make(map[keytype]valuetype, cap)
For example:
map2 := make(map[string]float, 100)
When the map has grown to its capacity, and a new key-value is added, then
the size of the map will automatically increase by 1. So for large maps or maps
that grow a lot, it is better for performance to specify an initial capacity; even
if this is only known approximately. If you don’t specify the initial capacity, a
default capacity of 8 bytes is taken (on a 64-bit machine). Here is a more
concrete example of a map. Map the name of a musical note to its frequency in
Hz:
noteFrequency := map[string]float32{
"C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83, "G0": 24.50, "A0": 27.
50, "B0": 30.87, "A4": 440}
In the above snippet, noteFrequency is a map. The declaration of
noteFrequency shows that its keys will be of string type and values associated
with its keys will be of float32 type. The initialization is done along with the
declaration. For example, the C0 key is given value 16.35, the D0 key is given
value 18.35 and so on.
Slices as map values #
When a key has only one associated value, the value can be a primitive type.
What if a key is associated with many values? For example, when we have to
work with all the processes on a Unix-machine where a parent process as key
(process-id pid is an int value) can have many child processes (represented as
a slice of ints with their pid’s as items). This can be elegantly solved by
defining the value type as a [ ]int or a slice of another type. Here are some
examples defining such maps:
mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)
Now, you are familiar with the use of maps. In the next lesson, you’ll study
how to examine values present in maps independently.
Existence of Key-Value Item
This lesson focuses on two major concepts: how to nd a value associated with a key and how to delete a keyvalue pair from a map.
WE'LL COVER THE FOLLOWING
• Testing the existence of a key
• Deleting an element with a key
Testing the existence of a key #
We saw in the previous lesson that val1 = map1[key1] returns the value val1
associated with key1 . If key1 does not exist in the map, val1 becomes the
zero-value for the value’s type, but this is ambiguous. Now we can’t
distinguish between this case or the case where key1 does exist and its value
is the zero-value! In order to test this, we can use the following comma ok
form:
val1, isPresent = map1[key1]
The variable isPresent will contain a Boolean value. If key1 exists in map1 ,
val1 will contain the value for key1 , and isPresent will be true. If key1 does
not exist in map1 , val1 will contain the zero-value for its type, and isPresent
will be false.
If you just want to check for the presence of a key and don’t care about its
value, you could write:
_, ok := map1[key1] // ok == true if key1 is present, false otherwise
Or combined with an if :
if _, ok := map1[key1]; ok {
// ...
}
Deleting an element with a key #
This is done with:
delete(map1, key1)
When key1 does not exist, this statement doesn’t produce an error.
Both the techniques are implemented in the following program.
package main
import "fmt"
func main() {
var value int
var isPresent bool
map1 := make(map[string]int)
map1["New Delhi"] = 55
map1["Beijing"] = 20
map1["Washington"] = 25
value, isPresent = map1["Beijing"]
// checking existence of a key
if isPresent {
fmt.Printf("The value of \"Beijing\" in map1 is: %d\n", value)
} else {
fmt.Println("map1 does not contain Beijing")
}
value, isPresent = map1["Paris"]
// chekcing existence of a key
fmt.Printf("Is \"Paris\" in map1 ?: %t\n", isPresent)
fmt.Printf("Value is: %d\n", value)
// delete an item:
delete(map1, "Washington")
value, isPresent = map1["Washington"] // checking existence of a key
if isPresent {
fmt.Printf("The value of \"Washington\" in map1 is: %d\n", value)
} else {
fmt.Println("map1 does not contain Washington")
}
}
Finding and Deleting a Key from Map
In the above code, in main at line 7, we made a map map1 . The declaration of
map1 shows that its keys will be of string type and values associated with its
keys will be of int type. Now to check the presence of a key in this map, we
made two variables: value of type int (at line 5) to get the value associated
with that key and isPresent of type bool (at line 6) to check whether that key
exists or not.
From line 8 to 10, we are making key-value pairs (each pair line by line) for
map1 . At line 8, we create a key New Delhi and give the value 55 to it. At line
9, we create a key Beijing and give the value 20 to it. At line 10, we create a
key Washington and give the value 25 to it.
Now at line 11, we are checking the existence of the key Beijing in map1 as:
value, isPresent = map1["Beijing"] . We know that Beijing does exist in
map1 so value will get 20, and isPresent will become true , causing the
condition at line 13 to become true. Consequently, The value of “Beijing” in
map1 is: 20 will be printed on the screen. Now at line 18, we are checking the
existence of the key Paris in map1 as: value, isPresent = map1["Paris"] . We
know that Paris does not exist in map1 so value will get 0, and isPresent will
become false , causing line 19 to print Is “Paris” in map1 ?: false. Line 20
will print Value is: 0.
At line 23, we are deleting a key Washington from map1 . The execution of this
line doesn’t tell whether the key was deleted or not. So in the next line, we are
verifying the existence of Washington in map1 as: value, isPresent =
map1["Washington"] . We know that Washington does not exist in map1 anymore
after deletion, so value will get 0, and isPresent will become false , causing
the condition at line 26 to become false. The control will transfer to line 28,
and map1 does not contain Washington will be printed on the screen.
That’s it about the existence of a value in a map, in the next lesson, you’ll see
how to apply the for construct on maps.
The for range Construct
This lesson explains how to use the for range construct to access key-value pairs in a map.
WE'LL COVER THE FOLLOWING
• Explanation
• Implementation
Explanation #
The following construct can also be applied to maps:
for key, value := range map1 {
...
}
The key is the key of the map, and the value is the value for the key . They
are local variables only known in the body of the for statement. If you are
only interested in the values, use the form:
for _, value := range map1 {
fmt.Printf("Value is: %d\n", value)
}
To get only the keys, you can use:
for key := range map1 {
fmt.Printf("Key is: %d\n", key)
}
The order in which elements are visited when iterating over a map using a for
range statement is unpredictable, even if the same loop is run multiple times
with the same map. The first element in a map iteration is chosen at random.
This behavior allows the map implementation to ensure better map
balancing. Your code should not assume that the elements are visited in any
particular order.
Implementation #
Here is an example of a program that uses a for loop on a map.
package main
import "fmt"
func main() {
map1 := make(map[int]float32)
map1[1] = 1.0
map1[2] = 2.0
map1[3] = 3.0
map1[4] = 4.0
// for range
for key, value := range map1 {
fmt.Printf("key is: %d - value is: %f\n", key, value)
}
}
For range on a Map
In the code above, in main , at line 5, we make a map map1 . The declaration of
map1 shows that its keys will be of int type and values associated with its keys
will be of float32 type. From line 6 to line 9, we are making key-value pairs
(each pair line by line) for map1 . At line 6, we create a key 1 and give the
value 1.0 to it. At line 7, we create a key 2 and give the value 2.0 to it. At line
8, we create a key 3 and give the value 3.0 to it. At line 9, we create a key 4
and give the value 4.0 to it. Now, we have a for loop at line 11. It’s reading all
the key-value pairs from map1 and printing a key-value pair in each iteration.
The order of output may change every time you run the program.
We see that a map is not key-ordered, neither is it sorted on the values. It is
even safe to delete pairs from a map while iterating over the map like:
package main
import "fmt"
func main() {
map1 := make(map[int]float32)
map1[1] = 1.0
map1[2] = 2.0
map1[3] = 3.0
map1[4] = 4.0
for key := range map1 {
if key > 3 {
delete(map1, key)
// deleting keys greater than and equal to 4
}
}
// printing the modified map
for key, value := range map1 {
fmt.Printf("key is: %d - value is: %f\n", key, value)
}
}
In the code above, in main at line 5, we made a map map1 . The declaration of
map1 shows that its keys will be of int type and values associated with its keys
will be of float32 type. From line 6 to line 9, we are making key-value pairs
(each pair line by line) for map1 . At line 6, we create a key 1 and give the
value 1.0 to it. At line 7, we created a key 2 and gave the value 2.0 to it. At
line 8, we create a key 3 and give the value 3.0 to it. At line 9, we create a key
4 and give the value 4.0 to it.
Now we have a for loop at line 11. It’s reading all the key-value pairs from
map1 and deleting the key-value pairs for which the key is greater than 3
using the delete function.
Then again, we have a for loop at line 17 that is printing modified map1 .
Now that you are familiar with accessing the keys and values from the maps
using for loop, in the next lesson, you have to write a program to solve a
problem.
Challenge: Map the Days
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Input
• Sample input
• Output
• Sample output
Problem statement #
Make a map to hold together the number of days in a week (1 -> 7) with its
name. Make sure to follow the following key-value pair pattern (KEY: VALUE):
1:
2:
3:
4:
5:
6:
7:
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
Then write a function that takes a key and returns the value associated with it.
If a non-existent key is given, the function should return the message: Wrong
Key.
Input #
An integer
Sample input #
4
Output #
A string
Sample output #
Thursday
Try to implement the function below. Feel free to view the solution, after
giving some shots. Good Luck!
package main
import "fmt"
import "strconv"
import "encoding/json"
var Days = map[int]string{} // do initialization here
func findDay(n int) string {
return ""
}
Map the Days
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Map the Days
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"fmt"
)
var Days = map[int]string{
1:"Monday",
2:"Tuesday",
3: "Wednesday",
4: "Thursday",
5: "Friday",
6: "Saturday",
7: "Sunday"}
func findDay(n int) string {
val,isPresent := (Days[n])
if isPresent{
// what if key is not present
return val
}else{
return "Wrong Key"}
// return wrong key if invalid key
}
func main() {
n := 4
fmt.Println(n,":",findDay(n))
n = 0
fmt.Println(n,":",findDay(n))
}
Map the Days
In the above code, outside main at line 6, we make a map Days . The
declaration of Days shows that its keys will be of int type and values
associated with its keys will be of string type. Initialization is as follows:
Key 1 is given Monday as a value.
Key 2 is given Tuesday as a value.
Key 3 is given Wednesday as a value.
Key 4 is given Thursday as a value.
Key 5 is given Friday as a value.
Key 6 is given Saturday as a value.
Key 7 is given Sunday as a value.
Now look at the header of the function findDay at line 15: func findDay(n
int) string . This function is taking n integer as a parameter and returning a
string type value. We find the value against key n and store it in the variable
val . We declare another bool variable isPresent to verify the existence of the
key n in Days . If isPresent is true, it means the key n does exist in the map
Days . If isPresent is false, it means the key n doesn’t exist in the map Days .
If isPresent is true, condition at line 17 will be evaluated and the value of
key n that is val will be returned from the function. But if isPresent is false,
condition at line 19 will be evaluated and the string Wrong Key will be
returned from the function.
Let’s see main now. At line 24 we declare a variable n and initialize it with 4.
In the next line, we call findDay with n as a parameter. We are printing the
result to verify the output. Thursay will be returned and printed on the
screen when n is 4. At line 26, we change the value of n to 0. In the next line,
we call findDay with n as a parameter. Wrong Key will be returned and
printed on the screen when n is 0 because 0 is an invalid key.
That’s it for the solution. In the next lesson, you’ll study how maps and slices
work together.
A Slice of Maps
This lesson discusses how to make slices of maps.
WE'LL COVER THE FOLLOWING
• Explanation
Explanation #
Suppose we want to make a slice of maps. We must use make() two times, first
for the slice, then for each of the map-elements of the slice. To access a specific
key-value pair from a map, you have to use an iterator to specify which map
from the slice of maps is required.
For example, if we have a slice of maps called maps , and we want to set a
value v with key 1 from map i , we’ll do something as follows:
maps[i][1] = v
Simply writing maps[1] = v won’t work, because it will initialize mapvariables, and will be lost on the next iteration.
Look at the following implementation of how to make a slice of maps in Go.
package main
import (
"fmt"
)
func main() {
// Version A:
items := make([]map[int]int, 5)
for i := range items {
items[i] = make(map[int]int, 1)
items[i][1] = 2 // This 'item' data will not be lost on the next iteration
}
fmt.Printf("Version A: Value of items: %v\n", items)
// Version B: NOT GOOD!
items2 := make([]map[int]int, 5)
for _, item := range items2 {
item = make(map[int]int, 1) // item is only a copy of the slice element.
item[1] = 2 // This 'item' will be lost on the next iteration.
}
fmt.Printf("Version B: Value of items: %v\n", items2)
}
Slice of Maps
In the code above, in main at line 9, we make a slice of maps: items :=
make([]map[int]int, 5) . The declaration of items shows that it contains 5
maps. For each map, its keys will be of int type and the values associated with
its keys will also be of int type. Now, there is a for loop at line 10, which
iterates through all 5 places and places a map with the key 1 ( at line 11). For
every map, the value 2 is assigned with the key 1 ( at line 12) as: item[i][1] =
2.
Now let’s try a separate version. In the code above, in main at line 19, we
make a slice of maps as: items2 := make([]map[int]int, 5) . The declaration of
items2 shows that it contains 5 maps. For each map, its keys will be of int type
and values associated with its keys will also be of int type. Now, there is a for
loop at line 20, which iterates through all 5 places and places a map with the
key 1 ( at line 21). For every map, the value 2 is assigned with the key 1 (at
line 22) as : item[1] = 2 . The value will be lost in the next iteration because
item is the copy of the slice; proper indexing is required as done in Version 1
at line 12.
That’s how you can make slices of the type map and access them when
needed. The next lesson covers some more concepts on modifying the maps.
Sorting and Inverting a Map
This lesson covers important details on sorting and inverting a map.
WE'LL COVER THE FOLLOWING
• Sorting a map
• Inverting a map
Sorting a map #
By default, a map is not sorted, not even on the value of its keys. If you want a
sorted map, copy the keys (or values) to a slice, sort the slice (using the sort
package), and print out the keys and/or values using the for-range on the slice.
This is illustrated in the following program:
package main
import (
"fmt"
"sort"
)
var (
barVal = map[string]int{"alpha": 34, "bravo": 56, "charlie": 23, "delta": 87,
"echo": 56, "foxtrot": 12, "golf": 34, "hotel": 16, "indio": 87, "juliet": 65, "kilo":
43, "lima": 98}
)
func main() {
fmt.Println("unsorted:")
for k, v := range barVal {
fmt.Printf("key: %v, value: %v / ", k, v)
// read random keys
}
keys := make([]string, len(barVal))
// storing all keys in separate slice
i := 0
for k := range barVal {
keys[i] = k
i++
}
sort.Strings(keys) // sorting the keys slice
fmt.Println()
fmt.Println("\nsorted:")
for _, k := range keys {
fmt.Printf("key: %v, value: %v / ", k, barVal[k])
// reading key from keys and valu
}
}
Sorting a Map
In the code above, outside main at line 4, we import sort package to perform
sorting, and at line 7, we make a map barVal . The declaration of barVal
shows that its keys will be of string type and values associated with its keys
will be of int type. The initialization is done at the same line. For example, the
key alpha gets value 34, bravo get 56, charlie gets 23 and so on.
In main , we have a for-loop at line 15. This loop prints a key and the value
associated with each key in every iteration, until all key-value pairs are not
printed. The output of this for loop will be unsorted, as maps’ keys are read
randomly.
Now at line 18, we make a slice of string keys of length equal to barVal 's
length ( total number of keys in barVel ). Then we make the for loop at line 20
that reads keys from barVel in random order and stores them one by one in
keys ( at line 21). By the end of for loop, all keys will be present in the keys
slice. There is a great chance that the keys from barVal will not be read in
sorted (alphabetical) order and will be stored in the keys slice. So at line 24,
we perform in-place sorting on the strings present in keys using:
sort.Strings(keys) .
Again we have a for-loop at line 27. This loop prints a key and the value
associated with each key in every iteration until all key-value pairs are not
printed. It reads the key from keys and store it in k . Then read the value
from barVal as: barVal[k] .The output of this for loop will be sorted, as
barVal 's keys are read from keys slice (containing sorted strings).
Inverting a map #
Inverting a map we mean switching the values and keys. If the value type of a
map is acceptable as a key type and the map values are unique, this can be
done easily. Look at the following program to see how a map can be inverted.
package main
import (
"fmt"
)
var (
barVal = map[string]int{"alpha": 34, "bravo": 56, "charlie": 23, "delta": 87,
"echo": 56, "foxtrot": 12, "golf": 34, "hotel": 16, "indio": 87, "juliet": 65, "kilo": 43,
"lima": 98}
)
func main() {
invMap := make(map[int]string, len(barVal)) // interchanging types of keys and values
for k, v := range barVal {
invMap[v] = k
// key becomes value and value becomes key
}
fmt.Println("inverted:")
for k, v := range invMap {
fmt.Printf("key: %v, value: %v / ", k, v)
}
}
In the above code, outside main at line 6, we make a map barVal . The
declaration of barVal shows that its keys will be of string type and values
associated with its keys will be of int type. The initialization is done at the
same line. For example, the key alpha gets value 34, bravo get 56, charlie
gets 23 and so on.
In main , at line 13, we make a map invMap . The declaration of invMap shows
that its keys will be of int type and values associated with its keys will be of
string type. The length of invMap is the same as barVal , and the map invMap is
the inverse of barVal . That is why the type of keys and values are
interchanged between them.
Then we have the for loop at line 14, where we read barVal key in k and
value for each k in v for each iteration. To invert a map, the key becomes
value and the value becomes key. This means that invMap[v]=k ( see line 15)
will serve the purpose. As v was the value associated to k key of barVal , to
invert the map k should be the value associated with v key of invMap . Again
we have a for-loop at line 18. This loop prints a key and the value associated
with each key in every iteration until all key-value pairs are not printed.
Because keys must be unique, the code goes wrong when the original value
items are not unique. In that case, no error occurs, but the inverted map will
not contain all pairs from the original map. One solution is to carefully test for
uniqueness and using a multi-valued map. In this case, use a map of type
map[int][]string.
Look at the following code, and see how uniqueness is tested.
package main
import (
"fmt"
)
var (
barVal = map[string]int{"alpha": 34, "bravo": 56, "charlie": 23, "delta": 87,
"echo": 56, "foxtrot": 12, "golf": 34, "hotel": 16, "indio": 87, "juliet": 65, "kilo": 43,
"lima": 98}
)
func main() {
invMap := make(map[int][]string, len(barVal)) // interchanging types of keys and values
for k,v := range barVal{
var a [1]string // making an array to hold values for a key in invMap
a[0]=k // placing key into array before passing directly into invMap
_, isPresent := invMap[v] // checking existence
if isPresent{ // if already present
invMap[v] = append(invMap[v],",") // add comma before adding another value
}
invMap[v] = append(invMap[v],a[0])
// key becomes value and value becomes key
}
fmt.Println("inverted:")
for k, v := range invMap {
fmt.Printf("key: %v, value: %v / ", k, v)
}
}
Now, you’re familiar with sorting and inverting a map, there is a quiz in the
next lesson for you to solve.
Quiz: Deduce the Outputs
In this lesson your concepts will be evaluated through a quiz.
Choose one correct answer.
1
What is the output of the following code snippet?
capitals := map[string] string {"France":"Paris", "Italy":"Ro
me", "Japan":"Tokyo" }
fmt.Println(capitals["Tokyo"])
fmt.Println(capitals["Italy"])
fmt.Println(capitals["Paris"])
COMPLETED 0%
1 of 4
We hope that you performed well in the quiz. That’s about the maps. In the
next chapter, we’ll discuss packages in Go.
Overview of the Standard Library
This lesson emphasizes the importance of packages and provides one-line descriptions of some famous packages
from the standard Go library.
WE'LL COVER THE FOLLOWING
• Introduction
• Common packages
Introduction #
The Go-distribution contains over 250 standard built-in
packages for common functionality, like fmt , os , … ,
designated as the standard library written in the Go
language itself (besides some low-level routines). See the
documentation here. The API in all packages (included
package os ) is the same for all systems (Windows, Linux, …);
the only package that is different for each system is syscall .
In the examples and exercises throughout the course, we use
the packages of the standard library.
Common packages #
Here we will discuss the general purpose of a number of common packages
grouped by function; we will not go into detail about their inner structure.
unsafe : contains commands to step out of the Go type-safety which is not
needed in normal programs. It can be useful when interfacing with
C/C++.
os : gives a platform-independent interface to operating-system
functionality. Its design is Unix-like. It hides the differences between
various operating systems to give a consistent view of files and other OSobjects.
os/exec : gives the possibility to run external OS commands and
programs.
syscall : this is the low-level, external package, which provides a
primitive interface to the underlying OS’s calls.
archive/tar and /zip – compress : contains functionality for
(de)compressing files.
fmt : contains functionality for formatted input-output.
io : provides basic input-output functionality, mostly as a wrapper
around os-functions.
bufio : wraps around io to give buffered input-output functionality.
path/filepath : contains routines for manipulating filename paths
targeted at the OS used.
flag : contains functionality to work with command-line arguments.
strings : contains functions for manipulating and processing strings.
strconv : converts strings to basic data types.
unicode : contains special functions for Unicode characters.
regexp : provides sophisticated pattern-searching functionalities for
strings.
bytes : contains functions for the manipulation of byte slices.
index/suffixarray : contains methods for very fast searching in strings.
math : contains the basic mathematical constants and functions.
math/cmplx : methods for manipulating complex numbers.
math/rand : contains pseudo-random number generators.
sort : contains functionality for sorting arrays and user-defined
collections.
math/big : contains multi-precision arithmetic methods for working with
arbitrarily large integers and rational numbers.
container : contains sub-packages that implement containers for
manipulating collections, for example:
list : for working with doubly-linked lists.
ring : for working with circular lists.
time : contains basic functionalities for working with times and dates.
log : contains functionalities for logging information in a running
program. We’ll use it throughout examples in the following chapters.
encoding/json : implements the functions for reading/decoding as well as
writing/encoding data in JSON format.
encoding/xml : this is a simple XML 1.0 parser for examples of JSON and
XML.
text/template : use this package to make data-driven templates that can
generate textual output mixed with data, like HTML.
net : contains basic functions for working with network data.
http : contains functionality for parsing HTTP requests/replies, provides
an extensible HTTP server and a basic client.
html : this is a parser for HTML5.
crypto – encoding – hash - ... : these form a multitude of packages for
encrypting and decrypting data.
runtime : contains operations for interacting with the Go-runtime, such as
the garbage collection and goroutines.
reflect : implements runtime introspection, allowing a program to
manipulate variables with arbitrary types.
These were some common packages used in a Go program. In the next lesson,
you have to write a program to solve a problem.
Challenge: Construct a Double-Linked List
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Input
• Sample Input
• Output
• Sample Output
Problem statement #
Write a function that takes an integer n as a parameter, creates a doublelinked list and returns it after adding values to it from 1 to n .
Input #
An integer
Sample Input #
5
Output #
Double-linked list
Sample Output #
[1,2,3,4,5]
Try to implement the function below. Feel free to view the solution, after
giving it a few shots. Good Luck!
package main
import "fmt"
import "strconv"
import "encoding/json"
import "container/list"
func insertListElements(n int)(*list.List){
lst := list.New()
// add elements in list from 1 to n
return lst
}
Double Linked List
We hope that you were able to solve the challenge. Next lesson brings you the
solution of this challenge.
Solution Review: Construct a Double-Linked List
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"container/list"
"fmt"
)
func insertListElements(n int)(*list.List){
// add elements in list from 1 to n
lst := list.New()
for i:=1;i<=n;i++{
lst.PushBack(i)
// insertion here
}
return lst
}
func main() {
n := 5
// total number of elements to be inserted
myList := insertListElements(n) // function call
for e := myList.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value) // printing values of list
}
}
Double Linked List
In the code above, at line 4, we import a package container/list because it is
used for the implementation of doubly-linked lists. Look at the header of
function insertListElements at line 8: func insertListElements(n int)
(*list.List) . This function takes a parameter n and is returning a double-
linked list. At line 9, we are making a new but empty doubly-linked list lst
using list.New() function. Now we have a for loop that will run n times. The
iterator i starts from 1 and ends at n because we have to add integers from 1
to n . Line 11 is inserting values in lst in every iteration as: lst.PushBack(i) .
By the end of the for loop, n values will be inserted, and we return lst from
the function.
Now, look at the main function. We declare a variable n and initialize it with
5. Remember n of main and n of insertListElements function are different
variables because they have different scope. In the next line (line 18), we call
the function insertListElements with n as a parameter, and store the return
value from the function in myList list. Then we have a for loop at line 19 to
verify the insertions. We have e as an iterator. The iterator e acts as a node.
It’s obvious that we’ll start from the front of the node and will visit all the
nodes until we reach a nill value (end of the list).
The package container/list provides a function Front() , which returns the
starting point of a list. So e was initialized with myList.Front() . The loop will
continue until we reach nill . The question is how to move across the list.
Previously in this course using an integer iterator, we simply increment or
decrement it by 1 or any other integer. In the case of e acting as a node, this is
not possible. Again the package container/list comes to rescue, by providing
the Next() function. By doing e = myList.Next() function, we can change e
to the next node in the myList list. To print the value of a node, you cannot
directly print e , as e is the node. To print the value residing in e , you have to
print e.Value ( see line 20). As output, 1 to 5 integers are printed on the
screen.
That’s it about the solution. In the next lesson, you’ll be studying a package
called regexp .
The regexp Package
This lesson provides information about the regexp package and the functionalities it provides.
WE'LL COVER THE FOLLOWING
• Overview
• Explanation
Overview #
Package regexp implements regular expression search. For information about
regular expressions and their syntax, visit this page.
Explanation #
package main
import (
"fmt"
"regexp"
"strconv"
)
func main() {
searchIn := "John: 2578.34 William: 4567.23 Steve: 5632.18" // string to search
pat := "[0-9]+.[0-9]+" // pattern search in searchIn
f := func (s string) string {
v, _ := strconv.ParseFloat(s, 32)
return strconv.FormatFloat(v * 2, 'f', 2, 32)
}
if ok, _ := regexp.Match(pat, []byte(searchIn)); ok {
fmt.Println("Match found!")
}
re, _ := regexp.Compile(pat)
str := re.ReplaceAllString(searchIn, "##.#") // replace pat with "##.#"
fmt.Println(str)
// using a function
str2 := re.ReplaceAllStringFunc(searchIn, f)
fmt.Println(str2)
}
regexp Package
In the code above, outside main at line 4, we import the package regexp . We
want to search a string pattern pat (declared in main at line 10) in a string
searchIn ( declared in main at line 9). At line 12, we have a function f . It is
taking a string s , formatting it in float32, and returning the formatted string.
Testing if the pattern occurs is easy, use the function Match as: ok, _ :=
regexp.Match(pat,[]byte(searchIn)) (see line 16); where ok will be true or
false. If ok is true, it means the match was found, so Match found! will be
printed on the screen.
For more functionalities, you must first make a (pointer to a) Regexp object
from the pattern; this is done through the Compile function (see line 19). Then
we have at our disposal a whole number of Match , Find and Replace
functions. At line 21, we replace the strings of pattern pat in searchIn with
##.#, and store the updated string in str . In the next line, we are printing str
to see how ReplaceAllString of the package regexp works.
At line 24, we are again replacing some part of string searchIn , but this time
through function f . We store the result in str2 . This means str2 will be a
formatted string in float32. In the next line, we are printing str to see the
changes.
The Compile function also returns an error, which we have safely ignored
here because we have entered the pattern ourselves and know that it is a
valid regular expression. Should the expression be entered by the user or
taken from a data source, it is necessary to check this parsing error.
In this example, we could also have used the function MustCompile , which is
like Compile but panics (stopping the program with an error message when
the pattern is not a valid regular expression.
That’s it about regexp package and its methods. Now you’ll study another
package sync in the next lesson.
Locking and the sync Package
This lesson gives a brief introduction on how Go tackles the problem of threading with the sync package.
WE'LL COVER THE FOLLOWING
• A major problem
• Solution provided by Go
A major problem #
In more complex programs, different parts of the application may execute
simultaneously (or concurrently as this is technically called), usually by
executing each part of the program in a different thread of the operating
system. When these different parts share and work with the same variables,
problems are likely to occur. The order in which these shared variables are
updated cannot be predicted, and hence, their values are also unpredictable!
This is commonly called a race condition. The threads race for the updates of
the variables. This is, of course, intolerable in a correct program, so how do
we solve this issue?
The classic approach is to let only one thread at a time change the
shared variable: the code in which the variable is changed (called
the critical section) is locked when a thread starts executing, so no
other thread can start with it. Only when the executing thread has
finished the section, an unlock occurs so that another thread can
access it.
In particular, the map type does not contain any internal locking to achieve
this effect (this is left out for performance reasons); it is said that the map type
is not thread-safe. So, concurrent accesses to a shared map data structure can
corrupt the data of a map.
Solution provided by Go #
In Go, this kind of locking is realized with the Mutex variable of the sync
package. Here, the sync stands for synchronized, meaning that the threads are
synchronized to update the variable(s) in an orderly fashion. A sync.Mutex is
a mutual exclusion lock, which means it serves to guard the entrance to the
critical section of the code so that only one thread can enter the critical
section at one time. If Info is a shared memory variable which must be
guarded, then a typical technique is to include a mutex in it, like:
import "sync"
type Info struct {
mu sync.Mutex
// ... other fields, e.g.:
Str string
}
A function that has to change this variable could be written like:
func Update(info *Info) {
info.mu.Lock()
// critical section:
info.Str = // new value
// end critical section
info.mu.Unlock()
}
An example of its usefulness is a shared buffer, which has to be locked before
updating the SyncedBuffer :
type SyncedBuffer struct{
lock sync.Mutex
buffer bytes.Buffer
}
The sync package also has an RWMutex : a lock that allows many reader threads
using RLock() but only one writer thread. If Lock() is used, the section is
locked for writing as with the normal Mutex . It also contains a handy function
once.Do(call) , where once is a variable of type Once, which guarantees that
the function call will only be invoked one time regardless of how many
once.Do(call) (s) there are.
For relatively simple situations using locking through the sync package (so
that only one thread at a time can access the variable or the map) will remedy
this problem. If this slows down the program too much or causes other
problems, the solution must be rethought with goroutines and channels in
mind: this is the technology proposed by Go for writing concurrent
applications. We will go deeper into this in Chapter 12.
That’s it about threading, locking, and enabling synchronization in Go. The
next lesson discusses another package known as big .
Accurate Computations and the big Package
This lesson discusses how Go ensures accurate computations in its program via the big package.
WE'LL COVER THE FOLLOWING
• A major problem
• Solution provided by Go
A major problem #
We know that programmatically performed floating-point computations are
sometimes not accurate. If you use Go’s float64 type in floating-point numbers
computation, the results are accurate to about 15 decimal digits, enough for
most tasks. When computing with very big whole numbers, the range of the
types int64 or uint64 might also be too small. In that case, float32 or float64
can be used if accuracy is not a concern, but if it is, we cannot use floatingpoint numbers because they are only represented in an approximated way in
memory.
Solution provided by Go #
For performing perfectly accurate computations with integer values Go
provides the math/big package contained in math : big.Int for integers,
big.Rat for rational numbers (these are numbers than can be represented by
a fraction like 2/5 or 3.1416 but not irrational numbers like e or π ) and
big.Float . These types can hold an arbitrary number of digits, only limited by
the machine’s available memory. The downside is the bigger memory usage
and the processing overhead, which means they are a lot slower to process
than built-in integers.
A big integer is constructed with the function big.NewInt(n) , where n is an
int64. A big rational number with big.NewRat(n, d) , where both n (the
numerator) and d (the denominator) are of type int64. A big float number is
constructed with the function big.NewFloat(x) , where x is a float64.
Because Go does not support operator overloading, all the methods of the big
types have names like Add() for addition and Mul() for multiplication. They
are methods (see Chapter 8) acting on the integer, rational or float as a
receiver. In most cases, they modify their receiver and return the receiver as a
result, so that the operations can be chained. This saves memory because no
temporary big.Int variables have to be created to hold intermediate results.
We see this in action in the following program:
package main
import (
"fmt"
"math"
"math/big"
)
func main() {
// Here are some calculations with bigInts:
im := big.NewInt(math.MaxInt64)
in := im
io := big.NewInt(1956)
ip := big.NewInt(1)
ip.Mul(im, in).Add(ip, im).Div(ip, io)
fmt.Printf("Big Int: %v\n", ip)
// Here are some calculations with big rationals:
rm := big.NewRat(math.MaxInt64, 1956)
rn := big.NewRat(-1956, math.MaxInt64)
ro := big.NewRat(19, 56)
rp := big.NewRat(1111, 2222)
rq := big.NewRat(1, 1)
rq.Mul(rm, rn).Add(rq, ro).Mul(rq, rp)
fmt.Printf("Big Rat: %v\n", rq)
}
big Package
In the program above, outside the main function at line 5, we import the big
package for accurate computations. In main , we perform some calculations
with big integers and then big rationals. At line 10, we are returning an *Int
set to the value of the math.MaxInt64 , which is equal to 9223372036854775807
and storing it in im . In the next line, we are declaring a new variable in and
initializing it with im . At line 12, we are returning an *Int set to the value of
the 1956 and storing it in io . At line 13, we are returning an *Int set to the
value of the 1 and storing it in ip .
Now at line 14, we are applying arithmetic functions as: ip.Mul(im,
in).Add(ip, im).Div(ip, io) . First, the values of im and in will be multiplied
and stored in ip . Then the value of im and updated value of ip will be added
together, and the result will be stored again in ip . Now the updated value of
ip will be divided with the value of io , and the final result will again be
stored in ip . In the next line, we are printing ip to check the final value.
The rest of the program is for rational numbers. At line 17, we are returning
*Rat set to the value of the division of numerator math.MaxInt64 and
denominator of 1956 and storing it in rm . In the next line, we are returning
*Rat set to the value of the division of numerator -1956 and denominator of
math.MaxInt64 , and storing it in rn . At line 19, we are returning *Rat set to
the value of the division of numerator 19 and denominator of 56 and storing it
in ro . At line 20, we are returning *Rat set to the value of the division of
numerator 1111 and denominator of 2222 and storing it in rp . In the next line,
we are returning *Rat set to the value of the division of numerator 1 and
denominator of 1 and storing it in rq .
Now at line 22, we are applying arithmetic functions on them as: rq.Mul(rm,
rn).Add(rq, ro).Mul(rq, rp) . First, the values of rm and rn will be multiplied
and stored in rq . Then the value of ro and updated value of rq will be added
together, and the result will be stored again in rq . Now the updated value of
rq will be multiplied with the value of rp , and the final result will again be
stored in rq . In the last line, we are printing rq to check the final value.
That’s it about the big package and its working. In the next lesson, you’ll see
how you can make a package of your own.
Custom Packages and Visibility
This lesson provides a detailed description on how to create and successfully run a custom package in any Go IDE.
WE'LL COVER THE FOLLOWING
• The go command
• Example: A project containing a package
• Building and installing the package: go install
• Building and installing the command executable: go install
• Initialization of a package: init()
Packages are the primary means in Go for organizing and compiling code. A
lot of basic information about packages has already been given in Chapter 2,
most notably the Visibility rule. Now we will see concrete examples of the use
of packages that you write yourself. By custom packages, we mean selfwritten packages or packages otherwise external to the standard library.
When writing your packages, use short, single-word, lowercase names
without _ for the filename(s).
The go command #
The go command is used as a uniform (OS independent) tool to fetch, build,
and install Go packages and applications. Everything from how to get a
package, how to compile, link and test it, is deduced by looking at the source
code to find dependencies and determine build conditions. A single
environment variable: GOPATH (which is discussed in Appendix) tells the go
command (and other related tools) where to find and install the Go packages
on your system.
This command has the same syntax as the system’s PATH environment
variable, so a GOPATH is a list of absolute paths, separated by colons (:) on
Unix-like machines and by semicolons (;) on Windows machines.
On Unix a typical value could be:
GOPATH=/home/user/ext:/home/user/go_projects
On Windows a typical value could be:
GOPATH=d:\go_projects;e:\go_projects
Each path in the list (in this case e:\go_projects or/home/user/go_projects)
specifies the location of a workspace.
A workspace contains Go source files and their associated package objects,
and command executables. It has a prescribed structure of three
subdirectories:
• src contains a structure of subdirectories each containing Go source files
(.go)
• pkg contains installed and compiled package objects (.a)
• bin contains executables (.out or .exe)
Note: There is no specific need to have several workspaces; one
workspace can often meet all needs. GOPATH should not normally
contain GOROOT which is a folder where the Software Development
Kit(SDK) for Go is downloaded on your systems.
Subdirectories of the src directory hold independent packages, and all source
files (.go, .c, .h, and .s) in each subdirectory are elements of that subdirectory’s
package.
When building a program that imports the package widget , the go command
looks for src/pkg/widget inside the Go root, and then, if the package source
isn’t found there, it searches for src/widget inside each workspace in order.
Multiple workspaces can offer flexibility and convenience, but for now, we’ll
concern ourselves with only a single workspace.
Schematically:
workspace /src
/pkg1
/pkg2
...
/pkg
...
/bin
...
Example: A project containing a package #
Here is a simple example as to how go command works for building/running
and how the visibility works. Create a folder e:\go_projects or
$HOME/go_projects as workspace.
For Windows, set the system variable GOPATH to the value e:\go_projects and
check that GOPATH\bin is added to the PATH variable.
For Linux, do the same by adding the following lines to $HOME/.profile (or
equivalent):
export GOPATH=$HOME/go_projects
export PATH=$PATH:$GOPATH/bin
Suppose we want to make a Go project named book. Inside the workspace
directory, create a src folder, and within it a folder book. Our base import
path in Go source files will then be book/. In __book, we want to create a
package pack1 . This can be downloaded from a subfolder pack1 of the folder
book. This subfolder contains a program pack1.go, which indicates it belongs
to package pack1 at its first line.
Remarks: It is not necessary that the subfolder and the source file have
the same name as the package. For the subfolder, it is considered good
practice, but in most cases, the package will be spread across more than
one source file. Our project could contain more than one package, but
the way of working is the same for additional packages.
By convention, there is a close relationship between subdirectories and
packages: each package (all the go-files belonging to it) resides in its
subdirectory, which has the same name as the package. For clarity, different
packages also reside in different directories.
package pack1
var Pack1Int int = 42
var pack1Float = 3.14
func ReturnStr() string {
return "Hello main!"
}
It exports an int variable Pack1Int , a float32 variable pack1Float and a
function ReturnStr , which returns a string. It is a good practice to type all
exported values explicitly: the API is typed. Moreover, in a real project, all the
exported objects should be documented. This program does not do anything
when it is run because it does not contain a main() function.
Building and installing the package: go install #
This is done via the subcommand install of go:
go install importpath
or in this case:
go install book/pack1
from a terminal or command-line (opened in the home folder or another
folder). This command builds and installs the package specified by importpath
and its dependencies. It writes the package object pack1.a to the
pkg\windows_amd64\book subdirectory of the workspace in which the
source resides. On Linux 64-bit this would be pkg\linux_amd64\book. If the
pkg folder does not exist, it is created. If everything builds and installs
correctly, then the command will produce no output. If you are in the folder of
the package (cd $GOPATH/src/book/pack1), then issue:
go install
So the resulting workspace directory tree looks like this:
src/
book/
# the sources for the project book
pack1/
pack1.go
# package source
pkg/
windows_amd64/
book/
# or linux_amd64 on a Linux 64 bit OS
# contains the object
pack1.a
# package object
Building and installing the command executable: go install
#
The go command treats code belonging to package main as an executable
command and installs the package binary to the GOPATH’s bin subdirectory.
A package is imported in another package by its importpath , which is the
pathname of the directory it is in starting after the src component. The
general format of the import is:
import "path or url to the package"
like:
import "github.com/org1/pack1"
Let us create the startup program in a source file main.go; this is preferably
put in a subfolder book_main of book, with the package being imported in
our case via the statement:
import "book/pack1"
(Working this way the startup program is clearly put aside from the packages;
it could also allow for more than 1 startup program in our book project.)
package main
import (
"fmt"
"pack1"
)
func main() {
var test1 string
test1 = pack1.ReturnStr()
fmt.Printf("ReturnStr from package1: %s\n", test1)
fmt.Printf("Integer from package1: %d\n", pack1.Pack1Int)
// fmt.Printf("Float from package1: %f\n", pack1.pack1Float)
}
Issue the command:
go install book/book_main
If it is an executable command it is written to the bin subdirectory of the
workspace in which the source resides. This compiles main.go, links in the
package pack1.a and installs the executable (or binary) book_main.exe (or
pack1_main on Linux) to $GOPATH/bin. If pack1.a was not yet built, it is
made with this command, so it is not necessary to build all dependent
packages beforehand. The workspace directory tree now looks like this:
bin/
book_main.exe
# command executable (pack1_main on Linux)
pkg/
windows_amd64/
book/
# or linux_amd64 on a Linux 64 bit OS
# contains the object files of the packages i
n the project book
pack1.a
# package object
src/
book/
# the sources for the project book
book_main/
main.go
pack1/
pack1.go
# command source
# package source
Because the bin directory is in our PATH variable, we can execute the
program on the command-line from any folder with book_main which
produces the output:
ReturnStr from package1: Hello main!
Integer from package1: 42
Initialization of a package: init() #
Program execution begins by importing the packages, initializing the main
package and invoking the function main() . A package with no imports is
initialized by assigning initial values to all its package-level variables and then
calling any package-level init() function defined in its source. A package
may contain multiple init() functions, even within a single source file. They
are executed in unspecified order. It is best practice if the determination of a
package’s values only depend on other values or functions found in the same
package. The init() functions cannot be called. Imported packages are
initialized before the initialization of the package itself, but initialization of a
package occurs only once in the execution of a program.
Now that you know how to make your package, in the next lesson, you’ll learn
how to document your package.
Documenting Your Packages
This lesson provides information on documenting a custom package.
The go doc tool also shows the comments in your package: the comments
must start with // and precede the declarations (package, types, functions …)
with no blank line in between. go doc will produce a series of Html-pages,
one for each go file. For example:
In the folder doc_example we have the folders sort and sort_main,
containing the go-files sort and sortmain,respectively, with some
comments in the sort file (the files need not be compiled).
Navigate on the command-line to the folder doc_example and issue the
command:
go doc doc_example/sort
This prints out on the command-line:
Sorting using a general interface
func Float64sAreSorted(a []float64) bool
func IntsAreSorted(a []int) bool
func IsSorted(data Interface) bool
func Sort(data Interface)
func SortFloat64s(a []float64)
func SortInts(a []int)
func SortStrings(a []string)
func StringsAreSorted(a []string) bool
type Float64Array []float64
type IntArray []int
type Interface interface{ ... }
type StringArray []string
go doc doc_example/sort_main
Which prints out:
This package gives an example of how to use a custom package with interfac
es
Go doc can also be used as a web server showing your source files in a
browser. To see them, run the command godoc –http=:6060 in the folder
$GOPATH/src, and navigate in your browser to http://localhost:6060.
Now the documentation of the custom package is ready. In the next lesson,
you’ll learn some testing and installation processes.
Custom Packages: Folder Structure, go install and go
test
This lesson covers important concepts like folder structure of a custom package, testing the executable and
installation under workspace.
WE'LL COVER THE FOLLOWING
• Folder-structure for custom packages
• Building the package uc in the uc folder
• Testing the uc package: go test
• Building the executable:
• Installing under $GOROOT:
• Alternatives for setting up your Go environment
• OS-dependent code
For demonstration, we take a simple package uc which has a function
UpperCase to turn a string into uppercase letters. This is just for
demonstration purposes (it wraps the same functionality from package
“strings”), but the same techniques can be applied to more complex packages.
Folder-structure for custom packages #
The following structure is considered best practice and imposed by the go
tool (where uc stands for a general package name; the names in bold are
folders; italicized is the executable):
go_projects (a workspace in $GOPATH)
src/uppercase
/uc (contains go code for package uc)
uc.go
uc_test.go
/uc_main
ucmain.go (main program for using package uc)
pkg/windows_amd64 (the actual name depends on your operating sy
stem/architecture)
/uppercase
uc.a (object file of package)
bin
(contains the final executable files)
uc_main.exe
Building the package uc in the uc folder #
The functionality is implemented in uc.go, belonging to the package uc :
package uc
import "strings"
func UpperCase(str string) string {
return strings.ToUpper(str)
}
From the app’s folder ($GOPATH/src/uppercase), build and install the
package locally with the command:
go install uppercase/uc
This copies the package archive uc.a to pkg/os_arch/uppercase.
Testing the uc package: go test #
Go can test our package uc . To do that, we write 1 (or more) test source files
whose names end with _test.go, and that import the package testing . They
must contain functions named TestXXX with signature func (t*testing.T) .
The test framework invoked by go test runs each such function. If the
function calls a failure function such as t.Error or t.Fail , the test is
considered to have failed. Add a test to the uc package by creating the file
$GOPATH/src/uppercase/uc/uc_test.go containing the following Go code:
package uc
import "testing"
type ucTest struct {
in, out string
}
var ucTests = []ucTest {
ucTest{"abc", "ABC"},
ucTest{"Go", "GO"},
ucTest{"Antwerp", "ANTWERP"},
}
func TestUC(t *testing.T) {
for _, ut := range ucTests {
uc := UpperCase(ut.in)
if uc != ut.out {
t.Errorf("UpperCase(%s) = %s, must be
%s.", ut.in, uc, ut.out)
}
}
}
Go to the package folder $GOPATH/src/uppercase and test it with:
go test uppercase/uc
which produces as output:
ok uppercase/uc 0.155s
or with more verbosity (-v):
go test –v uppercase/uc
which produces as output:
=== RUN TestUC
--- PASS: TestUC (0.00 seconds
PASS
ok uppercase/uc 0.091s
The command go test ./... will run all test code from the packages in and
beneath the current directory.
Building the executable: #
Then we make our main starting program which uses the uc package as
ucmain.go in folder uppercase/uc_main:
package main
import (
"fmt"
"uppercase/uc"
)
func main() {
str1 := " USING package uc!"
fmt.Println(uc.UpperCase(str1))
}
Then issue the following command in the package folder:
go install uppercase/uc_main
which puts the executable uc_main.exe in the bin folder. Running uc_main
gives as output:
USING PACKAGE UC!
If the go command has no path-parameter, it operates on the current directory
only.
cd
go
go
go
/path/to/package
build # build package to local directory
install # install package to $GOPATH/bin or $GOBIN
test # test package in local directory
Installing under $GOROOT: #
If we want the package to be used from any Go-program on the system, it must
be installed under $GOROOT. To do this, set GOPATH = $GOROOT in .profile and
.bashrc; then go install uppercase will:
Copy the source code to $GOROOT/src/pkg/os_arch/uppercase
Copy the package archive to $GOROOT/pkg/os_arch/uppercase
The package can then be imported in any Go-source as:
import uc
Alternatives for setting up your Go environment #
Put every project inside its own workspace:
GOPATH=/path/to/proj1:/path/to/proj2:…
Separate your own projects from 3rd party projects:
/home/user/goprojects
/own
/bin
/pkg
/src
/3rdparty
/bin
/pkg
/src
This can be accomplished with GOPATH=$HOME/projects/3rdparty:
$HOME/projects/own.
OS-dependent code #
Your program should rarely be written differently according to the operating
system on which it is going to run; in the vast majority of cases, the language
and standard library handle most portability issues. You could have an
excellent reason to write platform-specific code such as assembly language
support. In that case, it is reasonable to follow this convention:
prog1.go
prog1_linux.go
prog1_darwin.go
prog1_windows.go
prog1.go defines the common code interface independent from operating
systems, put the OS-specific code in its own Go-file named prog1_os.go.
That’s it about the custom packages. In the next lesson, you’ll learn the
distribution of Go code.
Using git for Distribution
This lesson discusses the Go's exibility to share code with whole programming community.
WE'LL COVER THE FOLLOWING
• Instructions
What we discussed previously is fine for a local package, but how do we
distribute it to the programmer community? We need a source version-control
system in the cloud, like the popular git. To install and set up git, consult here.
Instructions #
We will lead you through creating a git-repository for the package uc . Go to
the package directory uc and create a git repository in it:
git init
This message appears:
Initialized empty git repository in .../uc
Every git project needs a README file with a description of the package. So
open your favorite text editor and put some comments there. Then add all the
files to the repository with:
git add README uc.go uc_test.go
and mark it as the first version:
git commit -m "initial revision"
Now go to the GitHub-website where you must log in. If you don’t have a login
yet, click here where you can create a free account for open source projects.
Choose a username and password, give a valid email-address and Create an
Account. Then you will get a list with the git commands. We have already seen
the commands for the local repository. An excellent help system will guide
you if you encounter any problems. For creating a new repository uc in the
cloud, issue the instructions (substitute NNNN with your username):
git remote add origin git@github.com:NNNN/uc.git
git push -u origin master
You’re done. Go check the GitHub page of your package:
https://github.com/NNNN/uc If one wants to install your cloud-project to their
local machine, they have to execute the command:
go get github.com/NNNN/uc
Now that you’re familiar with how to use git for distributing your packages, in
the next lesson, you’ll learn how to import packages from the external
environment.
External Packages, Projects and Libraries
This lesson discusses the Go’s exibility to import a package, library or a project from the outer world.
WE'LL COVER THE FOLLOWING
• Go external packages and projects
• Using an external library in a Go program
• Downloading and installing the external library
• How to use the external library
Go external packages and projects #
We now know how to use Go and its standard library, but the Go-ecosystem is
bigger than this. When embarking on our Go-projects, it is best to first search
if we can use some existing third party Go package(s) or project(s). Most of
these can be installed with the go get tool. The place to look is here. It
provides a list of indexes and search engines specific for Go packages and
projects.
The index is further classified by categories such as Astronomy, Build Tools,
Compression, Data Structures, Databases and Storage, Development Tools and
so on. It contains a wealth of more than 500 projects, giving for each its name,
a short description and a download link. Use any of the available filters to
refine your search.
There is a wealth of many great external libraries, for example:
• MySQL (GoMySQL)
• PostgreSQL (go-pgsql)
• MongoDB (mgo, gomongo)
• CouchDB (couch-go)
• ODBC (godbcl)
• Redis(redis.go)
• SQLite3 (gosqlite) database drivers
• SDL bindings
• Google’s Protocol Buffers (goprotobuf)
• XML-RPC (go-xmlrpc)
• Twitter (twitterstream)
• OAuth libraries, (GOAuth)
and many more.
Using an external library in a Go program #
When starting a new project or adding new functionalities to an existing
project, you could save development time by incorporating an already
existing Go-library into your application. In order to do that, you have to
understand the API (Application Programming Interface) of the library which
contains the methods you can call in this library and how to call them.
Remember that you can only use the exported methods, whose name starts
with a capital letter. Only these will usually be documented. You might not
have the source of this library, but the makers will surely have documented
the API and details of how to use it.
How do you use a local library you have created yourself in a subdirectory
pack1 in a directory mypackage ? This is very simple, just do:
import "mypackage/pack1"
and use the exported methods of pack1 .
Using an external library is very simple. Once you know its API, you just call
the library’s functions and work with its results. We’ll use a little library
developed by Inanc Gumus, which you can find here. This library allows us to
easily GET HTTP resources from a specified URL with timeout support. This
means you can set a timeout in seconds for every GET request you want to
execute.
Downloading and installing the external library #
Downloading and installing the external library will be accomplished with the
go get tool. First, verify that the GOPATH variable is set in your environment
because the external source code will be downloaded into the directory
$GOPATH/src.
The packages will also be installed under the
$GOPATH/pkg/“machine_arch”/. Then we install the library by invoking the
following command in the console:
go get github.com/inancgumus/myhttp
go get takes the URL path without https:// as argument, and will download
the source code, compile it and install the package.
How to use the external library #
Of course, we need to import this package with:
import "github.com/inancgumus/myhttp"
The docs tell us that the New and Get methods form the API of this very
simple library:
First use the New function on the package’s name (myhttp) to create an
instance mh . This sets the timeout (here 1 second) for the Get request.
Call the Get method on mh . This returns an http Response object, from
which we print out the StatusCode value.
Here is the complete code:
package main
import (
"fmt"
"time"
"github.com/inancgumus/myhttp"
)
func main() {
mh := myhttp.New(time.Second)
response, _ := mh.Get("https://jsonip.com/")
fmt.Println("HTTP status code: ", response.StatusCode)
}
For brevity of code the error returns were not checked, but this should be
done for a real production application!
That’s it about the introduction to external packages and the libraries. In the
next lesson, we’ll see how to install such packages to make them work.
Installing External Packages
This lesson discusses how Go let users install external packages using go get and go install. In the later part, the
difference between them is discussed.
WE'LL COVER THE FOLLOWING
• Introduction
• Explanation
• Difference between go install and go get
Introduction #
If you need one or more external packages in your application, you will first
have to install them locally on your machine with the go get command.
Suppose you want to use a (fictional) package which can be downloaded from
the not-existent URL http://codesite.ext/author/goExample/goex, where
codesite could be googlecode, github, bitbucket, launchpad or others. Where
ext is the extension. The correct name for this is TLD (Top-Level Domain) like
.com or .org. The goex is the package name (often these start with go, but this
is by no means a necessary convention). You install this with the command:
go get codesite.ext/author/goExample/goex
This installs the code in the folder codesite.ext/author/goExample/goex
under $GOPATH/src/. Once installed, to import it in your code, use:
import goex "codesite.ext/author/goExample/goex"
The import path will be the web-accessible URL for your project’s root
followed by the subdirectory. The documentation for go install lists the import
paths for a number of widely used code repositories on the web.
Explanation #
go get is Go’s automatic package installation tool. Downloading them from
remote repositories over the internet if needed, on the local machine, which
includes checkout, compile and install in one go. It installs each of the
packages given on the command line. Moreover, it installs a package’s
prerequisites before trying to install the package itself and handles the
dependencies automatically. The dependencies of the packages residing in
subfolders are also installed but documentation or examples are not. The
dependencies are browsable on each package’s website, though. Again the
variable GOPATH is used (see Appendix).
Here is a concrete example. Suppose we want to use groupcache-db (from this
link ) in our Go project. This is a software solution to enhance the
responsiveness of a slow database using front-ends and a cache server. How
do we install this? Use the command:
go get -v github.com/capotej/groupcache-db-experiment/...
This command produces the following terminal output(verbose because of the
–v flag):
github.com/golang/groupcache (download)
github.com/golang/protobuf (download)
github.com/capotej/groupcache-db-experiment/api
github.com/golang/groupcache/lru
github.com/golang/groupcache/singleflight
github.com/golang/groupcache/consistenthash
github.com/capotej/groupcache-db-experiment/slowdb
github.com/golang/protobuf/proto
github.com/capotej/groupcache-db-experiment/client
github.com/capotej/groupcache-db-experiment/dbserver
github.com/capotej/groupcache-db-experiment/cli
github.com/golang/groupcache/groupcachepb
github.com/golang/groupcache
github.com/capotej/groupcache-db-experiment/frontend
(The -v flag produces verbose output) This puts the Go source-files of this
project in $GOPATH/src/github.com/capotej/groupcache-db-experiment
At the same time, the project is compiled to .a files (on a Windows 64-bit
machine) in
$GOPATH/pkg/windows_amd64/github.com/capotej/groupcache-dbexperiment.
The executables for the projects containing main() points in $GOPATH/bin.
From then on the functionality of the package groupcache-db can be used in
Go code by importing:
import groupcache "github.com/capotej/groupcache-db-experiment"
If you only want to update an external package, use the –u flag:
go get -u package(s)
Difference between go install and go get #
go get verifies if there are packages that need to be downloaded. If yes, then
packages are downloaded and compiled. go install doesn’t do a download. It
only compiles and throws an error if any packages are missing locally.
More info can be found here.
Packages provide great flexibility when writing code whether it be a package
from the standard library, a custom package or an external package. That’s it
about the packages in Go. The next chapter brings new concepts to learn
about structs and methods.
Introduction to Struct
This lesson introduces structs, addressing rudimentary concepts such as declaration and memory allocation.
WE'LL COVER THE FOLLOWING
• De nition of a struct
• Using new() function
• Struct of structs
Go supports user-defined or custom types in the form of alias types or structs.
A struct tries to represent a real-world entity with its properties. Structs are
composite types to use when you want to define a type that consists of several
properties each having their type and value, grouping pieces of data together.
Then, one can access that data as if it were part of a single entity.
Structs are value types and are constructed with the new function. The
component pieces of data that constitute the struct type are called fields . A
field has a type and a name. Field names within a struct must be unique.
The concept was called ADT (Abstract Data Type) in older texts on software
engineering. It was also called a record in older languages like Cobol, and it
also exists under the same name of struct in the C-family of languages and in
the OO languages as a lightweight-class without methods.
However, because Go does not have the concept of a class, the struct type has
a much more important place in Go.
De nition of a struct #
The general format of the definition of a struct is as follows:
type identifier struct {
field1 type1
field2 type2
...
}
Also, type T struct { a, b int } is legal syntax and is more suited for simple
structs. The fields in this struct have names, like field1 , field2 , and so on. If
the field is never used in code, it can be named _ .
These fields can be of any type, even structs themselves, functions or
interfaces. Because a struct is a value, we can declare a variable of the struct
type, and give the values of its fields, like:
var s T
s.a = 5
s.b = 8
An array could be seen as a sort of struct but with indexed rather than named
fields.
Using new() function #
Memory for a new struct variable is allocated with the new function, which
returns a pointer to the allocated storage:
var t *T = new(T)
This can be put on different lines if needed (e.g., when the declaration has to
be package scope, but the allocation is not needed at the start):
var t *T
t = new(T)
The idiom to write this shorter is: t := new(T) ; the variable t is a pointer to
T . At this point, the fields contain the zero-values according to their types.
However, declaring var t T also allocates and zero-initializes memory for t ,
but now t is of type T . In both cases, t in OO jargon is commonly called an
instance or object of the type T , but in Go it is merely a value of type T .
The fields can be given a different value by using the dot-notation, as is
custom in OO-languages:
structname.fieldname = value
The values of the struct-fields can be retrieved with the same notation:
structname.fieldname
This is called a selector in Go. In order to access the fields of a struct, whether
the variable is of the struct type or a pointer to the struct type, we use the
same selector-notation:
type myStruct struct { i int, j float32, k string }
var v myStruct // v has struct type
var p *myStruct // p is a pointer to a struct
v.i
p.i
An even shorter notation and the idiomatic way to initialize a struct value (a
struct-literal) is the following:
v := &myStruct{10, 15.5, "Chris"} // this means that `v` is of type *myStr
uct
or:
var mt myStruct
mt = myStruct{10, 15.5, "Chris"}
A simple example is given below:
package main
import "fmt"
type struct1 struct {
i1 int
f1 float32
str string
}
// struct definition
func main() {
ms := new(struct1)
// making a struct1 type variable
// Filling fields of the struct of struct1 type
ms.i1 = 10
ms.f1 = 15.5
ms.str = "Chris"
fmt.Printf("The int is: %d\n", ms.i1)
fmt.Printf("The float is: %f\n", ms.f1)
fmt.Printf("The string is: %s\n", ms.str)
fmt.Println(ms)
}
Making a Struct
In the above program, look at line 4. We are declaring a struct of type
struct1 . It has three different fields. First, we have an integer i1 (see line 5),
then we have a float32 type variable f1 (see line 6) and lastly, there is a string
variable str (see line 7). This means, if we made a variable of type struct1 , it
would hold these three different fields to which we can assign values by our
own choice. Now, look at the main function. At line 11, we make a struct1
type struct ms using the new function as: ms := new(struct1) . In the next
three lines, we are assigning values to the fields of ms . At line 13, we assign
ms.i1 the value of 10. At line 14, we assign ms.f1 the value of 15.5. At line 15,
we assign ms.str the value of Chris. In the next lines (from line 16 to line 19),
we are printing the fields’ values for ms to verify the results.
In the following example, we see that you can also initialize values by
preceding them with the field names. So, new(Type) and &Type{} are
equivalent expressions. A typical example of a struct is a time-interval (with a
start and end time expressed here in seconds):
type Interval struct {
start int
end int
}
Here, are some initializations:
inter := Interval{0,3} //(A)
inter2 := Interval{end:5, start:1} //(B)
inter3 := Interval{end:5} //(C)
In case A, the values given in the literal must be exactly in the same order as
the fields are defined in the struct; the & is not mandatory. Case B shows
another possibility where the field names with a : precede the values; in that
case, the order of the field names can be different from the order of their
definition. Also, if fields are omitted like in case C, zero-values are assigned.
The naming of the struct type and its fields adheres to the Visibility-rule. An
exported struct type may have a mix of fields where some are exported and
others not.
The following figure clarifies the memory layout of a struct value and a
pointer to a struct for the following struct type: type Point struct { x, y int
}
new(Point)
p := Point{10,20}
10
*Point
20
Point
pp := &Point{10,20}
0
0
Point
*Point
10
Memory Layout of a Struct, Initialized with
new()
20
Memory Layout of a Struct, Initialized as
Struct Literal
The following example shows how to pass a struct as a parameter:
package main
import (
"fmt"
"strings"
)
type Person struct {
firstName string
lastName string
}
// struct definition
func upPerson (p *Person) { // function using struct as a parameter
p.firstName = strings.ToUpper(p.firstName)
p.lastName = strings.ToUpper(p.lastName)
}
func main() {
// 1- struct as a value type:
var pers1 Person
pers1.firstName = "Chris"
pers1.lastName = "Woodward"
upPerson(&pers1)
fmt.Printf("The name of the person is %s %s\n", pers1.firstName, pers1.lastName)
// 2 - struct as a pointer:
Point
pers2 := new(Person)
pers2.firstName = "Chris"
pers2.lastName = "Woodward"
(*pers2).lastName = "Woodward" // this is also valid
upPerson(pers2)
fmt.Printf("The name of the person is %s %s\n", pers2.firstName, pers2.lastName)
// 3 - struct as a literal:
pers3 := &Person{"Chris","Woodward"}
upPerson(pers3)
fmt.Printf("The name of the person is %s %s\n", pers3.firstName, pers3.lastName)
}
Passing Struct as a Parameter
In the above program, look at line 7. We are declaring a struct of type Person .
It has two different fields: a string called firstName (see line 8) and a string
called lastName (see line 9). Look at the header of the function upPerson at
line 12: func upPerson (p *Person) . Here, a pointer to the Person type
variable is passed as a parameter, which means if the values of the fields are
changed inside the function for Person type struct, the effects will be visible
from outside the function too. Nothing is being returned from this function.
Look at line 13 and 14, we are converting both the strings to uppercase. Now,
let’s see the main function, where we use three different methods to call
upPerson function.
Let’s see the first case. At line 19, we declare a Person type variable pers1 as
a value type. In the next two lines, we fill the fields firstName as Chris and
lastName as Woodward of pers1 using . selector. At line 22, we call the
upPerson function on pers1 . The upPerson accepts pointer to the Person type,
but pers1 is of value type, so we pass pers1 as: upPerson(&pers1) . Because
&pers1 , is the pointer (address) to pers1 .
Let’s see the second case. At line 26, we declare a Person type variable pers2
as a pointer type. In the next two lines, we fill the fields firstName as Chris
and lastName as Woodward of pers1 using the . selector. At line 30, we call
the upPerson function on pers2 . The upPerson accepts a pointer to the Person
type and pers2 is already of pointer type, so we pass pers2 as it is:
upPerson(pers2) .
Let’s see the third case. At line 34, we declare a Person type variable pers3 as
a struct literal type as: pers3 := &Person{"Chris","Woodward"} . At line 35, we
call the upPerson function on pers3 . As upPerson accepts a pointer to the
Person type and pers3 is already of pointer type, so we pass pers3 as it is:
upPerson(pers3) .
At lines 23, 31 and 36, we are printing the fields of pers1 , pers2 , and pers3 ,
respectively. For all three of them, firstName will now be CHRIS instead of
Chris, and lastName will now be WOODWARD instead of Woodward.
Struct of structs #
Structs in Go and the data they contain, even when a struct contains other
structs, form a contiguous block in memory. It gives a huge performance
benefit. This is unlike in Java with its reference types, where an object and its
contained objects can be in different parts of memory. In Go, this is also the
case with pointers. This is clearly illustrated in the following example:
type Rect1 struct { Min, Max Point }
type Rect2 struct { Min, Max *Point }
r1 :=React1{Point{10,20},Point{50,60}}
10
20
50
60
React1
r2 :=React2{&Point{10,20},&Point{50,60}}
React2
10
20
50
60
Point
Memory Layout of a Struct of Structs
That’s it for the introduction. Now let’s dive into some advanced concepts of
structs in Go.
Advanced Structs Concepts
This lesson provides detailed information on structs and how other data structures are related to structs in Go.
WE'LL COVER THE FOLLOWING
• Recursive structs
• Linked List
• Binary Tree
• Size of a struct
• Conversion of structs
Recursive structs #
A struct type can be defined in terms of itself. This is particularly useful when
the struct variable is an element of a linked list or a binary tree, commonly
called a node. In that case, the node contains links (the addresses) to the
neighboring nodes.
In the following examples, next for a list and left and right for a tree are
pointers to another Node-variable.
Linked List #
head
next
data
next
tail
nil
Linked List as Recursive Struct
The data field contains useful information (for example a float64), and next
points to the successor node; in Go-code:
type Node struct {
data float64
next *Node
}
The first element of the list is called the head, and it points to the 2nd element.
The last element is called the tail; it doesn’t point to any successor, so its next
field has value nil. Of course, in a real list, we would have many data-nodes.
The list can grow or shrink dynamically. In the same way, you could define a
doubly-linked list with a predecessor node field pr and a successor field su.
type Node struct {
pr *Node
data float64
su *Node
}
Binary Tree #
left
nil
data
data
right
ri
nil
nil
data
data
nil
nil
Binary Tree as a Recursive Struct
Here, each node has at most two links to other nodes: the left and the right.
Both of them can propagate this further. The top element of the tree is called
the root. The bottom layer of nodes, which have no more nodes beneath them,
are called the leaves. A leaf node has nil-values for the left and right
pointers. We could define the type like this:
type Tree struct {
left *Tree
data float64
right *Tree
}
Size of a struct #
If you have a struct type T and you quickly want to see how many bytes a
value occupies in memory, use:
size := unsafe.Sizeof(T{})
There is a difference between the size of a struct containing another struct
and the size of a struct containing a pointer to another struct. This is
illustrated in the following code snippet:
package main
import (
"fmt"
"unsafe" // to use function Sizeof()
)
type T1 struct {
a, b int64
}
type T2 struct {
t1 *T1 // pointer to T1
}
type T3 struct {
t1 T1
// value type of T1
}
func main() {
fmt.Println("Size of T1:", unsafe.Sizeof(T1{}))
fmt.Println("Size of T2:", unsafe.Sizeof(T2{&T1{}}))
fmt.Println("Size of T3:", unsafe.Sizeof(T3{}))
}
// T1 value type
// T2 containing pointer to T1
// Value of T3
Size of Struct
In the program above, at line 4, we import the package unsafe to use the
function Sizeof() that tells the size of the parameter passed to it. At line 7, we
declare a struct of type T1 , which has two fields of type int64 ; a and b . At
line 11, we declare another struct of type T2 , which has one field, and is a
pointer to the variable of type T1 called t1 . At line 15, we declare another
struct of type T3 with one field t1 , which is a value of type T1 .
Now, look at the main . At line 20, we are printing the size of T1 . The size of
one int64 type number is 8 bytes. Since the struct T1 has two int64 type
numbers, the total size will be 16.
At line 21, we are printing the size of T2{&T1{}} . Since it’s a pointer or address
type, the size of the pointer is the same, regardless of what data type they are
pointing to. On a 32-bit machine, the size of the pointer is 4 bytes, where on a
64-bit machine, the size of the pointer is 8 bytes. In this case, the size of the
pointer is also 8 bytes.
At line 22, we are printing the size of T3 . Where T3 holds a value of type T1 ,
and the size of T1 is 16. So, the size of T3 will also be 16 bytes.
Conversion of structs #
As we have already seen, conversion in Go follows strict rules. When we have
a struct type and define an alias type for it, both types have the same
underlying type and can be converted into one another. Also note the
compile-error cases which denote impossible assignments or conversions.
Look at the following implementation of the conversion:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
)
type number struct {
f
float32
}
type nr number
// new distinct type
type nrAlias = number // alias type
func main() {
a := number{5.0}
b := nr{5.0}
c := nrAlias{5.0}
// var i float32 = b
// compile-error: cannot use b (type nr) as type float32 in as
// var i = float32(b) // compile-error: cannot convert b (type nr) to type float32
// var d number = b
// compile-error: cannot use b (type nr) as type number in ass
// needs a conversion:
var d = number(b)
// an alias doesn't need conversion:
var e = b
fmt.Println(a, b, c, d, e)
}
Click the RUN button and wait for the terminal to start. Type go run main.go
and press ENTER. In case you make any changes to the file you have to press
RUN again.
In the above code, at line 6, we declare struct number with one field of type
float32 f . Then, at line 10, we create another distinct type (aliasing) for
number as nr . In the next line, we alias the type number as nrAlias .
Now, look at main . At line 14, we are creating a variable of type number , as a
and setting its f as 5.0. In the next line, we are creating another variable of
type number but by using its alias nr as b and setting its f also as 5.0. At line
16, we are creating another variable of type number but by using its alias
nrAlias as c and setting its f also as 5.0.
Look at the commented part (from line 17 to line 20). At line 17, we are
creating a new variable i of type float32 and setting it equal to b . It will
generate an error because b type is nr , and it can’t be assigned to a variable
of type float32. Similarly, in the next line, we are again creating a new
variable i of type float32 but by setting it equal to float32(b) . It will
generate an error because b type is nr , and it can’t be directly converted to
float32. At line 19, we are creating a new variable d of type number and
setting it equal to b . It will generate an error because b type is nr , and it
can’t be assigned to a variable of type number , no matter whether nr is the
alias of type number .
Look at line 21, to make a number type variable equal to nr ; we have to
convert the type as: var d = number(b) . Such a conversion makes d as a
number type variable with fields equal to the field of b (of type nr ). In the
next line, we are making another nr type variable e and setting it equal to b .
For such a case, we don’t need any conversion, and all the fields of b and e
would be the same. At line 24, we are printing all the variables : a , b , c , d
and e . All variables will give {5} as an output. Because for all f is 5.0.
Now, that you are familiar with advanced concepts, let’s study the concept of
factories related to structs in the next lesson.
Factory Method
This lesson tells what a factory is and how to make and use a factory. In the later part, we revisit some important
functions.
WE'LL COVER THE FOLLOWING
• A factory of structs
• new() and make() revisited for maps and struct
A factory of structs #
Go doesn’t support constructors as in OO-languages, but constructor-like
factory functions are easy to implement. Often, a factory is defined for the
type for convenience. By convention, its name starts with new or New .
Suppose we define a File struct type:
type File struct {
fd int // file descriptor number
name string // filename
}
Then, the factory, which returns a pointer to the struct type, would be:
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
return &File{fd, name}
}
Often a Go constructor can be written succinctly using initializers within the
factory function. An example of calling it:
f := NewFile(10, "./test.txt")
If File is defined as a struct type, the expressions new(File) and &File{} are
equivalent. Compare this with the clumsy initializations in most OO
languages:
File f = new File( ...)
In general, we say that the factory instantiates an object of the defined type,
just like in class-based OO languages. How to force using the factory method?
By applying the Visibility rule, we can force the use of the factory method and
forbid using new , effectively making our type private as it is called in OOlanguages.
package matrix
type matrix struct {
...
}
function NewMatrix(params) *matrix {
m := new(matrix)
// m is initialized
return m
}
Because of the m of matrix , we need to use the factory method in another
package:
package main
import "matrix"
...
wrong := new(matrix.matrix) // will NOT compile (the struct matrix can't b
e used directly)
right := matrix.NewMatrix(...) // the ONLY way to instantiate a matrix
new() and make() revisited for maps and struct #
The difference between these two built-in functions was clearly defined in
Chapter 5 with an example of slices. By now, we have seen two of the three
types for which make() can be used: slices and maps. The 3rd make type is
channels, which we will discuss in Chapter 12.
To illustrate the difference in behavior for maps and possible errors,
experiment with the following program:
package main
type Foo map[string]string
type Bar struct {
thingOne string
thingTwo int
}
func main() {
// OK:
y := new(Bar)
(*y).thingOne = "hello"
(*y).thingTwo = 1
// not OK:
z := make(Bar) // compile error: cannot make type Bar
z.thingOne = "hello"
z.thingTwo = 1
// OK:
x := make(Foo)
x["x"] = "goodbye"
x["y"] = "world"
// not OK:
u := new(Foo)
(*u)["x"] = "goodbye" // !! panic !!: runtime error: assignment to entry in nil map
(*u)["y"] = "world"
}
The code doesn’t compile successfully. To find the reason, let’s study code line
by line. At line 3, we declared a type called Foo for all the maps having keys
and their values with type string.
Let’s first study the use of new and make in case of structs. At line 5, we make
a struct Bar with two fields thingOne a string variable and a thingTwo an
integer variable. Now, look at main . At line 12, we are making a Bar type
variable via new() function called y . As y is a pointer variable, to set the
values of its fields, we have to dereference y . See line 13, where we are
dereferencing y and using the selector to give the value of hello to thingOne
as: (*y).thingOne = "hello" . In the next line, we are dereferencing y and
using the selector to give value of 1 to thingTwo as: (*y).thingTwo = 1 .
Now, let’s do the same thing but with the make() function. Look at line 16, we
are making a Bar type variable via make() function called z . As z is a value
type variable, to set the values of its fields, we have to use the selector directly
without dereferencing z . See line 17, where we are using the selector to give
the value of hello to thingOne as: z.thingOne = "hello" . In the next line, we
are giving the value of 1 to thingTwo as: z.thingTwo = 1 . This piece of code will
give the compiler error because you can never make a struct type variable
with make . You can only use the new function for this purpose.
This is about the structs. Now, let’s see the implementation on maps. At line
20, we make a Foo type map called x using make function. At line 21 and line
22, we are making a key x with value goodbye and a key y with value world,
respectively. At line 24, we make a Foo type map called u using the new
function. As u is a pointer type variable, to set values or keys u will be
dereferenced when used. At line 25 and line 26, we are making a key x with
value goodbye after dereferencing u and a key y with value world after
dereferencing u , respectively. This piece of code will give a runtime error
because making keys and assigning values in the case of a pointer type map is
like an assignment to the entry in nil map. Using new to create a map and
trying to fill it with data gives a runtime error because the new(Foo) results in
a pointer to a nil not yet allocated, map. So be very cautious with this!
That’s it about the factory of methods. In the next lesson, you’ll see a new
addition to the declaration of structs, i.e., tags.
Structs with Tags
In this lesson, we'll cover how to attach a tag with the eld of a struct and how to read it.
WE'LL COVER THE FOLLOWING
• Tags: key “value” convention
A field in a struct can, apart from a name and a type, also optionally have a
tag. It is a string (or a raw string) attached to the field, which could be
documentation or some other important label. The tag-content cannot be used
in normal programming; only the package reflect can access it. This package
which we’ll explore deeper in the next chapter (Chapter 9), can investigate
types, their properties, and methods in runtime. For example:
reflect.TypeOf() on a variable gives its type. If this is a struct type, it can be
indexed by Field , and then, the Tag property can be used.
The following program shows how this works:
package main
import (
"fmt"
"reflect"
)
type TagType struct { // tags
field1 bool "An important answer"
field2 string `The name of the thing`
field3 int "How much there are"
}
func main() {
tt := TagType{true, "Barack Obama", 1}
for i:= 0; i < 3; i++ {
refTag(tt, i)
}
}
func refTag(tt TagType, ix int) {
ttType := reflect.TypeOf(tt)
ixField := ttType.Field(ix)
fmt.Printf("%v\n", ixField.Tag)
// getting field at a position ix
// printing tags
}
Structs with Tags
In the above code, at line 4, we import a package reflect . At line 7, we are
making a struct TagType type variable with three fields: field1 of type bool,
field2 with type string, and field3 with type int. You may have noticed with
all fields, are some tags associated in quotes and backticks.
Before studying main let’s focus on the function refTag . See its header at line
20: func refTag(tt TagType, ix int) . The function is taking a Tagtype
variable tt and an integer variable ix .
Using the reflect package, we are finding the type of tt and storing the type
in ttType (at line 21). Then, we are using the Field function from the reflect
package (at line 22) to find the field at position ix in ttType and storing it as
ixField . At line 23, we are using ixField to find its tag, and then printing it.
Now, look at main . At line 14, we make a TagType variable tt and assign
fields with values: true, Barack Obama, and 1. Now, we wish to print tags
associated with fields of tt . We’ll use the package refect for this purpose. As
we have three fields, we made a for loop at line 15 with iterator i , starting
from 0 and ending at 2, which means it will iterate three times. In every
iteration, we are calling a function refTag , which takes tt and iterator i .
When i is 0, the tag of the first field will be printed. When i is 1, the tag of
the second field will be printed. When i is 2, the tag of the third field will be
printed.
Tags: key “value” convention #
Go allows the definition of multiple tags through the use of the key: "value"
format. Look at the following example to see that once we have a tag, the
value of its key can be retrieved through the Get method:
package main
import (
"fmt"
"reflect"
)
type T struct {
a int "This is a tag"
b int `A raw string tag`
c int `key1:"value1" key2:"value2"`
}
func main() {
t := T{}
fmt.Println(reflect.TypeOf(t).Field(0).Tag)
if field, ok := reflect.TypeOf(t).FieldByName("b"); ok {
fmt.Println(field.Tag)
}
if field, ok := reflect.TypeOf(t).FieldByName("c"); ok {
fmt.Println(field.Tag)
fmt.Println(field.Tag.Get("key1"))
}
if field, ok := reflect.TypeOf(t).FieldByName("d"); ok {
fmt.Println(field.Tag)
} else {
fmt.Println("Field not found")
}
}
Getting Value from Tag
In the above code, at line 4, we import a package reflect . At line 7, we are
making a struct T type variable with three fields: a of type int, b with type
int, and c with type int. You may have noticed with all fields, are some tags
associated, in quotes and back ticks.
Now, look at main . At line 14, we make a T variable t without assigning
fields with values. At line 15, we are printing the tag of field 0 of t as:
fmt.Println(reflect.TypeOf(t).Field(0).Tag) . In the next line, we are
checking whether we have a field with the name b in t , using the reflect
package as: if field, ok := reflect.TypeOf(t).FieldByName("b"); ok . If b
exists then ok becomes true, and the field of b is stored in field . If ok is
true, only then line 17 will be executed. In this case, ok will be true because t
has the field b . So the tag of field b will be printed on the screen.
Similarly, at line 19, we are checking whether we have a field with the name
c in t , using the reflect package as: if field, ok :=
reflect.TypeOf(t).FieldByName("c"); ok . If c exists, then ok becomes true,
and the field of c is stored in field . If ok is true, only then line 24 will be
executed; otherwise, line 26 will be executed. In this case, ok will be false
because t has field d . So, the tag of field d will not be printed on the screen
(line 24). Line 26 will be executed, and Field not found will be printed.
Now that you’re familiar with tag’s convention, in the next lesson, you’ll study
the concept of anonymous fields and embedded structs.
Anonymous Fields and Embedded Structs
In the rst part of this lesson, you'll study how to make structs with nameless elds and how to use them. In the
second part, you'll see how to embed a struct inside another struct and use it.
WE'LL COVER THE FOLLOWING
• De nition
• Embedded structs
• Con icting names
• Anonymous structs
• Comparing structs
De nition #
Sometimes, it can be useful to have structs that contain one or more
anonymous (or embedded) fields that are fields with no explicit name. Only the
type of such a field is mandatory, and the type is then also the field’s name.
Such an anonymous field can also be itself a struct, which means structs can
contain embedded structs. This compares vaguely to the concept of
inheritance in OO-languages, and as we will see, it can be used to simulate a
behavior very much like inheritance. This is obtained by embedding or
composition. Therefore, we can say that in Go composition is favored over
inheritance.
Consider the following program:
package main
import "fmt"
type innerS struct {
in1 int
in2 int
}
type outerS struct {
b int
c float32
int // anonymous field
innerS // anonymous field
}
func main() {
outer := new(outerS)
outer.b = 6
outer.c = 7.5
outer.int = 60
outer.in1 = 5
outer.in2 = 10
fmt.Printf("outer.b is: %d\n", outer.b)
fmt.Printf("outer.c is: %f\n", outer.c)
fmt.Printf("outer.int is: %d\n", outer.int)
fmt.Printf("outer.in1 is: %d\n", outer.in1)
fmt.Printf("outer.in2 is: %d\n", outer.in2)
// with a struct-literal:
outer2 := outerS{6, 7.5, 60, innerS{5, 10}}
fmt.Println("outer2 is: ", outer2)
}
Struct with Anonymous Fields
In the above code, at line 4, we make a struct of type innerS containing two
integer fields in1 and in2 . At line 9, we make another struct of type outerS
with two fields b (an integer) and c (a float32 number). That’s not all. We
also have two more fields that are anonymous fields. One is of type int (see
line 13), and the other is of type innerS (see line 14). They are anonymous
fields because they have no explicit names.
Now, look at main . We make an outerS variable outer via the new function at
line 18. In the next few lines, we are giving the values to its fields. The fields
b and c of outer are given values at line 19 and line 20, respectively. Now,
it’s the turn for anonymous fields of outer .
To store data in an anonymous field or get access to the data, we use the name
of the data type, e.g. outer.int . A consequence is that we can only have one
anonymous field of each data type in a struct. Look at line 21. We are
assigning the value of 60 to the int type anonymous field declared at line 13.
But how to assign the value to the second anonymous field innerS declared at
line 14? The answer is simple. We also have a struct of type innerS in our
program, which contains two int fields in1 and in2 . So, to assign the value to
the second anonymous field innerS , we’ll assign the values to the fields of
innerS ( see line 22 and line 23). From line 24 to line 28, we are printing the
above fields of outer to which we assigned the values, to verify the results.
This is one of the methods to make and assign values to a struct type variable
that contains anonymous fields, i.e., using the new function and then the
selector operator. How about making such a struct type variable using structliteral? Look at line 30, we are making an outerS variable outer2 , and
assigning the same values as the above but in a struct-literal manner as:
outer2 := outerS{6, 7.5, 60, innerS{5, 10}} . The order is followed. The
variable b gets 6, and c gets 7.5. The first anonymous field of outer2 , which
is an int, gets 60. The anonymous field of innerS gets the value 5 for in1 and
10 for in2 . In the last line, we are printing outer2 to check the values
assigned to its fields.
To store data in an anonymous field or get access to the data, we use the name
of the data type, e.g., outer.int . A consequence is that we can only have one
anonymous field of each data type in a struct.
Embedded structs #
As a struct is also a data type; it can be used as an anonymous field. See the
example above. The outer struct can directly access the fields of the inner
struct as in outer.in1 ; this is possible even when the embedded struct comes
from another package. The inner struct is inserted or embedded into the outer
struct. This simple inheritance mechanism provides a way to derive some or
all of your implementation from another type or types.
Con icting names #
What are the rules when there are two fields with the same name (possibly a
type-derived name) in the outer struct and an inner struct?
1. An outer name hides an inner name. This provides a way to override a
field or method.
2. If the same name appears twice at the same level, it is an error if the
program uses the name. If it’s not used, it doesn’t matter. There are no
rules to resolve the ambiguity; it must be fixed. For example:
type A struct { a int }
type B struct { a, b int }
type C struct { A; B }
var c C
According to rule (2), when we use c.a , it is an error because there is
ambiguity on what is meant: c.A.a or c.B.a . The compiler error is: ambiguous
DOT reference c.a . We have to disambiguate with either c.A.a or c.B.a . Look
at another example:
type D struct { B; b float32 }
var d D
According to rule (1), using d.b is ok. It is the float32, not the b from B . If we
want the inner b , we can get at it by d.B.b .
Anonymous structs #
Go also allows you to use structs without a type name, so-called anonymous
structs, as in this example:
package main
import "fmt"
func main() {
var person struct {
name, surname string
}
person.name, person.surname = "Barack", "Obama"
anotherPerson := struct {
// anonymous struct
name, surname string
}{"Barack", "Obama"}
fmt.Println(person, anotherPerson)
}
Anonymous Structs
In the above code, at line 6, we make a struct type person with two string
fields name and surname . At line 10, we set the fields of the person struct. The
field name gets Barack and surname gets Obama. At line 11, we make an
anonymous struct anotherPerson . We do not define any struct type alias but
create a struct from the inline struct type and define the initial values in the
same syntax. We assign the value to name and surname as Barack and Obama.
At line 14, we are printing person and anotherPerson . The output shows that
they both print the same result.
Comparing structs #
Various structs are by default different types, so the values of these structs
can’t be equal. However, if the fields are identical, structs can be converted
into one another. This is illustrated in the following program:
package main
import "fmt"
type T1 struct {
a int
}
type T2 struct {
b int
}
type T3 struct {
a int
}
func main() {
t1 := T1{10}
t2 := T2{10}
t3 := T3{10}
t4 := T3{20}
fmt.Println(t1, t2, t3 ,t4)
// fmt.Println("t1 == t2?", t1==t2) // <-- invalid operation: t1 == t2 (mismatched types T1
// fmt.Println("t1 == t3?", t1==t3) // <-- invalid operation: t1 == t3 (mismatched types T1
fmt.Println("t1 == t3?", t1==T1(t3)) // true
fmt.Println("t3 == t4?", t3==t4) // false
}
Comparing Structs
In the above code, at line 4, we make a struct of type T1 with one int field a .
Then at line 8, we make a struct of type T2 with one int field b . Again at line
12, we make a struct of type T3 with one int field a .
Now, look at main . At line 17, we make a T1 type variable t1 using
struct_literal assigning a value of 10. In the next line, we make a T2 type
variable t2 using struct_literal assigning b value of 10. Then, at line 19, we
make a T3 type variable t3 using struct_literal, assigning a a value of 10.
Similarly, in the next line, we make a T3 type variable t4 using struct_literal,
assigning a a value of 20. At line 21, we are printing t1 , t2 , t3 and t4 to
verify the outputs.
Now, let’s perform some comparisons between these structs. See the
commented line 22. At line 22, comparison between t1 and t2 was made via
the == operator. It will give an error because the fields of t1 and t2 are not
the same. Similarly, see the next commented line, where the comparison
between t1 and t3 was made. It will give an error not because fields of t1
and t3 are not the same. They are the same. However, you have to convert
the type first (see line 24). The struct t3 is first type-casted to T1 and then
compared with t1 as: t1==T1(t3) , which will give true because both fields ( a )
have a value of 10. In the last line, we are comparing t3 with t4 . This doesn’t
even type conversion because t3 and t4 are of type T3 . This will give false
because a of t3 is 10 and a of t4 is 20.
Now, you are familiar with the use of structs. In the next lesson, you have to
write a program to solve a problem.
Challenge: Anonymous Struct
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Make a struct with 1 named float field, and 2 anonymous fields of type int and
string . Create a value of the struct with a literal expression and print the
content.
Try to implement the function below. Good Luck!
package main
// make struct here
func main(){
// create struct using struct literal and print its field using fmt package
}
Anonymous Struct
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Anonymous Struct
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import "fmt"
type C struct {
// struct
x float32
int
// int type anonymous field
string
// string type anonymous field
}
func main() {
c := C{3.14, 7, "hello"} // making struct via literal expression
fmt.Println(c.x, c.int, c.string) // output: 3.14 7 hello
fmt.Println(c) // output: {3.14 7 hello}
}
Anonymous Struct
In the code above, look at line 4, where we declare a struct of type C . It has
three fields. The first field is a named field x of type float32 . The second and
third fields are anonymous and of types int and string , respectively.
Now, look at the main function. At line 11, we make a variable c of type C
using the literal expression: c := C{3.14, 7, "hello"} . So, x of c is 3.14. The
anonymous int and string variable gets 7 and hello, respectively. In the last
two lines, we are printing the c struct. At line 12, we are printing struct’s
fields independently, using the selector. At line 13, we are printing c as a
whole, which will automatically print all its fields enclosed in braces({}).
That’s it about the solution. In the next lesson, you’ll be studying methods, an
important concept in Go.
Introduction to Methods
So far in this course, you have studied and used functions. This lesson brings a new concept of Go similar to
functions but slightly different, i.e., methods.
WE'LL COVER THE FOLLOWING
• What is a method?
What is a method? #
Structs look like a simple form of classes, so an OO programmer might ask,
where are the methods of the class? Again, Go has a concept with the same
name and roughly the same meaning. A Go method is a function that acts on
a variable of a certain type, called the receiver. Therefore, a method is a special
kind of function.
Note: A method acting on a variable in Go is similar to the object of a
class calling its function in other OO languages, using a . selector, e.g.,
object.function() .
The receiver type can be (almost) anything, not only a struct type. Any type
can have methods, even a function type or alias types for int, bool, string, or
array. However, the receiver cannot be an interface type (see Chapter 9) since
an interface is an abstract definition and a method is the implementation.
Trying to do so generates the compiler error: invalid receiver type.
Lastly, a method cannot be a pointer type, but it can be a pointer to any of the
allowed types. The combination of a (struct) type and its methods is the Go
equivalent of a class in OO. One important difference is that the code for the
type and the methods binding to it are not grouped together. They can exist in
different source files; the only requirement is that they have to be in the same
package.
The collection of all the methods on a given type T (or *T ) is called the
method set of T (or *T ).
Methods are functions, so again, there is no method overloading, which means
for a given type, there is only one method with a given name. However, based
on the receiver type, there is overloading. A method with the same name can
exist on two or more different receiver types, e.g., this is allowed in the same
package:
func (a *denseMatrix) Add(b Matrix) Matrix
func (a *sparseMatrix) Add(b Matrix) Matrix
An alias of a certain type can’t redefine the methods defined on that type
because an alias is the same as the original type. However, a new type based
on a type can redefine these methods.
This is illustrated in the following example:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import "fmt"
type S struct {
a int
}
type
type
type
type
SType S // New type
SAlias = S // Alias
IntType int // New type
IntAlias = int // Alias
func (recv S) print() { // function for type defined on type S
fmt.Printf("%t: %[1]v\n", recv)
}
func (recv SType) print() { // function for type defined on the basis of S
fmt.Printf("%t: %[1]v\n", recv)
}
// func (recv SAlias) print() { // <-- error: S.print redeclared in this block previous decla
// fmt.Printf( %t: %[1]v\n , recv)
// }
func (recv IntType) print() { // function for type defined on type on the basis of int
fmt.Printf("%t: %[1]v\n", recv)
}
// func (recv IntAlias) print() { // <-- error: cannot define new methods on non-local type i
// fmt.Printf("%t: %[1]v\n", recv)
// }
func main(){
a := S{10}
a.print() // calling
b := SType{20}
b.print() // calling
c := SAlias{30}
c.print() // calling
d := IntType(40)
d.print() // calling
// e := IntAlias(50)
// e.print()
}
function from line 13
function from line 16
function from line 13
function from line 24
<-- error: e.print undefined (type int has no field or method print)
Click the RUN button and wait for the terminal to start. Type go run main.go
and press ENTER. In case you make any changes to the file, you have to press
RUN again.
In the above code, at line 4, we declare a struct of type S with one int field a .
Then, at line 8, we declare a new type similar to S with the name SType . In
the next line, we alias the type s as SAlias . Similarly, at line 10, we declare a
similar type to int as IntType . In the next line, we alias the type int as
IntAlias .
Let’s study the methods involved in our program:
Look at the header of print() method at line 13: func (recv S) print() .
The part recv S means that the object of type S can call this method.
This method is printing the value assigned to the field of the calling
object.
Look at the header of the print() method at line 16: func (recv SType)
print() . We redefine the print() method for SType , as this type is
defined based on S . So, redefining the method is allowed. The part recv
SType means that the object of type SType can call this method. This
method is also printing the value assigned to the field of the calling
object.
See the commented line 20. It is the header for the print() method but
also for the object of type SAlias . This will give an error because for
alias, the same method of the base class can’t be redefined.
Look at the header of print() method at line 24: func (recv IntType)
print() . The part recv IntType means that the object of type IntType
can call this method. This method is printing the value assigned to the
field of the calling object.
See the commented line 28. It is the function header for the print()
method but also for the object of type IntAlias . This will give an error
because for alias, the same method of the base class can’t be redefined.
Let’s see main now. At line 33, we make an S type object a using struct-literal
and assign its field with a value of 10. In the next line, we call print() method
on a , due to which method at line 13 will get control.
Similarly, at line 35, we made an SType type object b using struct-literal and
assign its field with a value of 20. In the next line, we call print() method on
b . Due to which, method at line 16 will gain control.
At line 37, we make an SAlias type object c using struct-literal, and assign its
field with a value of 30. In the next line, we call print() method on c . Due to
which, the method at line 13 will gain control (as SAlias is the alias for type
S ).
At line 39, we make an IntType type object d using struct-literal, and assign
its field with a value of 40. In the next line, we call print() method on d . Due
to which, the method at line 24 will gain control.
See the commented line 41, where we made an IntAlias type object e using
struct-literal and assign its field with a value of 50. In the next line, we are
calling print() method. It will generate an error because IntType is an alias
for int, where int type doesn’t have any method called print() . So, it can’t
find any method print() .
The general format of a method is:
func (recv receiver_type) methodName(parameter_list) (return_value_list) {
... }
The receiver is specified in ( ) before the method name after the func
keyword. If recv is the receiver value and Method1 the method name, then
the call or invocation of the method follows the traditional object.method
selector notation: recv.Method1() . In this expression, if recv is a pointer, then
it is automatically dereferenced. If the method does not need to use the value
recv , you can discard it by substituting a _ , as in:
func (_ receiver_type) methodName(parameter_list) (return_value_list) {
... }
(or you could also remove it entirely).
Here is a simple example of methods on a struct:
package main
import "fmt"
type TwoInts struct {
a int
b int
}
func main() {
two1 := new(TwoInts)
two1.a = 12
two1.b = 10
fmt.Printf("The sum is: %d\n", two1.AddThem()) // calling method
fmt.Printf("Add them to the param: %d\n", two1.AddToParam(20)) // calling method
two2 := TwoInts{3, 4}
fmt.Printf("The sum is: %d\n", two2.AddThem()) // calling method
}
func (tn *TwoInts) AddThem() int { // can be called by pointer to TwoInt type var.
return tn.a + tn.b
}
func (tn *TwoInts) AddToParam(param int) int { // can be called by pointer to TwoInt type var
return tn.a + tn.b + param
}
Methods on a Struct
In the above code, at line 4, we make a struct TwoInts with two integer fields
a and b . Before going in main , let’s see two basic methods: AddThem() and
AddtoParam . Look at the header of method AddThem at line 19: func (tn
*TwoInts) AddThem() int . The part (tn *TwoInts) means that the pointer to
the variable of type TwoInts can call this method. It takes nothing as a
parameter and returns an int value. It adds both fields of the variable to
which tn is pointing using the selector operator (see line 20), and return the
sum. Similarly, at line 23, look at the header of the method AddtoParam : func
(tn *TwoInts) AddToParam(param int) int . The part (tn *TwoInts) means that
pointer to the variable of type TwoInts can call this method. It takes an integer
param as a parameter, and returns an int value. It adds both fields of the
variable to which tn is pointing using the selector operator and then adds
param also (see line 24) and returns the sum.
Now, look at main . At line 10, we are making a TwoInts type variable two1 . In
the next two lines, we are assigning the values to the fields of two1 . At line 13,
we are calling the method AddThem on two1 as: two1.AddThem . It will return 22
as a of two1 is 12 (see line 11), and b of two1 is 10 (see line 12). Similarly, in
the next line, we are calling the method AddToParam on two1 as:
two1.AddToParam(20) . It will return 42 (10+12+20). At line 15, we are making a
TwoInts type variable two2 using struct-literal, giving a of two2 the value of 3
and b of two2 the value of 4. At line 16, we are calling method AddThem on
two2 as: two2.AddThem . It will return 7 (3+4).
A method and the type on which it acts must be defined in the same package;
that’s why you cannot define methods on type int, float, or the like. Trying to
define a method on an int type gives the compiler error: cannot define new
methods on non-local type int .
For example, if you want to define the following method on time.Time :
func (t time.Time) first3Chars() string {
return time.LocalTime().String()[0:3]
}
You get the same error for a type defined in another, thus also non-local
package. However, there is a way around this: you can define an alias for that
type (int, float, …), and then define a method for that alias. Or, embed the type
as an unknown type in a new struct, like in the following example. Of course,
this method is only valid for the alias type.
package main
import (
"fmt"
"time"
)
type myTime struct {
time.Time //anonymous field
}
func (t myTime) first3Chars() string {
return t.String()[0:3]
}
func main() {
m := myTime{time.Now()}
//calling existing String method on anonymous Time field
fmt.Println("Full time now:", m.String())
fmt.Println("First 3 chars:", m.first3Chars()) //calling myTime.first3Chars
}
Methods on time
In the above code, we importe package time at line 4 to use its methods. At
line 7, we make a struct of type myTime and create an anonymous field of type
time.Time in it. Look at the header of method first3Chars() at line 11: func
(t myTime) first3Chars() string . The part (t myTime) means that the
variable of type myTime can call this method. It takes nothing as a parameter
and returns a string value. It converts the time stored in the field of t to
string and returns the first three characters (see line 12).
Now, look at main . At line 16, we are creating a variable of type myTime m
using struct-literal, and setting its anonymous field of type time.Time to the
present time. At line 18, we are printing the string value for m . It means that
the present time that was stored at line 16 will be printed in the form of a
string. In the next line, we call the method first3Chars on m as:
m.first3Chars() , which will print only the first three characters of the present
time from line 16 after returning from the method.
Now, that you know what are methods and how to use them, let’s study the
difference between methods and functions.
Methods Versus Functions
This lesson explains differences between a function and a method in Golang.
WE'LL COVER THE FOLLOWING
• Differences between a function and a method
Differences between a function and a method #
A function has the variable as a parameter:
Function1(recv)
A method is called on the variable:
recv.Method1()
A method can change the values (or the state) of the receiver variable
provided this is a pointer, just as is the case with functions (a function can also
change the state of its parameter when this is passed as a pointer: call by
reference).
Note: Don’t forget the ( ) after Method1 , or you get the compiler error:
method recv.Method1 is not an expression, must be called .
The receiver must have an explicit name, and this name must be used in the
method. receiver-type is called the (receiver) base type; this type must be
declared within the same package as all of its methods. In Go, the methods
attached to a (receiver) type are not written inside of the structure, as is the
case with classes; the coupling is much loose: the receiver establishes the
association between method and type.
Methods are not mixed with the data definition (the structs). They are
orthogonal to types; representation (data) and behavior (methods) are
independent.
Now that you are familiar with the details of methods, in the next lesson, you
have to write a program to solve a problem.
Challenge: Coordinates of a Point
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Define a 2 dimensional Point with coordinates X and Y as a struct.
Implement a method Abs() that calculates the length of the vector
represented by a Point, and a method Scale () that multiplies the coordinates
of a point with a scale factor.
Note: Point is the struct type, and X and Y are its fields. Do not change
the name of these variables.
Try to implement the function below. Feel free to view the solution, after
giving some shots. Good Luck!
package main
import "fmt"
import "encoding/json"
type Point struct {
X, Y float64
}
func (p *Point)Abs() float64 {
return 0
}
func (p *Point)Scale(s float64) {
return
}
Coordinates of a Point
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Coordinates of a Point
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"fmt"
"math"
)
type Point struct { /// struct of type Point
X, Y float64
}
func (p *Point)Abs() float64 { // method calculating absolute value
return math.Sqrt(float64(p.X*p.X + p.Y*p.Y))
}
func (p *Point)Scale(s float64) {
p.X = p.X * s
p.Y = p.Y * s
return
}
// method to scale a point
func main() {
p1 := new(Point)
p1.X = 3
p1.Y = 4
fmt.Printf("The length of the vector p1 is: %f\n", p1.Abs() ) // calling Abs() func
p2:= &Point{4, 5}
fmt.Printf("The length of the vector p2 is: %f\n", p2.Abs() ) // calling Abs() func
p1.Scale(5) // calling Scale() fucn
fmt.Printf("The length of the vector p1 is: %f\n", p1.Abs() ) // calling Abs() func
fmt.Printf("Point p1 scaled by 5 has the following coordinates: X %f - Y %f", p1.X, p
}
Coordinates of a Point
In the above code, look at line 7. We make a struct of type Point that is a 2D
point. It has two fields X and Y , both of type float64.
Now, we need to write a method Abs() . See its header at line 11: func (p
*Point)Abs() float64 . From its header, it’s clear that this method can be
called only by a pointer to the object of type Point . In the next line, the
absolute of a point p is taken using the Sqrt function (from math package
imported at line 4), and it is returned from the method.
Now, let’s discuss the method Scale() . See its header at line 15: func (p
*Point)Scale(s float64) . From its header, it’s clear that this method can be
called only by a pointer to the object of type Point . In the next line, both fields
of p ( X and Y ) are multiplied with s (parameter) to scale the coordinates to
the amount s .
Now, look at the main function. At line 22, we make a point p1 using the
new() function. In the next two lines, we set X and Y (3 and 4) fields of point
p1 . At line 25, we call the method Abs() on p1 and print the resulting value,
which is 5.
Now, we make another point p2 , using a literal expression at line 27. You may
have noticed the operator & . This is because the method can be called by a
pointer. In the next line, we call the method Abs() on point p2 and print the
resulting value, which is 6.403124.
Now, at line 30, we are scaling point p1 by a factor of 5. In the next line, we
again call the Abs() function on point p1 to see the change and print the
resulting value which is 25 this time. To see the scaled values of X and Y
which are now 15 and 20, we are printing them in the last line.
That’s it for the solution. In the next lesson, you’ll be attempting another
challenge.
Challenge: Make a Rectangle
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Define a struct Rectangle with int properties length and width . Give this type
two methods Area() and Perimeter() and test it out.
Area= l*w
Perimeter = 2(l+w)
Note: Rectangle is the struct type, and length and width are its fields.
Do not change the name of these variables.
Try to implement the function below. Feel free to view the solution after
giving it a few shots. Good Luck!
package main
import "fmt"
import "strconv"
import "encoding/json"
type Rectangle struct { // struct of type Rectangle
length, width int
}
func (r *Rectangle) Area() int {
return 0
// method calculating area of rectangle
}
func (r *Rectangle) Perimeter() int { // method calculating perimeter of rectangle
return 0
}
Make a Rectangle
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Make a Rectangle
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import "fmt"
type Rectangle struct { // struct of type Rectangle
length, width int
}
func (r *Rectangle) Area() int {
return r.length * r.width
}
// method calculating area of rectangle
func (r *Rectangle) Perimeter() int { // method calculating perimeter of rectangle
return 2* (r.length + r.width)
}
func main() {
r1 := Rectangle{4, 3}
fmt.Println("Rectangle is: ", r1)
fmt.Println("Rectangle area is: ", r1.Area()) // calling method of area
fmt.Println("Rectangle perimeter is: ", r1.Perimeter()) // calling method of perimeter
}
Make a Rectangle
In the above code, at line 4, we make a struct Rectangle containing two fields
length and width of type int. Then, we have two important methods Area()
and Perimeter() . Look at the header of Area method at line 8 as: func (r
*Rectangle) Area() int . It returns the area of rectangle r , i.e.,
r.Length*r.Width . Now, look at the header of Perimeter method at line 12 as:
func (r *Rectangle) Perimeter() int . It returns the perimeter of rectangle
r , i.e., 2*(r.Length+r.Width) . Let’s see the main function. At line 17, we make
a rectangle r1 with literal expression r1 := Rectangle{3,4} . In the next line,
we are printing r1 , which will automatically print the fields of r enclosed
within braces({}). At line 19, we are calling method Area() on r1 and printing
the returned value, which is 12, the area of r1 . At line 20, we are calling
method Perimeter() on r1 and printing the returned value, which is 14, the
perimeter of r1 .
That’s it for the solution. In the next lesson, you’ll be attempting another
challenge.
Challenge: Decide Employee Salary
This lesson brings you a challenge to solve.
Problem statement
Define a struct employee with a field salary and make a
method giveRaise() for this type to increase the salary with
a certain percentage.
Note: employee is the struct type, and salary is its
field. Do not change the name of these variables.
Try to implement the function below. Feel free to view the solution, after
giving some shots. Good Luck!
package main
import "fmt"
import "encoding/json"
/* basic data structure upon which we'll define methods */
type employee struct {
salary float32
}
/* a method which will add a specified percent to an
employees salary */
func (this *employee) giveRaise(pct float32) {
return
}
Decide Employee Salary
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Decide Employee Salary
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import "fmt"
/* basic data structure upon which we'll define methods */
type employee struct {
salary float32
}
/* a method which will add a specified percent to an
employees salary */
func (this *employee) giveRaise(pct float32) {
this.salary += this.salary * pct
}
func main() {
/* create an employee instance */
var e = new(employee)
e.salary = 100000;
/* call our method */
e.giveRaise(0.04)
fmt.Printf("Employee now makes %f", e.salary)
}
Decide Employee Salary
In the above code, at line 5, we make a struct employee containing one field
salary of type float32. Then, we have an important method giveRaise() .
Look at the header of giveRaise() method at line 11 as: func (this *employee)
giveRaise(pct float32) . It updates the salary of employee this by adding the
previous salary to the salary multiplied by pct .
Let’s see the main function. In line 17, we make an employee e with new() . In
the next line, we are assigning the value to the salary of e . At line 20, we are
calling method giveRaise() on e . At line 21, we are printing the updated
value of the salary of e .
That’s it about the solution. In the next lesson, you’ll study the variations of
call methods.
Receivers and Methods as Pointers and Values
In this lesson, you'll study the difference between the value and the pointer type in Go to receive a result or call a
method.
WE'LL COVER THE FOLLOWING
• Pointer or value as a receiver
• Calling methods on values and pointers
• Methods as values and parameters
Pointer or value as a receiver #
The recv is most often a pointer to the receiver-type for performance reasons
(because we don’t make a copy of the value, as would be the case with call by
value); this is especially true when the receiver type is a struct. Define the
method on a pointer type if you need the method to modify the data the
receiver points to. Otherwise, it is often cleaner to define the method on a
normal value type.
This is illustrated in the following example :
package main
import (
"fmt"
)
type B struct {
thing int
}
func (b *B) change() { b.thing = 1 }
func (b B) write() string { return fmt.Sprint(b) }
func main() {
var b1 B // b1 is a value
b1.change()
fmt.Println(b1.write())
b2 := new(B) // b2 is a pointer
b2.change()
fmt.Println(b2.write())
}
Pointer and Value
In the above code, at line 6, we make a struct of type B with one integer field
thing . Look at the header of change() method at line 10: func (b *B)
change() . The part (b *B) shows that only the pointer to B type object can
call this method. This method is changing its internal field thing by assigning
the value of 1 to it. Now, look at the header of the write() method at line 12:
func (b B) write() string . The part (b B) shows that only the B type object
can call this method. This method is returning its internal field thing after
converting it to type string.
Note: The function Sprint() from fmt package returns the string
without printing them on the console.
Now, look at main . We make a variable b1 of type B using var keyword at
line 15. In the next line, we call change() method on b1 . After returning from
this method, thing of b1 will hold value 1. At line 17, we are printing the
result from write() method called on b1 . It will print thing from b1 , which
will output 1 on the screen. We make a variable b2 of type B using the new()
function at line 18. In the next line, we call the change() method on b2 . After
returning from this method, thing of b2 will hold a value of 1. At line 20, we
are printing the result from the write() method called on b2 , which will
print thing from b2 that will output 1 on the screen.
Notice, in main() , that Go does plumbing work for us; we do not have to figure
out whether to call the methods on a pointer or not, Go does that for us. The
variable b1 is a value, and b2 is a pointer. However, the method calls work
just fine. Try to make write() change its receiver value b . You will see that it
compiles fine, but the original b is not changed. We see that a method does
not require a pointer as a receiver as in the following example, where we only
need the values of Point3 to compute something:
type Point3 struct { x, y, z float }
// A method on Point3:
func (p Point3) Abs() float {
return math.Sqrt(p.x*p.x + p.y*p.y + p.z*p.z)
}
However, this is a bit expensive because Point3 will always be passed to the
method by value and copied, but it is valid in Go. In this case, the method can
also be invoked on a pointer to the type (there is automatic dereferencing).
Suppose p3 is defined as a pointer:
p3 := &Point3{3, 4, 5}
Then, you can write p3.Abs() instead of (*p3).Abs() .
Calling methods on values and pointers #
There can be methods attached to the type, and other methods attach a
pointer to the type. However, this does not matter: if for a type T a method
Meth() exists on *T and t is a variable of type T , then t.Meth() is
automatically translated to (&t).Meth() . Pointer and value methods can both
be called on the pointer or non-pointer values. This is illustrated in the
following program:
package main
import (
"fmt"
)
type List []int
func (l List) Len() int { return len(l) }
func (l *List) Append(val int) { *l = append(*l, val) }
func main() {
// A bare value
var lst List
lst.Append(1)
fmt.Printf("%v (len: %d)\n", lst, lst.Len()) // [1] (len: 1)
// A pointer value
plst := new(List)
plst.Append(2)
fmt.Printf("%v (len: %d)\n", plst, lst.Len()) // &[2] (len: 1)
}
Calling Methods
In the above program, at line 6, we declare a new type List on the basis of
the type of slice on integers( []int ). Look at the header of the method Len() at
line 8: func (l List) Len() int . The part (l List) shows that the method
can be called by the object of type List only. The method returns the length of
the object l . Now, look at the header of the method Append() at line 10: func
(l *List) Append(val int) . The part (l *List) shows that the method can be
called by the pointer to the object of type List only. The method appends the
val value at the end of the list l .
Now, look at main , we make a variable lst of type List using var keyword
at line 14. In the next line, we call Append(1) method on lst . At line 16, we
are printing lst and length of lst by calling Len() on lst . We make a
variable plst of type List using new() function at line 18. In the next line,
we call Append(2) method on plst . At line 20, we are printing plst and the
length of plst by calling Len() on plst .
Methods as values and parameters #
Methods are just like functions in that they can be used as values, and passed
to other functions as parameters. This is shown in the following program:
package main
import "fmt"
type T struct {
a int
}
func (t T) print(message string) {
fmt.Println(message, t.a)
}
func (T) hello(message string) {
fmt.Println("Hello!", message)
}
func callMethod(t T, method func(T, string)) {
method(t, "A message")
}
func main() {
t1 := T{10}
t2 := T{20}
var f func(T, string) = T.print
callMethod(t1, f)
callMethod(t2, f)
callMethod(t1, T.hello)
}
Passing Methods
In the above program, at line 4, we make a struct of type T with a single
integer field a . Look at the header of the print method at line 8 as: func (t
T) print(message string) . The part (t T) means that this method can only be
called by an object of type T . This method is printing message sent as a
parameter, and the internal field a of t . Look at the header of the print
method at line 12 as: func (T) hello(message string) . The part (T) means
that this method can be directly called with type T as: T.hello("anyString") .
This method is printing Hello!, and then prints the message parameter. Look
at the header of callMethod function at line 16 as: func callMethod(t T,
method func(T, string)) . This function takes t as a parameter and a function
method . That function method is called with T and a string “A message” as
parameters.
Now, look at main . We made a T type variable t1 using struct-literal giving a
value of 10, at line 21. Similarly, in the next line, we make a T type variable
t2 using struct-literal giving a value of 20. At line 23, we make a variable f
equal to the print method of T .
Now, at line 24, we are calling the callMethod function with t1 and f as
parameters. Here, the method in callMethod is equal to the print() method of
type T . So, from line 17, print() will be called for t1 with message as “A
message”. So, A message 10 will be printed on the screen. Similarly, in the
next line, we are calling the callMethod function with t2 and f as
parameters. Here, the method in callMethod is equal to print() method of
type T . So, from line 17, print() will be called for t2 with message as “A
message”. So, A message 20 will be printed on the screen.
Now at line 26, we are calling the callMethod function with t1 and T.hello
as parameters. Here the method in callMethod is equal to the hello() method
of type T . So, from line 17, hello() will be called for t1 with the message as
A message. So, Hello! A message will be printed on the screen.
That’s it about values and pointers, in the next lesson you’ll study the unexported concept in Go.
Non-Exported Fields
This lesson covers the important concept of how to use elds of a struct if a struct is present in another le or in a
separate custom package.
WE'LL COVER THE FOLLOWING
• Methods and non-exported elds
Methods and non-exported elds #
How can we change, or even read the name of the object of a user-defined type
in another program? This is accomplished using a well-known technique from
OO-languages: provide getter and setter methods. For the setter-method, we
use the prefix Set, and for the getter-method, we only use the field name.
Look at the example below:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"pers"
)
func main() {
p := new(pers.Person)
// error: p.firstName undefined
// (cannot refer to unexported field or method firstName)
//p.firstName = "Eric"
p.SetFirstName("Eric")
fmt.Println(p.FirstName()) // Output: Eric
}
See the pers.go file. We have a struct of type Person with two string fields
firstName and lastName at line 3.
Look at the header of the FirstName() method at line 8: func (p *Person)
FirstName() string . From the name, it’s obvious that it is a getter-method.
Only the pointer to the object of the Person type can call it. This method
returns the firstName (internal field) of p .
Now, look at the header of the SetFirstName() method at line 12: func (p
*Person) SetFirstName(newName string) . From the name, it’s obvious that it is a
setter-method. Only the pointer to the object of the Person type can call it. This
method sets the value of firstName (internal field) of p equal to
newName (parameter). Keep in mind that the type Person can be exported
anywhere, but its field can’t be imported.
Let’s look at the main.go file to grasp this concept. At line 4, we import the
package pers . In main , at line 8, we make a Person type object p using new()
by accessing Person as: pers.Person . We can easily import the type Person
with a selector statement, but we can’t export its field. See the commented
line 11, where firstName (internal field of p ) is directly accessed. It will give
an error. That’s why we made setter and getter methods.
At line 12, we are using the setter to set the name Eric to the firstName of p .
In the next line, we are using the getter method to get the firstName of p and
then printing the returned value.
That’s it about using a struct from another package. In the next lesson, we’ll
study the print mechanism of a struct.
String() Method and Format Specifiers
This lesson describes how to use format speci ers and change the String() function to bring variations to the
default print functions in Go.
When you define a type with a lot of methods, chances are you will want to
make a customized string-output for it with the String( ) method, in other
words: a human-readable and printable output. This is because if String( ) is
defined for a certain type, then this method will be used in fmt.Printf() to
produce the default output, which is the output produced with the format
specifier %v. Furthermore, fmt.Print() and fmt.Println() will automatically
use the String( ) method.
We will test this out with the help of a program:
package main
import (
"fmt"
"strconv"
)
type TwoInts struct {
a int
b int
}
func main() {
two1 := new(TwoInts)
two1.a = 12
two1.b = 10
fmt.Printf("two1 is: %v\n", two1) // output: two1 is: (12 / 10)
fmt.Println("two1 is:", two1) // output: two1 is: (12 / 10)
fmt.Printf("two1 is: %T\n", two1) // output: two1 is: *main.TwoInts
fmt.Printf("two1 is: %#v\n", two1) // output: &main.TwoInts{a:12, b:10}
}
func (tn *TwoInts) String() string {
return "(" + strconv.Itoa(tn.a) + " / " + strconv.Itoa(tn.b) + ")"
}
String Method
In the above code, we have a struct of type TwoInts with two integer fields a
and b . See the header of the String method at line 22 as: func (tn *TwoInts)
String() string . It shows that the variation of this function can be called on
TwoInts type variables only. It converts the fields of tn to string using the
strconv package (imported at line 4) and concatenating it with / , which in
turn is placed with closed brackets. Then, it returns this string.
Now, look at main . We make a variable two1 of type TwoInts using the new()
function at line 13. In the next two lines, we assign the fields of two1 : a and
b with the values: 12 and 10.
At line 16, we are printing two1 using the Printf function of package fmt .
The variation of the String() method from line 22 will be called, and (12 / 10)
will be returned and printed. In the next line, we are printing two1 using
Println function of package fmt . Notice the format specifier is %T in this
case. So, the type of two1 will be printed, which is *main.TwoInts . Now, at line
19, we are printing two1 using the Printf function of package fmt . Notice the
format specifier is %#v in this case. This format specifier prints the type along
with the values, so &main.TwoInts{a:12, b:10} is printed.
If you make the mistake of defining String() in terms of itself, like in the
following snippet, then the program does an infinite recursion ( TT.String()
calls fmt.Sprintf which calls TT.String() …) and quickly gives an out of
memory error:
type TT float64
func (t TT) String() string {
return fmt.Sprintf("%v", t)
}
t.String()
That’s it about the String() function and its role in printing output. In the
next lesson, we’ll study how methods work on embedded structs.
Methods on Embedded Types
This lesson covers in detail how to embed a functionality in a type and how methods work for embedded structs.
WE'LL COVER THE FOLLOWING
• Embed functionality in a type
When an anonymous type is embedded in a struct, the visible methods of that
type are embedded as well. In effect, the outer type inherits the methods:
methods associated with the anonymous field are promoted to become
methods of the enclosing type. To subtype something, you put the parent type
within the subtype. This mechanism offers a simple way to emulate some of
the effects of subclassing and inheritance found in classic OO-languages; it is
also very analogous to the mixins of Ruby.
Here is an illustrative example. Suppose we have an interface type Engine ,
and a struct type Car that contains an anonymous field of type Engine .
type Engine interface {
Start()
Stop()
}
type Car struct {
Engine
}
We could then construct the following code:
func (c *Car) GoToWorkIn {
// get in car
c.Start();
// drive to work
c.Stop();
// get out of car
}
The following complete example shows, how a method on an embedded struct
can be called directly on a value of the embedding type.
package main
import (
"fmt"
"math"
)
type Point struct {
x, y float64
}
func (p *Point) Abs() float64 {
return math.Sqrt(p.x*p.x + p.y*p.y)
}
type NamedPoint struct {
Point
// anonymous field of Point type
name string
}
func main() {
n := &NamedPoint{Point{3, 4}, "Pythagoras"} // making pointer type variable
fmt.Println(n.Abs()) // prints 5
}
Method on Embedded Type
In the above code, at line 7, we make a struct of type Point with two fields x
and y of type float64 . We make another struct at line 15, of type NamedPoint
with two fields in it. The first is an anonymous field of type Point and the
second is name , a variable of type string. Look at the header of the method
Abs() at line 11: func (p *Point) Abs() float64 . It shows that this method
can only be called by the pointer to the variable of type Point , and it returns a
value of type float64. Following is the formula for calculating the absolute
value of a point:
value = √x2 + y 2
Line 12 of the code implements the above formula. We get the values of x
and y through the selector operator applied on Point p . To calculate the
square root, we use the package of math which is imported at line 4. The
method calculates the absolute value for a point and returns it. Now, look at
main . At line 21, we make a pointer variable n of type NamedPoint using the
struct-literal: &NamedPoint{Point{3, 4}, "Pythagoras"} . The part Point{3,4} is
used to assign value to the anonymous Point variable( x gets 3 and y gets 4),
and Pythagoras is assigned to the name string. The symbol & makes it a
pointer variable. At line 22, we call the method Abs on variable n and print
the return value, which is 5 (√32
+ 42 = √25).
Embedding injects fields and methods of an existing type into another type. Of
course, a type can have methods that act only on variables of that type, not on
variables of the embedded parent type.
Embed functionality in a type #
There are two ways of embedding functionality in a type:
Aggregation (or composition): including a named field of the type of the
wanted functionality
Embedding: embedding the type of the wanted functionality
anonymously
Suppose we have a type Customer and we want to include a Logging
functionality with a type Log , which contains an accumulated message (of
course, this could be elaborated). If you want to equip all your domain types
with a logging capability, you implement such a Log and add it as a field to
your type. Also, add a method Log() , returning a reference to this log.
The first method could be implemented as follows:
package main
import (
"fmt"
)
type Log struct {
msg string
}
type Customer struct {
Name string
log *Log
}
func main() {
c := new(Customer)
c.Name = "Barack Obama"
c. a e
a ac Oba a
c.log = new(Log)
c.log.msg = "1 - Yes we can!"
// shorter:
c = &Customer{"Barack Obama", &Log{"1 - Yes we can!"}}
fmt.Println(c.log)
c.Log().Add("2 - After me, the world will be a better place!")
fmt.Println(c.Log())
}
func (l *Log) Add(s string) {
l.msg += "\n" + s
}
func (l *Log) String() string {
return l.msg
}
func (c *Customer) Log() *Log {
return c.log
}
Aggregation
In the above code, at line 6, we make a struct of type Log with one field of
string named msg . At line 10, we make a struct of type Customer with two
fields. The first is a string variable Name , and the second is a pointer variable
log , of type Log . Before going to main , let’s see the other three functions of
the program:
See the header of Add() method at line 27: func(l *Log) Add(s string) .
This shows that this method can be called by the pointer variable of type
Log . It takes a string as a parameter. Look at line 28. This method
appends a new blank line and then adds a string s to the internal field
msg of l .
See the header of String() method at line 31: func(l *Log)
String()string . This shows that this method can be called by the pointer
variable of type Log . This method returns the msg of l .
See the header of String() method at line 35: func (c *Customer) Log()
*Log . This shows that this method can be called by the pointer variable
of type Customer . This method returns the log of c .
Now, look at main . At line 16, we make a Customer type variable c using the
new() function. In the next few lines, we are giving the fields their values. At
line 17, we are giving Name ( a field of Customer c ) a value Barack Obama. In
the next line, we make a new Log type variable and assign it to c.log . Every
variable of type Log has a msg . So, at line 19, we give the value to msg of
c.log as: c.log.msg = "1 - Yes we can!" . It took us four lines to make a
proper Customer object.
How about doing it in a single line? Look at line 21. We make a pointer
variable of type Customer using struct-literal and reinitialize c as:
&Customer{"Barack Obama", &Log{"1 - Yes we can!"}} . The part &Log{"1 - Yes
we can!"} is used to assign a value to an anonymous Log pointer variable( msg
gets 1 - Yes we can!), and Barack Obama is assigned to the Name string. The
symbol & makes it a pointer variable.
See line 22. We are printing the c.log . This will call the String() function,
and we’ll print the return value. In this case, it’s 1 - Yes we can!. In the next
line, we are adding a string to the msg of the log of c as: c.Log().Add("2 After me, the world will be a better place!") . The part c.Log() calls the
method Log() , and the pointer to log of c is returned, which then calls the
Add() method along with the string. The string gets appended at the end of
msg of c.Log() ( c.log ). In the next line, we are printing c.Log() to see
whether the msg was changed or not. The c.Log() calls the method Log() ,
and the pointer to log of c is returned, which will call the String() function.
Then, we’ll print the return value. In this case, it’s:
1 - Yes we can!
2 - After me, the world will be a better place!
The second method, on the contrary, would be like:
package main
import (
"fmt"
)
type Log struct {
msg string
}
type Customer struct {
Name string
Log
}
func main() {
c := &Customer{"Barack Obama", Log{"1 - Yes we can!"}}
c.Add("2 - After me, the world will be a better place!")
fmt.Println(c)
}
func (l *Log) Add(s string) {
l.msg += "\n" + s
}
func (l *Log) String() string {
return l.msg
}
func (c *Customer) String() string {
return c.Name + "\nLog:\n" + fmt.Sprintln(c.Log)
}
Embedding
In the above code, at line 6, we make a struct of type Log with one field of
string named msg . At line 10, we make a struct of type Customer with two
fields. The first is a string variable Name , and the second is an anonymous
variable of type Log . Before going to main , let’s see the other three functions
of the program:
See the header of Add() method at line 21: func(l *Log) Add(s string) .
This shows that this method can be called by the pointer variable of type
Log . It takes a string as a parameter. Look at line 22. This method
appends a new blank line and then adds a string s to the internal field
msg of l .
See the header of String() method at line 25: func(l *Log)
String()string . This shows that this method can be called by the pointer
variable of type Log . This method returns the msg of l .
See the header of String() method at line 29: func (c *Customer)
String() string . This shows that this method can be called by the
pointer variable of type Customer . This method appends the Name of
customer c and its log (anonymous) and returns this final string.
Now, look at main . At line 16, we make a pointer variable of type Customer
using struct-literal and reinitialize c as: &Customer{"Barack Obama", Log{"1 -
Yes we can!"}} . The part Log{"1 - Yes we can!"} is used to assign value to the
anonymous Log variable( msg gets 1 - Yes we can!), and Barack Obama is
assigned to the Name string. The symbol & makes it a pointer variable. In the
next line, we are adding a string to the msg of the log of c as: c.Add("2 After me, the world will be a better place!") . The log of c will
automatically call the Add() method. The string gets appended at the end of
msg . In the next line, we are printing c to see whether the msg was changed
or not. The c calls the method String() , and we’ll print the returned value.
In this case, it’s:
Barack Obama
Log: {1 - Yes we can!
2 - After me, the world will be a better place!}
Now that you’re familiar with embedding, in the next lesson, you’ll cover
multiple-inheritance in Go.
Multiple Inheritance
This lesson describes what multiple inheritance is and how Go achieves this via structs.
WE'LL COVER THE FOLLOWING
• Universal methods and method naming
• Comparisons between Go types and methods and other OO languages
Multiple inheritance is the ability for a type to obtain the behaviors of more
than one parent class. In classical OO languages, it is usually not implemented
(exceptions are C++ and Python), because, in class-based hierarchies, it
introduces additional complexities for the compiler. But in Go, multiple
inheritance can be implemented simply by embedding all the necessary
‘parent’ types in the type under construction.
Look at the following implementation:
package main
import "fmt"
type Camera struct { }
func (c *Camera) TakeAPicture() string {
return "Click"
}
// method of Camera
type Phone struct { }
func (p *Phone ) Call() string {
return "Ring Ring"
}
// multiple inheritance
type SmartPhone struct {
Camera
Phone
}
// method of Phone
// can use methods of both structs
func main() {
cp := new(SmartPhone)
fmt.Println("Our new SmartPhone exhibits multiple behaviors ...")
fmt.Println("It exhibits behavior of a Camera: ", cp.TakeAPicture())
fmt.Println("It works like a Phone too: ", cp.Call())
}
Multiple Inheritance
In the above code, at line 4, we make a struct of type Camera with no fields. At
line 6, there is a method that can be called only by a pointer that points to a
Camera object. The method TakeAPicture returns a string Click. At line 10, we
make a struct of type Phone with no fields. At line 12, there is a method that
can be called only by a pointer pointing to a Phone object. The method Call
just returns a string Ring Ring.
Now, we have a struct SmartPhone at line 17. It can implement the
functionalities of both Camera and Phone . It means we need multiple
inheritance now. So, we made two anonymous fields in SmartPhone : Camera
and Phone . Now, look at main . We make a SmartPhone type pointer variable
cp using the new() function. Now, this cp object can call methods of Camera
and Phone as well. See line 25. We are calling the method TakeAPicture ,
which can be called by the Camera object. Since SmartPhone inherits Camera ,
cp.TakeAPicture() works fine. It will print Click on the screen. In the next
line, we are calling the method Call() , which can be called by the Phone
object. Since SmartPhone inherits Phone , cp.Call() works fine. So it will print
Ring Ring on the screen.
Universal methods and method naming #
In programming, a number of basic operations appear over and over again
like opening, closing, reading, writing, sorting, and so on. Moreover, they have
a general meaning to them: opening can be applied to a file, a network
connection, a database connection, and so on. The implementation details are
very different in each case, but the general idea is the same. In Go, this is
applied extensively in the standard library through the use of interfaces (see
Chapter 9), wherein such generic methods get canonical names like
Open() , Read() , Write() , and so on. If you want to write idiomatic Go, you
should follow this convention, giving your methods the same names and
signatures as those ‘canonical’ methods where appropriate. This makes Go
software more consistent and readable. For example, if you need a convert-to-
string method, name it String() and not ToString() .
Comparisons between Go types and methods
and other OO languages #
Methods in OO languages like C++, Java, C#, or Ruby are defined in the context
of classes and inheritance: when a method is invoked on an object, the
runtime sees whether its class or if any of its superclasses have a definition
for that method. Otherwise, the result is an exception.
In Go, such an inheritance hierarchy is not needed at all. If the method is
defined for that type, it can be invoked, independent of whether or not the
method exists for other types. In that sense, there is greater flexibility.
This is nicely illustrated in the following schema:
aObj.X(x)
aObj.X(x)
A implementing
method X
Can be set to aObj
Inheritance
B
Can be set
to aObj
Inheritance
Common method calls
A implementing
method X
B implementing
method X
cannot be used unless the objects are
in an inheritance relationship
C
Inheritance
C implementing
method X
D implementing
method X
D
If the same method is implemented, it can be even called
without inheritance relationship
C++ Example
Go Example
Go does not require an explicit class definition like Java, C++, C#, etc do. Instead,
a class is implicitly defined by providing a set of methods that operate on a
common type. This type may be a struct or any other user-defined type.
For example, say we would like to define our Integer type to add some
possible methods for working with integers, like a to-string-conversion. In Go,
we would define this as:
type Integer int
func (i *Integer) String() string {
return strconv.Itoa(i)
}
In Java or C#, the type and the func would be placed together in a class
Integer ; in Ruby, you could write the method on the basic type int.
In Go, types are classes (data and associated methods). Go doesn’t know
inheritance like class-oriented OO languages. Inheritance has two main
benefits, code reuse and polymorphism.
Code reuse in Go is achieved through composition and delegation, and
polymorphism is achieved through the use of interfaces: it implements what is
sometimes called component programming. Many developers say that Go’s
interfaces provide a more powerful and yet simpler polymorphic behavior
than class inheritance.
If you need more OO capabilities, take a look at the goop package (“Go ObjectOriented Programming”) from Scott Pakin (click here). It provides Go with
JavaScript-style objects (prototype-based objects) but supports multiple
inheritance and type-dependent dispatch so you can probably implement
most of your favorite constructs from other programming languages.
Now you are familiar with the use of arrays, in the next lesson, you have to
write a program to solve a problem.
Challenge: Implement Stack Data Structure
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Implement the stack data structure. It has cells to contain
data. For example, integers 1 , 2 , 3 , 4 , and so on. The cells
are indexed from the bottom (index 0) to the top (index n).
Let’s assume n=3 for this exercise, so we have 4 places. A
new stack contains 0 in all cells. A new value is put in the
highest cell, which is empty (containing 0) on top (of the
stack): this is called push. To get a value from the stack, take
4
3
2
1
the value in the highest cell which is not 0: this is called pop.
We can understand why a stack is called a Last In First Out
(LIFO) structure.
Define a new type Stack for this data structure. Make 2 methods Push and
Pop . Make a String() method (for debugging purposes) that shows the
content of the stack as: [0:i] [1:j] [2:k] [3:l] . Take the underlying data
structure, a struct containing an index , an array data of int, and the ix
contains the first free position.
Generalize the implementation by making the number of elements 4 a
constant LIMIT .
Note: Stack is the struct type, and ix and data[LIMIT] are its fields. The
variable ix denotes total elements present in the stack, and data holds
the element of a stack. Do not change the name of these variables.
Try to implement the function below. Feel free to view the solution, after
giving some shots. Good Luck!
package main
import "fmt"
import "strings"
import "strconv"
import "encoding/json"
const LIMIT = 4 // DONOT CHANGE IT!
type Stack struct {
ix
int // first free position, so data[ix] == 0
data [LIMIT]int
}
func (st *Stack) Push(n int) {
return
}
func (st *Stack) Pop() int {
return 0
}
func (st Stack) String() string {
return ""
}
Implement Stack Data Structure
We hope that you were able to solve the challenge. The next lesson brings you
the solution of this challenge.
Solution Review: Implement Stack Data Structure
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"fmt"
"strconv"
)
const LIMIT = 4 // DONOT CHANGE IT!
type Stack struct {
ix
int // first free position, so data[ix] == 0
data [LIMIT]int
}
func (st *Stack) Push(n int) {
if st.ix == LIMIT {
return // stack is full!
}
st.data[st.ix] = n
st.ix++
// increment total no. of elements present
}
func (st *Stack) Pop() int {
if st.ix >0{
// if stack not empty
st.ix-// decrease amount of elements present
element := st.data[st.ix]
st.data[st.ix] = 0
return element
}
return -1
// if stack already empty
}
func (st Stack) String() string {
str := ""
for ix := 0; ix < st.ix; ix++ {
str += "[" + strconv.Itoa(ix) + ":" + strconv.Itoa(st.data[ix]) + "] "
}
return str
}
func main() {
st1 := new(Stack)
fmt.Printf("%v\n", st1)
st1.Push(3)
// function call to Push
fmt.Printf("%v\n", st1)
st1.Push(7) // function call to Push
fmt.Printf("%v\n", st1)
st1.Push(10) // function call to Push
fmt.Printf("%v\n", st1)
st1.Push(99) // function call to Push
fmt.Printf("%v\n", st1)
p := st1.Pop() // function call to Pop
fmt.Printf("Popped %d\n", p)
fmt.Printf("%v\n", st1)
p = st1.Pop() // function call to Pop
fmt.Printf("Popped %d\n", p)
fmt.Printf("%v\n", st1)
p = st1.Pop() // function call to Pop
fmt.Printf("Popped %d\n", p)
fmt.Printf("%v\n", st1)
p = st1.Pop() // function call to Pop
fmt.Printf("Popped %d\n", p)
fmt.Printf("%v\n", st1)
}
Implement Stack Data Structure
In the above code, look at line 8. As per the requirement to generalize the
implementation, we make a constant LIMIT and set its value to 4. This means
that our stack can, at maximum hold four values. Now that we have defined
the limit of the stack, we make a struct of type Stack at line 10. It has two
fields. The first field is an integer field ix that keeps track of how many
elements are currently in the stack. The next field is an array of integers data
of capacity LIMIT . It keeps track of the elements that are present in a stack.
When you declare a Stack type variable, ix will be 0 and the data array will
hold four zeros, initially. Now, let’s see how the basic functionalities (push and
pop) of the stack are implemented.
Look at the header of method Push at line 15: func (st *Stack) Push(n int) .
The header makes it clear that only the pointer to the stack object can call this
method, and n is the element that is to be pushed in the stack. Now, the
question is, would you push every time? No, you can’t push every single time
because what if the stack is full ( ix equals LIMIT ) and you are trying to push
an element at the top. In that case, it would give an error. Look at line 16, we
start the implementation from this condition: if st.ix == LIMIT . If this
condition is satisfied( ix of the stack st is equal to LIMIT ), then control will
exit from the function (see line 17). If not, then we’ll add n in the stack at the
top. Here, top means the index that is free from the end. Consider an example,
if a stack is empty then ix will be 0. So, n should be pushed at position 0. If
there is already one element in the stack, then ix will be one. So, n will be
inserted at index 1. This means that every time n will be inserted at the ix
position. See line 19. We are inserting n at the ix position of the data array.
In the next line, we are incrementing ix because we need to create space for
an empty top for the next push operation until the stack is full.
Look at the header of method Pop at line 23: func (st *Stack) Pop() int . The
header makes it clear that only the pointer to the stack object can call this
method, and the popped element is returned from the method. Now, the
question is: would you pop every time? No, you can’t pop every single time
because what if the stack is empty ( ix equals 0 ), and you are trying to pop an
element at the top. This would give an error. Look at line 24. We start the
implementation from this condition: st.ix >0 . If this condition is not satisfied
(stack is empty), then control will exit from the function (see line 30). If
satisfied, then we’ll pop the element at the top. Here, top means the index that
is free from the end. Consider an example. If there is already one element in
the stack, then ix will be one, and the new element can be inserted at
position 1. So, this means to pop an element you have to remove the element
from position 0; that is ix-1 . Look at line 25. We are decrementing ix by 1 to
access the recently added value, and after the pop function, it’s obvious we
have to decrease ix because the number of elements decreases by 1. Before
popping, we store the popped element in element , a separate variable. Before
returning element we placed 0 at the position of the popped element, so that
position looks empty. In this case, when the stack is empty we are returning
-1.
Before going to main , let’s see debugging function String() . Look at the
header of String method at line 33: func (st Stack) String() string . It
shows that the method can be called by the object of type Stack , and returns a
string. The format of the returned string is [Index: Value at Index] . To make
a string that shows the stack, we have to run a loop till ix times ( see line 35),
so that this pattern can be obtained: [0:Value at Index 0] [1:Value at Index
1] ... [ix-1:Value at Index ix-1] .
Now, look at main . At line 42, we make a pointer type variable st1 of type
Stack using the new function. In the next line, we call Printf function. This
call will automatically transfer control to the String method declared at line
33. As the string is empty, an empty string will be printed on the screen.
At line 44, we are calling the Push method on st1 for n equals to 3. In the
next line, we call Printf function. This call will automatically transfer control
to the String method declared at line 33. As the stack contains 3 at position 0,
[0:3] will be printed on screen. At line 46, we are calling the Push method on
st1 for n equals to 7. In the next line, we call the Printf function. As the
stack now contains 7 at position 1, [0:3] [1:7] will be printed on screen. At line
48, we are calling the Push method on st1 for n equals to 10. In the next line,
we call Printf function. As the stack now contains 10 at position 2, [0:3] [1:7]
[2:10] will be printed on screen. At line 50, we are calling the Push method on
st1 for n equals to 99. In the next line, we call the Printf function. As the
stack now contains 99 at position 3, [0:3] [1:7] [2:10] [3:99] will be printed on
screen.
Now, at line 52, we are calling Pop on st1 . With this pop, the last inserted
value, which is 99, will be popped and returned. The popped value is printed
in the next line. At line 54, we call the Printf function. As 99 from position 3
was removed, [0:3] [1:7] [2:10] will be printed on screen. Now, at line 55, we
are calling Pop on st1 . With this pop, the last inserted value, which is 10, will
be popped and returned. The popped value is printed in the next line. At line
57, we call Printf function. As 10 from position 2 was removed, [0:3] [1:7]
will be printed on screen. Now, again at line 58, we are calling Pop on st1 .
With this pop, the last inserted value, which is 7, will be popped and returned.
The popped value is printed in the next line. At line 60, we call the Printf
function. As 7 from position 1 was removed, [0:3] will be printed on screen.
Now, again at line 61, we are calling Pop on st1 . With this pop, the last
inserted value, which is 3, will be popped and returned. The popped value is
printed in the next line. At line 63, we call the Printf function. As 3 from the
position 0 was removed, an empty string will be printed because the stack is
empty now.
That’s it about the solution. In the next lesson, you’ll study the concept of
garbage in Go.
Garbage Collection and SetFinalizer
This lesson brie y discusses how garbage collector and nalizer work in Go.
WE'LL COVER THE FOLLOWING
• Collecting garbage
Collecting garbage #
The Go developer doesn’t have to code the release of
memory for variables and structures, which are not used
anymore in the program. A separate process in the Go
runtime, the garbage collector, takes care of that. It starts
now and then searches for variables which are not listed
anymore, and frees that memory. Functionality regarding
this process can be accessed via the runtime package.
Garbage collection can be called explicitly by invoking the function
runtime.GC() , but this is only useful in rare cases, e.g., when memory
resources are scarce. In that case, a great chunk of memory could
immediately be freed at that point of the execution, and the program can take
a momentary decrease in performance (because of the garbage collection
process).
If you want to know the current memory status, use:
fmt.Printf("%d\n", runtime.MemStats.Alloc/1024)
This will give you the amount of allocated memory by the program in Kb. For
further measurements, visit this page.
Suppose special action needs to be taken before an object obj is removed from
memory, like writing to a log-file. This can be achieved by calling the function:
runtime.SetFinalizer(obj, func(obj *typeObj))
The func(obj *typeObj) is a function that takes a pointer-parameter of the
type of obj , which performs the additional action. func could also be an
anonymous function.
SetFinalizer does not execute when the program comes to a normal end or
when an error occurs, but before the object was chosen by the garbage
collection process to be removed.
That’s it about how the runtime package provides support to collect garbage
and finds the lost references of an object. There is a quiz in the next lesson for
you to solve.
Quiz: Deduce the Outputs
In this lesson your concepts will be evaluated through a quiz.
Choose one correct answer
1
What’s wrong with the following code?
package main
import "container/list"
func (p *list.List) Iter() {
// ...
}
func main() {
lst := new(list.List)
for _ = range lst.Iter() {
}
}
COMPLETED 0%
1 of 6
We hope that you performed well in the quiz. That’s it about the structs and
methods. In the next chapter, we’ll discuss interfaces in Go.
What is an Interface?
This lesson gives a brief introduction to interfaces and explains how Go, having no concepts of classes and
inheritance, implements the OO behavior.
WE'LL COVER THE FOLLOWING
• Introduction to interface
• Explanation
• An example from the standard library
Introduction to interface #
Go is not a classic OO language. It doesn’t recognize the concept of classes and
inheritance. However, it does contain the very flexible concept of interfaces,
with which a lot of aspects of object-orientation can be made available.
Interfaces in Go provide a way to specify the behavior of an object.
An interface defines a set of methods (the method set), but these methods do
not contain code: they are not implemented (they are abstract). Also, an
interface cannot contain variables. An interface is declared in the format:
type Namer interface {
Method1(param_list) return_type
Method2(param_list) return_type
...
}
Namer is an interface type. The name of an interface is formed by the method
name plus the [er] suffix, such as Printer, Reader, Writer, Logger, Converter,
and so on, thereby giving an active noun as a name. A less-used alternative
(when …er is not so appropriate) is to end it with able, like in Recoverable or
to start it with an I (more like in .NET or Java). Interfaces in Go are short; they
usually have one two three methods (except for the empty interface, which
has 0 methods).
Unlike in most OO languages, in Go, interfaces can have values that are a
variable of the interface type or an interface value:
var ai Namer
ai is a multiword data structure with an uninitialized value of nil. Although
not entirely the same thing, ai is, in essence, a pointer. So, pointers to
interface values are illegal; they would be wholly useless and give rise to
errors in code.
ai:
receiver value
method table
ptr
Interface Value in Memory
Explanation #
Types (like structs) can have the method set of the interface implemented.
The implementation contains real code for each method and how to act on a
variable of that type: they implement the interface. The method set forms the
interface of that type. A variable of a type that implements the interface can
be assigned to ai (the receiver value), and the method table then has pointers
to the implemented interface methods. Of course, both of these change when
a variable of another type (that also implements the interface) is assigned to
ai . Note the following important points:
A type doesn’t have to state explicitly that it implements an interface;
interfaces are satisfied implicitly.
Multiple types can implement the same interface.
A type that implements an interface can also have other functions.
A type can implement many interfaces.
An interface type can contain a reference to an instance of any of the
types that implement the interface.
Even if the interface was defined later than the type, in a different package or
compiled separately: if the object implements the methods named in the
interface, then it implements the interface. All these properties allow for a lot
of flexibility.
As a first example look below:
package main
import "fmt"
type Shaper interface {
Area() float32
}
type Square struct {
side float32
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
func main() {
sq1 := new(Square)
sq1.side = 5
var areaIntf Shaper
areaIntf = sq1
// shorter, without separate declaration:
// areaIntf := Shaper(sq1)
fmt.Printf("The square has area: %f\n", areaIntf.Area())
}
Interfaces in Go
In the program above, we define an interface Shaper at line 4, with one
method Area() returning float32 value and a struct of type Square at line 8,
with one field side of type float32. At line 12, we define a method Area() that
can be called by a pointer to the Square type object. This method returns the
area of a square sq . The struct Square implements the interface Shaper . Now,
the interface variable contains a reference to the Square variable, and
through it, we can call the method Area() on Square .
Look at the main . We make a Square type variable sq1 at line 17 and give a
value of 5 to its field side (at line 18). As discussed above, you could call the
method immediately on the Square value sq1.Area() , but the novel thing is
that we can call it on the interface value (see line 23), thereby generalizing the
call.
The interface variable both contains the value of the receiver value and a
pointer to the appropriate method in a method table.
This is Go’s version of polymorphism, a well-known concept in OO software.
The right method is chosen according to the current type or put otherwise. A
type seems to exhibit different behaviors when linked to different values. If
Square didn’t have an implementation of Area() , we would receive the clear
compiler error: cannot use sq1 (type *Square) as type Shaper in assignment:
*Square does not implement Shaper (missing Area method) .
The same error would occur if Shaper had another method Perimeter() , and
Square would not have an implementation for that. We expand the example
with a type Rectangle which also implements Shaper . Now, we can make an
array with elements of type Shaper and show polymorphism in action by
using a for range on it and calling Area() on each item:
package main
import "fmt"
type Shaper interface {
Area() float32
}
type Square struct {
side float32
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
type Rectangle struct {
length, width float32
}
func (r Rectangle) Area() float32 {
return r.length * r.width
}
func main() {
r := Rectangle{5, 3} // Area() of Rectangle needs a value
q := &Square{5} // Area() of Square needs a pointer
// shapes := []Shaper{Shaper(r), Shaper(q)}
// or shorter:
shapes := []Shaper{r, q}
fmt.Println("Looping through shapes for area ...")
for n, _ := range shapes {
fmt.Println("Shape details: ", shapes[n])
fmt.Println("Area of this shape is: ", shapes[n].Area())
}
}
Interface with 2 Different Structs
In the above program, we define an interface Shaper at line 4 with one
method Area() , returning a float32 value and a struct of type Square at line 8
with one field side of type float32. At line 12, we define a method Area() that
can be called by a pointer to the Square type object. Again at line 16, we make
a struct of type Rectangle at line 16 with one field side of type float32. At
line 20, we define a method Area() that can be called by the Rectangle type
object.
Now, look at main . We make a value type Rectangle object and a pointer type
Square object. As the interface variable contains the reference to the Square
and Rectangle type object, we create a Shaper type array shapes and add r
(a Rectangle object) and q (a Square object) in it.
Our goal is to find the area of all the shapes present in shapes . We use a for
loop at line 31. For each iteration, we print the details of a shape in shapes
(see line 32) and print the area (see line 33). In this case, our first shape is the
rectangle r . So, line 32 will output {5,3} on the screen. At line 33, Area() ,
having the object of Rectangle as a receiver (see line 20), will be called, and
15 will be returned from the method and printed on the screen by line 33. In
the next iteration, our shape is the square q . So, line 32 will output &{5} on
the screen, as q is the pointer variable. At line 33, Area() , having a pointer to
the object of Square as a receiver (see line 12), will be called, and 25 will be
returned from the method and printed on screen by line 33.
Perhaps, you can now begin to see how interfaces can produce cleaner,
simpler, and more scalable code.
An example from the standard library #
The io package contains an interface type Reader :
type `Reader` interface {
Read(p []byte) (n int, err error)
}
If we define a variable r as:
var r io.Reader
then the following is correct code:
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
f,_ := os.Open("test.txt")
r = bufio.NewReader(f)
because the right-hand side objects each implement a Read() method with the
exact same signature. The static type of r is io.Reader .
Remark: Sometimes the word interface is also used in a slightly different
way: seen from the standpoint of a particular type, the interface of that
type is the set of exported methods defined for that type, without there
having to be an explicit interface type defined with these methods. This
is better called the API (Application Programming Interface) of the type.
Now that you know the purpose of interfaces in Go, in the next lesson, you
have to write a program to solve a problem.
Challenge: Make a Simple Interface
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Define an interface Simpler with methods Get() , which returns an integer,
and Set() which has an integer as a parameter. Make a struct type Simple ,
which implements this interface.
Then, define a function that takes a parameter of the type Simpler and calls
both methods upon it. Call this function from main to see if it all works
correctly.
Try to implement the challenge below. Good Luck!
package main
type Simpler interface {
// interface implementing functions called on struct Simple
}
type Simple struct {
}
func (p *Simple) Get() int {
return 0
}
func (p *Simple) Set(u int) {
return
}
func fI(it Simpler) int {
// function calling both methods through interface
return 0
}
func main() {
}
Implementing a Simple Interface
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Make a Simple Interface
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"fmt"
)
type Simpler interface {
Get() int
Set(int)
}
// interface implementing functions called on Simple struct
type Simple struct {
i int
}
func (p *Simple) Get() int {
return p.i
}
func (p *Simple) Set(u int) {
p.i = u
}
func fI(it Simpler) int {
it.Set(5)
return it.Get()
}
func main() {
var s Simple
fmt.Println(fI(&s))
}
// function calling both methods through interface
// &s is required because Get() is defined with a receiver type
Implementing a Simple Interface
In the code above, from line 6 to line 9, we define the interface Simpler by
specifying the signature of the functions Get and Set . The Get() function
returns an integer, and the Set(int) function takes an integer as a parameter.
Note that because these are only function descriptions, we do not have to
specify variables’ names here.
The type Simple itself is defined from line 11 to line 13. It contains a field i
of type int. In order for Simple to implement the interface Simpler , it has to
implement the functions Get and Set as methods, working on a receiver
argument p of type *Simple (see line 15 and line 19). The Get will return the
value of field i of p , as shown at line 16. The Set will take an integer u and
change p.i to this value, as shown at line 20.
Our test function fI (from line 23 to line 26) will take a parameter it of type
Simpler and will call both methods of Simpler . The parameter it will have to
contain an integer field at the very least ( it could contain additional fields as
well). We will first call Set (line 24) to give the integer field an arbitrary value
5, and then return the value of the integer field with a call to Get (line 25).
Finally, in main() we define a variable s of type Simple at line 29. Then, at
line 30, we pass a reference to s ( &s ) as a parameter to the test function fI .
&s is required because Get() is defined with a receiver type pointer *Simple .
The function fI first sets s.i to 5, and then gets the value of s.i , which is
printed out.
That’s it about the solution. In the next lesson, you’ll see how an interface can
be embedded inside another interface.
Embedded Interface and Type Assertions
This lesson explains how embedding the interfaces works like a charm and how a function is called depending
upon the type of interface decided at runtime.
WE'LL COVER THE FOLLOWING
• Interface embedding interface(s)
• Detecting and converting the type of an interface variable
Interface embedding interface(s) #
An interface can contain the name of one (or more) interface(s), which is
equivalent to explicitly enumerating the methods of the embedded interface
in the containing interface. For example, the interface File contains all the
methods of ReadWrite and Lock , in addition to a Close() method:
type ReadWrite interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type Lock interface {
Lock()
Unlock()
}
type File interface {
ReadWrite
Lock
Close()
}
Detecting and converting the type of an interface
variable #
An interface type variable varI can contain a value of any type; we must have
a means to detect this dynamic type, which is the actual type of the value
stored in the variable at run time. The dynamic type may vary during
execution but is always assignable to the type of the interface variable itself.
In general, we can test if varI contains at a certain moment a variable of type
T with the type assertion test:
v := varI.(T) // unchecked type assertion
varI must be an interface variable. If not, the compiler signals the error:
invalid type assertion: varI.(T) (non-interface type (type of varI) on
left)
A type assertion may not be valid. The compiler does its utmost best to see if
the conversion is valid, but it cannot foresee all possible cases. If this
conversion fails while running the program, a runtime error occurs! A safer
way is to use the following form:
if v, ok := varI.(T); ok { // checked type assertion
Process(v)
return
}
// here varI is not of type T
If this conversion is valid, v will contain the value of varI converted to type
T and ok will be true. Otherwise, v is the zero value for T and ok is false, so
no runtime error occurs!
Note: Always use the comma, ok form for type assertions
In most cases, you would want to test the value of ok in an if. Then, it is most
convenient to use the form:
if v, ok := varI.(T); ok {
// ...
}
In this form, shadowing the variable varI by giving varI and v the same
name is sometimes done.
An example can be seen below:
package main
import (
"fmt"
"math"
)
type Square struct {
side float32
}
type Circle struct {
radius float32
}
type Shaper interface {
Area() float32
}
func main() {
var areaIntf Shaper
sq1 := new(Square)
sq1.side = 5
areaIntf = sq1
// Is Square the type of areaIntf ?
if t, ok := areaIntf.(*Square); ok {
fmt.Printf("The type of areaIntf is: %T\n", t)
}
if u, ok := areaIntf.(*Circle); ok {
fmt.Printf("The type of areaIntf is: %T\n", u)
} else {
fmt.Println("areaIntf does not contain a variable of type Circle")
}
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
func (ci *Circle) Area() float32 {
return ci.radius * ci.radius * math.Pi
}
Type Assertions
In the above code, at line 7, we make a struct of type Square with one field
side of type float32. Similarly, at line 11, we make a struct of type Circle
with one field radius of type float32. At line 15, we make an interface Shaper
with a single method Area() . At line 35, we define a method Area() that can
be called by a pointer to the Square type object. Again, at line 39, we define a
method Area() that can be called by a pointer to the Circle type object.
Now, look at main . We create a Shaper variable areaIntf at line 20. In the
next line, we make a Square variable sq1 and assign it to areaIntf . At line 25,
we check whether areaIntf contains a reference to the type Square using the
if condition. If ok is true, control will transfer to line 26, and its type will be
printed. Otherwise, control will transfer to line 28. At line 28, we are checking
whether areaIntf contains a reference to the type Circle using the if
condition.
If ok is true, control will transfer to line 29, and its type will be printed.
Otherwise, control will transfer to line 31, and a message will be printed on
the screen that such type doesn’t exist.
Remark: If we omit the * in areaIntf.(*Square) , we get the compilererror: impossible type assertion: areaIntf (type Shaper) cannot have
dynamic type Square (missing Area method) .
Now, that you know how to embed interfaces, in the next lesson, we’ll try a
combination of switch-cases control structure and interfaces.
The Type Switch
In this lesson, you'll learn how to use the switch statement to determine the type of interface and writing cases
accordingly.
WE'LL COVER THE FOLLOWING
• Testing the type of interface
Testing the type of interface #
The type of an interface variable can also be tested with a special kind of
switch: the type-switch. Look at the following program:
package main
import (
"fmt"
"math"
)
type Square struct {
side float32
}
type Circle struct {
radius float32
}
type Shaper interface {
Area() float32
}
func main() {
var areaIntf Shaper
sq1 := new(Square)
sq1.side = 5
areaIntf = sq1
switch t := areaIntf.(type) {
case *Square:
fmt.Printf("Type Square %T with value %v\n", t, t)
case *Circle:
fmt.Printf("Type Circle %T with value %v\n", t, t)
default:
fmt.Printf("Unexpected type %T", t)
}
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
func (ci *Circle) Area() float32 {
return ci.radius * ci.radius * math.Pi
}
The Type Switch
The variable t receives both value and type from areaIntf . All of the listed
types have to implement the interface ( Shaper in this case); if the current type
is none of the case-types, the default clause is executed. Fallthrough is not
permitted. With a type-switch, a runtime type analysis can be done. Of course,
all the built-in types as int, bool, and string can also be tested in a type switch.
Never use element.(type) outside of a switch statement.
In the following code snippet, a type classifier function is shown which
accepts an array with a variable number of arguments of any type and
executes something according to the determined type:
func classifier(items ...interface{}) {
for i, x := range items {
switch x.(type) {
case bool: fmt.Printf("param #%d is a bool\n", i)
case float64: fmt.Printf("param #%d is a float64\n", i)
case int, int64: fmt.Printf("param #%d is an int\n", i)
case nil: fmt.Printf("param #%d is nil\n", i)
case string: fmt.Printf("param #%d is a string\n", i)
default: fmt.Printf("param #%d's type is unknown\n", i)
}
}
}
For example, this function could be called as a classifier(13, -14.3,
“BELGIUM”, complex(1, 2), nil, false). When dealing with data of an
unknown type from external sources, type testing and conversion to Go data
types can be very useful, e.g. parsing data that are JSON- or XML-encoded.
That’s it about checking the type of an interface variable with a type-switch; in
the next lesson, you have to write a program to solve a problem.
Challenge: Advancing the Simple Interface
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Continuing with the previous challenge, make a second type RSimple which
also implements the interface Simpler . Expand the function fI so that it can
distinguish between variables of both types.
Try to solve the challenge below. Good Luck!
package main
type Simpler interface {
}
type Simple struct {
}
func (p *Simple) Get() int {
return 0
}
func (p *Simple) Set(u int) {
}
type RSimple struct {
}
func (p *RSimple) Get() int {
return 0
}
func (p *RSimple) Set(u int) {
}
func fI(it Simpler) int {
return 0
}
func main() {
}
Advancing the Simple Interface
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Advancing the Simple Interface
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"fmt"
)
type Simpler interface {
Get() int
Set(int)
}
// interface declaring Simple methods
type Simple struct {
i int
}
func (p *Simple) Get() int {
return p.i
}
func (p *Simple) Set(u int) {
p.i = u
}
type RSimple struct {
i int
j int
}
func (p *RSimple) Get() int {
return p.j
}
func (p *RSimple) Set(u int) {
p.j = u
}
func fI(it Simpler) int { // switch cases to judge whether it's Simple or RSimple
switch it.(type) {
case *Simple:
it.Set(5)
return it.Get()
case *RSimple:
it.Set(50)
return it.Get()
default:
return 99
}
return 0
}
func main() {
var s Simple
fmt.Println(fI(&s))
var r RSimple
fmt.Println(fI(&r))
}
// &s is required because Get() is defined with the type pointer
Advancing a Simple Interface
As we stated in the discussion of the previous challenge, we can make other
types that implement the interface Simpler ; the only condition is that they
have at least one integer field. We illustrate this here by defining a type
RSimple (see implementation from line 23 to line 26), which has two integer
fields i and j . We define the Get and Set methods on RSimple in the exact
same way as for Simple (see implementation from line 28 to line 34).
In our test function fI , we want to differentiate between a parameter of type
Simple (which we will give a value of 5) and a parameter of type RSimple
(which we will give a value of 50). To do this, we’ll make use of the type
switch:
At line 38, we have our first case. If the type is *Simple , we’ll set the
value 5 via Set and return it via Get .
At line 41, we have our second case. If the type is *RSimple , we’ll set the
value 50 via Set and return it via Get .
At line 44, we have our default case. If none of the above cases are true,
it’ll simply return 99.
Because the methods work on pointers to the struct types ( *Simple and
*RSimple ), we have to use pointer types in the cases as well.
In our main() function, we call fI with a reference to a variable r of type
RSimple at line 54, just as we did for type Simple before.
That’s it for the solution. In the next lesson, you’ll study how to implement
interfaces.
Implementation of Interfaces
This lesson covers the details about how to test the implementation of interfaces.
WE'LL COVER THE FOLLOWING
• Testing if a value implements an interface
• Using method sets with interfaces
Testing if a value implements an interface #
This is a special case of the type assertion: suppose v is a value, and we want
to test whether it implements the Stringer interface. This can be done as
follows:
type Stringer interface { String() string }
if sv, ok := v.(Stringer); ok {
fmt.Printf("v implements String(): %s\n", sv.String()); // note: sv, no
t v
}
An interface is a kind of contract, which the implementing type(s) must fulfill.
Interfaces describe the behavior of types, specifying what types can do. They
completely separate the definition of what an object can do from how it does
it, allowing distinct implementations to be represented at different times by
the same interface variable, which is what polymorphism essentially is.
Writing functions so that they accept an interface variable as a parameter
makes them more general.
Note: Use interfaces to make your code more generally applicable.
This is also ubiquitously applied in the code of the standard library. It is
impossible to understand how it is built without a good grasp of the interface-
concept.
Using method sets with interfaces #
In Chapter 8, we saw that methods on variables do not distinguish between
values or pointers. When storing a value in an interface type, it is slightly
more complicated because a concrete value stored in an interface is not
addressable. Still, luckily the compiler flags an error on improper use.
Consider the following program:
package main
import (
"fmt"
)
type List []int
func (l List) Len() int { return len(l) }
func (l *List) Append(val int) { *l = append(*l, val) }
type Appender interface {
Append(int)
}
func CountInto(a Appender, start, end int) {
for i := start; i <= end; i++ {
a.Append(i)
}
}
type Lener interface {
Len() int
}
func LongEnough(l Lener) bool {
return l.Len()*10 > 42
}
func main() {
// A bare value
var lst List
// compiler error:
// cannot use lst (type List) as type Appender in function argument:
// List does not implement Appender (Append method requires pointer receiver)
// CountInto(lst, 1, 10)
if LongEnough(lst) { // VALID: Identical receiver type
fmt.Printf(" - lst is long enough\n")
}
// A pointer value
plst := new(List)
CountInto(plst, 1, 10) // VALID: Identical receiver type
if LongEnough(plst) { // VALID: a *List can be dereferenced for the receiver
fmt.Printf(" - plst is long enough\n") // - plst2 is long enough
}
}
In the above code at line 6, we define a type for a slice of ints as List . At line
8, we make a method that returns the length of a list of type List . Then, at
line 10, we make another method Append() that appends a value val in a list
with a pointer to that list as receiver.
At line 12, we have an interface Appender with one method Append(int) ,
implemented by List type. Then, at line 16, we have a function CountInto
that takes an Appender type object a and the start and end value to append
values ranging from start to end with a for loop.
At line 22, we have an interface Lener with one method Len() , implemented
by List type. Then, at line 26, we have a function longEnough that takes an
Lener type object l and returns true if length of l multiplied by 10 is greater
than 42. Otherwise, it returns false.
Now, look at the main . We make a List variable lst at line 32. At line 37, we
call the LongEnough function on lst . In this case, the function returns
false(0*10>42), so control will directly transfer to line 41. At line 41, we make
a List variable plst using the new() function and call the CountInto
function with plst as an Appender , start as 1 and end as 10. Now, the length
of plst is 10. In the next line, we call the LongEnough function on plst . In this
case, the function returns true(10*10>42), so control will directly transfer to
line 44, and the message will be printed on screen.
CountInto called with the value lst gives a compiler error because CountInto
takes an Appender, and Append() is only defined for a pointer. LongEnough on
value lst works because Len() is defined on a value. CountInto called with
the pointer plst works because CountInto takes an Appender, and Append()
is defined for a pointer. LongEnough on pointer plst works because a pointer
can be dereferenced for the receiver.
When you call a method on an interface, it must either have an identical
receiver type or it must be directly discernible from the concrete type:
Pointer methods can be called with pointers.
Value methods can be called with values.
Value-receiver methods can be called with pointer values because they
can be dereferenced first.
Pointer-receiver methods cannot be called with values; however, because
the value stored inside an interface has no address.
When assigning a value to an interface, the compiler ensures that all possible
interface methods can be called on that value, so trying to make an improper
assignment will fail on the compilation.
Implementing all the methods of an interface is a must. Otherwise, the
compilation fails. The next lesson shows the implementation of an interface
and demonstrates how to use an interface to sort some data.
Sorting with the Sorter Interface
This lesson provides a detailed code and explanation of an interface designed in a separate package and
implemented in the main package.
WE'LL COVER THE FOLLOWING
• Sorting the data
Sorting the data #
An excellent example comes from the Go standard library itself, namely the
package sort (look up the documentation of this package here).
To sort a collection of numbers or strings, you only need the number of
elements Len() , a way to compare items i and j with Less(i, j) and a
method to swap items with indexes i and j with Swap(i, j) . The Sortfunction in sort has an algorithm that only uses these methods on a collection
data (to implement it we use here a bubble sort, but any sort-algorithm could
be used):
func Sort(data Sorter) {
for pass:=1; pass < data.Len(); pass++ {
for i:=0; i < data.Len() - pass; i++ {
if data.Less(i+1, i) {
data.Swap(i, i+1)
}
}
}
}
Sort can accept a general parameter of an interface type Interface , which
declares these methods:
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
The type int in Interface does not mean that the collected data must contain
ints, i and j are integer indices, and the length is also an integer. Note that a
type implementing the methods of Interface can be a collection of data of any
type.
Now, if we want to be able to sort an array of ints, all we must do is to define a
type and implement the methods of Interface . For example, for a type
IntSlice the package defines:
type IntSlice []int
func (p IntSlice) Len() int { return len(p) }
func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p IntSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
Here, is the code to call the sort-functionality in a concrete case:
data := []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984,
7586}
a := sort.IntSlice(data) //conversion to type IntSlice from package sort
sort.Sort(a)
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package mysort
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
func Sort(data Interface) {
for pass:=1; pass < data.Len(); pass++ {
for i:=0; i < data.Len() - pass; i++ {
if data.Less(i+1, i) {
data.Swap(i, i+1)
}
}
}
}
func IsSorted(data Interface) bool {
n := data.Len()
for i := n - 1; i > 0; i-- {
if data.Less(i, i-1) {
return false
}
}
return true
}
// Convenience types for common cases
type IntSlice []int
func (p IntSlice) Len() int { return len(p) }
func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p IntSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
type StringSlice []string
func (p StringSlice) Len() int { return len(p) }
func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// Convenience wrappers for common cases
func SortInts(a []int) { Sort(IntSlice(a)) }
func SortStrings(a []string) { Sort(StringSlice(a)) }
func IntsAreSorted(a []int) bool { return IsSorted(IntSlice(a)) }
func StringsAreSorted(a []string) bool { return IsSorted(StringSlice(a)) }
In the file mysort.go:
We make an interface Interface with three methods Len() , Less() and
Swap() , as discussed above. At line 9, we have a function header of Sort
that takes Interface as a parameter, and sorts the data of a collection
using the Len() , Less() and Swap() functions.
Then, at line 19, we have a function header of IsSorted that takes
Interface as a parameter and returns true if the data is sorted.
Otherwise, false. At line 30, we define a type IntSlice for []int.
From line 32 to line 36, we implement the methods Len() , Less() and
Swap() for slice of ints. At line 38, we define a type StringSlice for
[]string.
From line 40 to line 45, we implement the methods Len() , Less() and
Swap() for slices of strings. Next, we make wrappers of two functions
Sort() and IsSorted() each for IntSlice and StringSlice from line 48
to line 54.
Now, look at the main.go file. Before studying main , let’s see three basic
functions, ints() , strings() , and days() :
Look at the header of function ints() at line 9. We make a slice of
integers data and convert it to type IntSlice by importing:
mysort.IntSlice() at line 11, and saving it in a . Then, we call the Sort
function on a . At line 13, we check whether a is sorted or not by calling
IsSorted function on a . If a is not sorted, control will transfer to line 16,
and the sorted slice will be printed. If, sorted then line 14 will be
executed.
Now, look at the header of function strings() at line 20. We make a slice
of strings data and convert it to type StringSlice by importing it as:
mysort.StringSlice() at line 22, and saving it in a . Then, we call the
Sort function on a . At line 24, we check whether a is sorted or not by
calling the IsSorted function on a . If a is not sorted, control will
transfer to line 27, and the sorted slice will be printed. If sorted, then line
25 will be executed.
Now, before studying the days() function, look at the definition of day
type struct at line 31. It has three fields: one num and integer field and
two strings fields shortName and longName . Then, at line 37, we make
another struct of type dayArray with one field data with a type of pointer
to slice of type day . We implement Len() , Less() and Swap() for
dayArray type (from line41 to line 43), just like we did for IntSlice and
StringSlice in mysort.go.
Now, look at the header of function days() at line 46. At line 47, we make
a day type variable and assign 0 to num , SUN to shortName and Sunday to
longName . Then, we make further six day type variables each for a day
(from line 48 to line 53). Next, we make a slice of these seven day type
variables and convert it into dayArray type and store it in a . At line 56,
we check whether a is sorted or not by calling the IsSorted function on
a . If a is not sorted, control will transfer to line 60, and the sorted slice
will be printed using longName . If sorted, then line 58 will be executed.
Now, look at main . We are just calling ints() , strings() and days() function
at line 67, line 68 and line 69, respectively. At the end, sorted arrays are
shown in output on the screen.
Remarks: The panic("fail") is a way to stop the program in a situation
that could not occur in common circumstances; we could also have
printed a message and then used os.Exit(1) . This code can also be run
with the sort package from Go’s standard library. Replace "./mysort"
with "sort" in main.go, and replace all the calls to mysort. with sort.
This example has given us a better insight into the significance and use of
interfaces. For sorting primitive types, we know that we don’t have to write
this code ourselves. The standard library provides for this. The Interface type
can be used in implementing sorting for other kinds of data. In fact, that is
precisely what we did in the example above, using this for ints and strings,
but also for a user-defined type dayArray , to sort an internal array of strings.
Interfaces make it possible to write generic programs such as the one in this
lesson that can sort data of any type. There is a challenge in the next lesson
for you to solve.
Challenge: Advancing the Shapes Analysis
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Expand the same program given below, define a type Triangle , and let it
implement AreaInterface . Test this by calculating the area of a specific
triangle.
Area of a triangle = 0.5 ∗ (base ∗ height)
Then, define a new interface PeriInterface , which defines a method
Perimeter() . Let the type Square implement this interface and test it with our
square value.
Try to solve the challenge below. Good Luck!
package main
type Square struct {
side float32
}
type Triangle struct {
// implement this struct
}
type AreaInterface interface {
Area() float32
}
type PeriInterface interface { // implement this interface
}
func main() {
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
func (sq *Square) Perimeter() float32 { // implement method called on square to calculate its
return 0
}
func (tr *Triangle) Area() float32 { // implement method called on triangle to calculate its
return 0
}
Advancing the Shape Analysis
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Advancing the Shapes Analysis
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import "fmt"
type Square struct {
side float32
}
type Triangle struct {
base
float32
height float32
}
type AreaInterface interface {
Area() float32
}
type PeriInterface interface {
Perimeter() float32
}
func main() {
var areaIntf AreaInterface
var periIntf PeriInterface
sq1 := new(Square)
sq1.side = 5
tr1 := new(Triangle)
tr1.base = 3
tr1.height = 5
areaIntf = sq1
fmt.Printf("The square has area: %f\n", areaIntf.Area())
periIntf = sq1
fmt.Printf("The square has perimeter: %f\n", periIntf.Perimeter())
areaIntf = tr1
fmt.Printf("The triangle has area: %f\n", areaIntf.Area())
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
func (sq *Square) Perimeter() float32 {
return 4 * sq.side
}
func (tr *Triangle) Area() float32 {
return 0.5 * tr.base*tr.height
}
Advancing the Shape Analysis
From line 8 to line 11, we implement the Triangle type: from the formula for
the area, we see that it needs two fields base and height, both of type float32 .
See the implementation of PeriInterface from line 17 to line 18: it needs a
function Perimeter() , that also returns a float32 .
From line 48 to line 50, the Triangle type implements AreaInterface by
defining the Area() method: given a parameter tr of type *Triangle , the
area is given by the formula: 0.5 * tr.base * tr.height .
From line 44 to line 46, the Square type implements PeriInterface by
defining the Perimeter() method: given a parameter sq of type *Square , the
perimeter is given by the formula: 4 * sq.side .
In the main() function, we define the variables areaIntf and periIntf of
their respective interface types at line 21 and line 22, respectively. From line
24 to line 28, we define variables sq1 and tr1 of the Square and Triangle
type with new() , and fill in their properties.
At line 30, we demonstrate that we can assign a Square variable sq1 to a
variable areaIntf of type AreaInterface because Square implements
AreaInterface . So, we can call the Area() method on areaIntf ( see line 31).
Similarly, at line 33, we assign a Square variable sq1 to a variable periIntf
of type PeriInterface as Square implements PeriInterface . So, we can call
the Perimeter() method on periIntf ( see line 34).
At line 36, we demonstrate that we can assign a Triangle variable tr1 to a
variable areaIntf of type AreaInterface , because Triangle implements
AreaInterface . So, we can call the Area() method on areaIntf ( see line 37).
That’s it about the solution. In the next lesson, there’s another challenge for
you to solve.
Challenge: Sort People with Sorter Interface
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Define a struct Person with firstName and LastName , and a type Persons as a
[ ]Person . Implement the Sorter interface for Persons and test it. Implement
the Sorter interface in the file mysort.go in mysort folder, and Person in
main.go.
Try to attempt the challenge below. Feel free to view the solution, after giving
some shots. Good Luck!
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package mysort
type Sorter interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
func Sort(data Sorter) {
for pass:=1; pass < data.Len(); pass++ {
for i:=0; i < data.Len() - pass; i++ {
if data.Less(i+1, i) {
data.Swap(i, i+1)
}
}
}
}
func IsSorted(data Sorter) bool {
n := data.Len()
for i := n - 1; i > 0; i-- {
if data.Less(i, i-1) {
return false
}
}
return true
}
// Convenience types for common cases
type IntSlice []int
func (p IntSlice) Len() int { return len(p) }
func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p IntSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
type StringSlice []string
func (p StringSlice) Len() int { return len(p) }
func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// Convenience wrappers for common cases
func SortInts(a []int) { Sort(IntSlice(a)) }
func SortStrings(a []string) { Sort(StringSlice(a)) }
func IntsAreSorted(a []int) bool { return IsSorted(IntSlice(a)) }
func StringsAreSorted(a []string) bool { return IsSorted(StringSlice(a)) }
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Sort People with Sorter Interface
This lesson discusses solution to the challenge given in previous lesson.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package mysort
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
func Sort(data Interface) {
for pass:=1; pass < data.Len(); pass++ {
for i:=0; i < data.Len() - pass; i++ {
if data.Less(i+1, i) {
data.Swap(i, i+1)
}
}
}
}
func IsSorted(data Interface) bool {
n := data.Len()
for i := n - 1; i > 0; i-- {
if data.Less(i, i-1) {
return false
}
}
return true
}
// Convenience types for common cases
type IntSlice []int
func (p IntSlice) Len() int { return len(p) }
func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p IntSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
type StringSlice []string
func (p StringSlice) Len() int { return len(p) }
func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// Convenience wrappers for common cases
func SortInts(a []int) { Sort(IntSlice(a)) }
func SortStrings(a []string) { Sort(StringSlice(a)) }
func IntsAreSorted(a []int) bool { return IsSorted(IntSlice(a)) }
func StringsAreSorted(a []string) bool { return IsSorted(StringSlice(a)) }
From line 8 to line 11, we define a struct Person with two fields: firstName
and LastName . Then at line 13 we define a type Persons as a [ ]Person .
In order to be able to sort Persons , we need to implement the Sorter
interface, which is defined in mysort.go- in the mysort folder. To do this, we
need to define the three methods Len() int , Less(I, j int) bool and Swap(I,
j int) for a variable p of type Persons .
Now look at main.go.
See the implementation of Len function at line 15. Len() amounts to the
size of the array p: len(p) .
In the Less method we have to define how we want to sort a person p[i]
based on its name. We decide to first concatenate the firstName and the
lastName to the full name (see line 18 and line 19). Then at line 20, we
compare the two full names and return in < jn .
See the implementation of Swap function at line 24. It simply swaps the
values of p[i] and p[j] .
Note: The < operator works in this case, as < is defined for strings.
All that is left is to make some Person variables in main() . We make three
different Person variables from line 28 to line 30, and make an array arrP
with them at line 31. To sort this array on the full names of the persons, we
now can call the Sort method from package mysort on arrP at line 33:
mysort.Sort(arrP) .
That’s it about the solution. In the next lesson, you’ll see how Go provides
support for reading and writing mechanisms.
Reading and Writing
This lesson introduces a package provided by Golang, that has implemented interfaces for reading and writing the
information.
WE'LL COVER THE FOLLOWING
• Introduction
• Reading with io package
• Writing with io package
Introduction #
Reading and writing are universal activities in software: reading and writing
files comes to mind first, whereas reading and writing to buffers (e.g., to slices
of bytes or to strings), and to the standard input, output, and error streams,
network connections, pipes, and so on (or to our custom types) comes second.
To make the codebase as generic as possible, Go takes a consistent approach to
reading and writing data.
The package io provides us with the interfaces for reading and writing,
io.Reader and io.Writer :
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
Reading with io package #
You can read from and write to any type as long as the type provides the
methods Read() and Write() necessary to satisfy the reading and writing
interfaces. For an object to be readable, it must satisfy the io.Reader
interface. This interface specifies a single method with the signature,
Read([]byte) (int, error) . The Read() method reads data from the object it
is called on and puts the read data into the given byte slice. It returns the
number of bytes read and an error object, which will be nil if no error
occurred or io.EOF (“End Of File”) and the end of the input was reached, or
some other non-nil value if an error occurred.
Writing with io package #
Similarly, for an object to be writable, it must satisfy the io.Writer interface.
This interface specifies a single method with the signature, Write([]byte)
(int, error) . The Write() method writes data from the given byte slice into
the object the method was called on and returns the number of bytes written
and an error object (which will be nil if no error occurred).
The io Readers and Writers are unbuffered; the package bufio provides for
the corresponding buffered operations and so are especially useful for
reading and writing UTF-8 encoded text files. We see this in action in the
many examples in the next chapter.
Through using as much as possible these interfaces in the signature of
methods, they become as generic as possible: every type that implements
these interfaces can be used by other methods requiring these Reading and
Writing operations.
For example, the function which implements a JPEG decoder takes a Reader as
a parameter and thus can decode from a disk, network connection, gzipped
http, and so on.
We’ll discuss the reading and writing processes in detail in the next chapter.
For now, the next lesson brings us a new concept of empty interfaces.
Empty Interface
Sometimes we have no idea about the types that can be used by the code. Therefore, Go provides us with ‘empty
interfaces’ that let us deal with these values. This lesson shows us how to work with them.
WE'LL COVER THE FOLLOWING
• Introduction
• Constructing an array of a general type or with variables of different types
• Copying a data-slice in a slice of interface{}
• Node structures of general or different types
• Interface to interface
• Type map[string] interface{}
Introduction #
The empty or minimal interface has no methods, and so it doesn’t make any
demands at all.
type Any interface{}
Any variable, any type implements it (not only reference types that inherit
from Object in Java/C#), and any or Any is a good name as an alias and
abbreviation! (It is analogous to the class Object in Java and C#, the base class
of all classes. So, Obj would also fit.) A variable of that interface type var val
interface{} can through assignment receive a variable of any type.
This is illustrated in the program:
package main
import "fmt"
var i = 5
var str = "ABC"
type Person struct {
name string
age int
}
type Any interface{}
// empty interface
func main() {
var val Any
val = 5 // assigning integer to empty interface
fmt.Printf("val has the value: %v\n", val)
val = str // assigning string to empty interface
fmt.Printf("val has the value: %v\n", val)
pers1 := new(Person)
pers1.name = "Rob Pike"
pers1.age = 55
val = pers1 // assigning *Person type variable to empty interface
fmt.Printf("val has the value: %v\n", val)
switch t := val.(type) {
// cases defined on type of val
case int:
// if val is int
fmt.Printf("Type int %T\n", t)
case string: // if val is string
fmt.Printf("Type string %T\n", t)
case bool: // if val is bool
fmt.Printf("Type boolean %T\n", t)
case *Person:
// if val is *Person
fmt.Printf("Type pointer to Person %T\n", *t)
default:
// None of the above types
fmt.Printf("Unexpected type %T", t)
}
}
Empty Interface
In the above code, at line 4 and line 5, we make two global variables i and
str , respectively, and set their values. Then, at line 7, we define a Person type
struct with two fields name (a string) and age (an integer). Next, at line 12, we
make an empty interface Any .
Now, look at main . We make Any type variable val at line 15. In the next line,
we set val to 5. At line 18, val gets the value of str , so in the next line, ABC
(value of str ) will be printed.
At line 20, we make pers1 a Person pointer-type variable. In the next two
lines, we give a value to the internal fields ( Rob Pike as name and 55 as age )
of pers1 . At line 23, val1 is given the value of pers1 . In the next line, val is
printed, which prints the pers1 struct as &{Rob Pike 55}.
Now, we have switch cases ahead. Cases are to be judged on type of val .
The first case will be true if val is of int type.
The second case will be true if val is of string type.
The third case will be true if val is of bool type.
The fourth case will be true if val is of *Person type.
Now, the type will be printed accordingly when one case is found true. In this
case, the last updated value of val was pers1 of type *Person . So, the third
case will become true, and line 33 will be executed.
What if, no case is found true? In this case, we have a default case that prints
the message of the unexpected type.
Each interface{} variable takes up 2 words in memory: one word for the type
of what is contained, and the other word for either the contained data or a
pointer to it.
The following program is an example of usage of the empty interface in a type
switch combined with a lambda function:
package main
import "fmt"
type specialString string
var whatIsThis specialString = "hello"
func TypeSwitch() {
testFunc := func(any interface{}) {
// lambda function in combination with empty
switch v := any.(type) {
case bool: // if v is bool
fmt.Printf("any %v is a bool type", v)
case int: // if v is int
fmt.Printf("any %v is an int type", v)
case float32: // if v is float32
fmt.Printf("any %v is a float32 type", v)
case string: // if v is string
fmt.Printf("any %v is a string type", v)
case specialString: // if v is specialString
fmt.Printf("any %v is a special String!", v)
default: // none of types satisfied
fmt.Println("unknown type!")
}
}
testFunc(whatIsThis)
}
func main() {
TypeSwitch()
}
Type Switch and Lambda Function
In the above code, we define a new type specialString on the basis of string
type at line 4. Then, at line 6, we create a variable whatIsThis of type
specialString and give it a value hello. Now, we have a typeSwitch function
at line 8. In the next line, we declare a function testFunc . This function can
take an empty interface as its parameter.
At line 10, we are making a switch statement with cases dependent on the
type of v passed to testFunc .
The first case will be true if v is of bool type.
The second case will be true if v is of int type.
The third case will be true if v is of float32 type.
The fourth case will be true if val is of string type.
The fifth case will be true if v is of specialString type.
Now, the type will be printed, accordingly, when one case is found true.
What if no case is found true? In this case, we have a default case that prints
the message of an unknown type. At line 25, we pass whatIsThis to testFunc ,
which means that the empty interface is of type specialString here.
Now, look at main . At line 29, we are calling the TypeSwitch function, which
will call the testFunc for whatIsThis . Line 20 will be executed, and any hello
is a special String will be printed on the screen.
Constructing an array of a general type or with
variables of different types #
In Chapter 5, we saw how arrays of ints, floats, and strings can be searched
and sorted. But what about arrays of other types? Do we have to program that
for ourselves? We now know that this is possible by using the empty interface.
Let us give it the alias Element : type Element interface {} Then, define a
container struct Vector, which contains a slice of Element items:
type Vector struct {
a []Element
}
Vectors can contain anything because any type implements the empty
interface; every element could be of different types. We can define a method
At() that returns the i-th element:
func (p *Vector) At(i int) Element {
return p.a[i]
}
And a function Set() that sets the i-th element:
func (p *Vector) Set(i int, Element e) {
p.a[i] = e
}
Everything in the vector is stored as an Element to get the original type back
(unboxing); we need to use type-assertions. The compiler rejects assertions
guaranteed to fail, but be aware that type assertions always execute at run
time and so can produce run-time errors!
Copying a data-slice in a slice of interface{} #
Suppose you have a slice of data of myType and you want to put them in a slice
of empty interface, like in:
var dataSlice []myType = FuncReturnSlice()
var interfaceSlice []interface{} = dataSlice
This doesn’t work; the compiler gives you the error: cannot use dataSlice
(type []myType) as type []interface{} in assignment . The reason is that the
memory layout of both variables is not the same (try to reason this yourself or
see this link). The copy must be done explicitly with a for-range statement, like
in:
var dataSlice []myType = FuncReturnSlice()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for ix, d := range dataSlice {
interfaceSlice[ix] = d
}
Node structures of general or different types #
In Chapter 8, we encountered data-structures like lists and trees; using a
recursive struct type called a node. The nodes contained a data field of a
certain type. Now, with the empty interface at our disposal, data can be of that
type, and we can write generic code. Here is some starting code for a binary
tree structure:
type Node struct {
le *Node
data interface{}
ri *Node
}
func NewNode(left, right *Node) *Node {
return &Node{left, nil, right}
}
func (n *Node) SetData(data interface{}) {
n.data = data
}
In the method, the SetData data type is kept an empty interface. It means the
type of data can be decided on runtime. It can be int, string, float32, or even
any user-defined type, as seen in the above examples.
Interface to interface #
An interface value can also be assigned to another interface value, as long as
the underlying value implements the necessary methods. This conversion is
checked at runtime. When it fails, a runtime error occurs: this is one of the
dynamic aspects of Go, comparable to dynamic languages like Ruby and
Python. Suppose:
var ai AbsInterface // declares method Abs()
type SqrInterface interface { Sqr() float }
var si SqrInterface
pp := new(Point) // say *Point implements Abs, Sqr
var empty interface{}
Then, the following statements and type assertions are valid:
empty = pp; // everything satisfies empty
ai = empty.(AbsInterface); // underlying value pp implements Abs()
// (runtime failure otherwise)
si = ai.(SqrInterface); // *Point has Sqr() even though AbsInterface doesn
't
empty = si; // *Point implements empty set
// Note: statically checkable so type assertion not necessary.
Here is an example with a function call:
type myPrintInterface interface {
print()
}
func f3(x myInterface) {
x.(myPrintInterface).print() // type assertion to myPrintInterface
}
The conversion to myPrintInterface is entirely dynamic: it will work as long as
the underlying type of x (the dynamic type) defines a print method.
Type map[string] interface{} #
By using the empty interface as type, we could store any value, but when
using that value, we would first have to do a type assertion.
This type is commonly used to store anything that doesn’t fit into a typedvalue map. It’s practical for building an application rapidly, since you only
have to unbox the elements as you need them, and you can mix and match
types for its values. This is great for semantically related elements, like
configuration structures. It is used in untyped JSON unmarshalling, and the
gob and template packages use them internally to store things: types by name,
and functions by name.
Now that you are familiar with the uses and advantages of empty interfaces,
the next lesson brings you a challenge to solve.
Challenge: Implement Miner Interface
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Analogous to the Sorter interface, we developed previously, make a Miner
interface with the necessary operations and a function Min , which has as a
parameter a variable which is a collection of type Miner and which calculates
and returns the minimum element in that collection.
Try to solve the challenge below. Good Luck!
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package min
type Miner interface {
Len() int
ElemIx(ix int) interface{}
Less(i, j int) bool
}
func Min(data Miner) interface{}
{
}
type
func
func
func
IntArray []int
(p IntArray) Len() int
(p IntArray) ElemIx(ix int) interface{}
(p IntArray) Less(i, j int) bool
{}
{}
{}
type StringArray []string
func (p StringArray) Len() int
{}
func (p StringArray) ElemIx(ix int) interface{}
func (p StringArray) Less(i, j int) bool
{}
{}
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Implement Miner Interface
This lesson discusses the solution to the challenge given in the previous lesson.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package min
type Miner interface {
Len() int
ElemIx(ix int) interface{}
Less(i, j int) bool
}
func Min(data Miner) interface{} {
min := data.ElemIx(0)
for i:=1; i < data.Len(); i++ {
if data.Less(i, i-1) {
min = data.ElemIx(i)
}
}
return min
}
type
func
func
func
IntArray []int
(p IntArray) Len() int
(p IntArray) ElemIx(ix int) interface{}
(p IntArray) Less(i, j int) bool
type
func
func
func
StringArray []string
(p StringArray) Len() int
(p StringArray) ElemIx(ix int) interface{}
(p StringArray) Less(i, j int) bool
{ return len(p) }
{ return p[ix] }
{ return p[i] < p[j] }
{ return len(p) }
{ return p[ix] }
{ return p[i] < p[j] }
In min.go in folder min, we implement the Min function (from line 8 to line
16). At line 9, we take min to be the first element of the collection. Then in the
for loop, we traverse the collection comparing the element at i index with the
previous element at index i-1 . When data.Less(i, i-1) is true, this means
the ith element is smaller, and we set min to the ith element. After the for loop,
min is the smallest item in the collection and we return it.
ElemIx(ix int) interface{} returns the element at the ix index. The function
Min and the method ElemIx return an empty interface{}. This means that they
can return an element of any type: we don’t know beforehand for which
collections we want to use the Miner interface.
At line 18, we define an alias IntArray for []int , and at line 23, we define an
alias StringArray for []string .
From line 19 to line 21, we implement Miner for an array of ints. From line
24 to line 26, we do the same for an array of strings. Now all the work is done!
In main.go we simply test our work. In function ints() , we make an int array
data at line 8. We convert it to a variable a of type IntArray at line 9. Then
we can call the Min function from package min on a , returning the smallest
element m from the int array. From line 14 to line 19, the same steps are
repeated for an array of strings.
That is it about the solution. In the next lesson, we’ll come back to Go’s
reflect package but this time it will have more functionalities.
The reflect Package
This lesson will help you explore the re ect package more relative to interfaces.
WE'LL COVER THE FOLLOWING
• Methods and types in reflect
• Modifying (setting) a value through re ection
• Creating a type through re ection
Methods and types in reflect #
In Chapter 8, we saw how reflect can be used to analyze a struct. Here, we
elaborate further on its powerful possibilities. Reflection in computing is the
ability of a program to examine its structure, mainly through the types; it’s a
form of meta-programming. The reflect can be used to investigate types and
variables at runtime, e.g., their size, and methods, and it can also call these
methods dynamically. It can also be useful to work with types from packages
of which you do not have the source. It’s a powerful tool that should be used
with care and avoided unless strictly necessary.
Basic information of a variable is its type and its value: these are represented
in the reflection package by the types Type, which represents a general Go
type, and Value, which is the reflection interface to a Go value. Two simple
functions, reflect.TypeOf and reflect.ValueOf, retrieve the Type and Value
pieces out of any value. For example, if x is defined as:
var x float64 = 3.4
Then, reflect.TypeOf(x) gives float64 and reflect.ValueOf(x) returns 3.4.
Reflection works by examining an interface value; the variable is first
converted to the empty interface. This becomes apparent if you look at the
signatures of both of these functions:
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
The interface value then contains a type and value pair.
Reflection goes from interface values to reflection objects and back again, as
we will see. Both reflect.Type and reflect.Value have lots of methods that
let us examine and manipulate them. One important example is that Value
has a Type method that returns the Type of a reflect.Value . Another is that
both Type and Value have a Kind method that returns a constant, indicating
what sort of item is stored: Uint, Float64, Slice, and so on. Also, methods on
Value with names like Int and Float let us grab the values (like int64 and
float64) stored inside. The different kinds of Type are defined as constants:
type Kind uint8
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
For our variable x , if v := reflect.ValueOf(x) then v.Kind() is float64, so the
following is true:
v.Kind() == reflect.Float64
The Kind is always the underlying type: if you define:
type MyInt int
var m MyInt = 5
v := reflect.ValueOf(m)
Then, v.Kind() returns reflect.Int .
The Interface() method on a Value recovers the (interface) value, so to print
a Value v do this:
fmt.Println(v.Interface())
Experiment with these possibilities with the following code:
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
v := reflect.ValueOf(x)
fmt.Println("value:", v)
fmt.Println("type:", v.Type())
fmt.Println("kind:", v.Kind())
fmt.Println("value:", v.Float())
fmt.Println(v.Interface())
fmt.Printf("value is %5.2e\n", v.Interface())
y := v.Interface().(float64)
fmt.Println(y)
}
Reflection
In the above code at line 4, we import a package reflect . Look inside main .
At line 8, we declare a float64 type variable x and assign a 3.4 value to it.
Now, in the next line, we are printing the type of x through the TypeOf()
function of the reflect package which is float64. At line 10, we are reading
the value of x in new variable v through ValueOf() function of the reflect
package and printing it in the next line which is 3.4.
Look at line 12. The type of v is printed just by using the Type() method,
which is float64. In the next line, the Kind() method is called by v , and the
returned value is printed, which is also the type of v , i.e., float64. At line 14,
to print the value of v , the Float() method is called by v . At line 15, we
recover the interface value of v , and print v in the %5.2e format in the next
line. At line 17, we convert the type of recovered value to float64 again and
store it in y , which is later printed in the next line.
Modifying (setting) a value through re ection #
Continuing with the previous example, suppose we want to modify the value
of x to be 3.1415. Value has a number of Set methods to do this, but here we
must be careful:
v.SetFloat(3.1415)
It produces an error: will panic: reflect.Value.SetFloat using unaddressable
value . Why is this? The problem is that v is not settable (not that the value is
not addressable). Settability is a property of a reflection Value, and not all
reflection Values have it. It can be tested with the CanSet() method. In our
case, we see that this is false, settability of v: false. When v was created with v
:= reflect.ValueOf(x) , a copy of x was passed to the function, so it is logical
that you can’t change the original x through v .
In order to change x through v , we need to pass the address of x: v =
reflect.ValueOf(&x) . Through Type() , we see that v is now of the type
*float64 and still not settable. To make it settable, we need to let the Elem()
function work on it, which indirects through the pointer v = v.Elem() . Now,
v.CanSet() gives true and v.SetFloat(3.1415) works!
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
// setting a value:
// Error: will panic: reflect.Value.SetFloat using unaddressable value
// v.SetFloat(3.1415)
fmt.Println("settability of v:", v.CanSet())
v = reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("type of v:", v.Type())
fmt.Println("settability of v:", v.CanSet())
v = v.Elem()
fmt.Println("The Elem of v is: ", v)
fmt.Println("settability of v:", v.CanSet())
v.SetFloat(3.1415) // this works!
fmt.Println(v.Interface())
fmt.Println(v)
}
Setting Value through Reflection
In the above code, at line 4, we import a package reflect . At line 8, we
declare a float64 type variable x and assign 3.4 value to it. At line 9, we are
reading the value of x in the new variable v through ValueOf() function of
the reflect package.
Now, before setting a value to x it’s better to check it’s settable or not to avoid
errors. Look at line 13. We are calling canSet() method on v , and printing
the result. It will print false, as v isn’t settable because it’s unaddressable.
Look at line 14, we are reading the address of x (pointer) in new variable v
through ValueOf() function of the reflect package. In the next line, printing
the type of v gives *float64. Again, before setting a value to x , it’s better to
check if it’s settable or not to avoid errors. Look at line 16. We are calling
canSet() method on v , and printing the result. It will print false, as v isn’t
settable.
Now, we try Elem() method on v at line 17. The line 18 prints the elem of v ,
which is its value 3.4. In the next line, we are again checking its settability.
This time it works. We set the value of v to a floating value 3.1415 at line 20.
At line 21, we recover the interface value of v , and print v at the line 23,
which is 3.1415 now.
Reflection Values need the address of something in order to modify what they
represent.
Creating a type through re ection #
In the following example a map is created from a struct:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"reflect"
"strings"
)
func mapToStruct(m map[string]interface{}) interface{} {
var structFields []reflect.StructField
for k, v := range m {
sf := reflect.StructField{
Name: strings.Title(k),
Type: reflect.TypeOf(v),
}
structFields = append(structFields, sf)
}
// Creates the struct type
structType := reflect.StructOf(structFields)
// Creates a new struct
structObject := reflect.New(structType)
return structObject.Interface()
}
func main() {
m := make(map[string]interface{})
m["name"] = "Barack"
m["surname"] = "Obama"
m["age"] = 57
s := mapToStruct(m)
fmt.Printf( %t %[1]v\n , s)
sr := reflect.ValueOf(s)
sr.Elem().FieldByName("Name").SetString("Donald")
sr.Elem().FieldByName("Surname").SetString("Trump")
sr.Elem().FieldByName("Age").SetInt(72)
fmt.Println(s)
}
Click the RUN button and wait for the terminal to start. Type go run main.go
and press ENTER. In case you make any changes to the file, you have to press
RUN again.
In the above code, at line 8, we have the header of the function mapToStruct .
It shows that this function takes an interface of type map[string] and returns
an interface. In the next line, we made a variable structFields , which is a
slice of the type reflect.StructFields . At line 11, we have a for loop that is
reading the fields and making a struct sf of type reflect.structFields in
each iteration and assigning fields with the values read in that iteration.
Before an iteration ends, sf is appended in structFields .
This reflection has a considerable impact on the standard library of Go. We
can customize the standard functions using reflection. To learn more, move to
the next lesson.
Printf and Reflection
This lesson shows how to modify printing using the re ect package and how to overload a function using an
empty interface.
WE'LL COVER THE FOLLOWING
• Introduction
• Explanation
• Empty interface and function overloading:
Introduction #
The capabilities of the reflection package discussed in the previous section are
heavily used in the standard library. For example, the function Printf and so
on use it to unpack its … arguments. Printf is declared as:
func Printf(format string, args ... interface{}) (n int, err error)
The … argument inside Printf has the type interface{} , and Printf uses the
reflection package to unpack it and discover the argument list. As a result,
Printf knows the actual types of their arguments. Because they know if the
argument is unsigned or long, there is no %u or %ld, only %d. This is also how
Print and Println can print the arguments nicely without a format string.
Explanation #
To make this more concrete, we implement a simplified version of such a
generic print-function in the following example, which uses a type-switch to
deduce the type. According to this, it prints the variable out.
package main
import (
"os"
"strconv"
)
type Stringer interface {
String() string
}
type Celsius float64
func (c Celsius) String() string {
return strconv.FormatFloat(float64(c),'f', 1, 64) + " °C"
}
type Day int
var dayName = []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday",
"Sunday"}
func (day Day) String() string {
return dayName[day]
}
func print(args ...interface{}) {
for i, arg := range args {
if i > 0 {os.Stdout.WriteString(" ")}
switch a := arg.(type) { // type switch
case Stringer: os.Stdout.WriteString(a.String())
case int: os.Stdout.WriteString(strconv.Itoa(a))
case string: os.Stdout.WriteString(a)
// more types
default: os.Stdout.WriteString("???")
}
}
}
func main() {
print(Day(1), "was", Celsius(18.36)) // Tuesday was 18.4 °C
}
Print and Reflection
In the above code, at line 7, we define an interface Stringer with one method
String() that returns a string. At line 11, we define a type Celsius on the
basis of type float64. Then, we have a method String that can be called by the
object of type Celsius and return a string, bringing variation to the print
mechanism of Go. It is converting the c type object to string after parsing
float64 and rounding off to 1 digit.
At line 17, we define a type Day on the basis of type int. Then, at line 19, we
make a slice of strings dayName , and add the name of seven days of the week to
it. Then, we have a method String that can be called by object of type Day
and return a string. It is returning the day present at the day index in the
dayName slice.
Now, look at the header of the print function at line 26. It takes multiple
arguments with the interface{} type (not sure how many arguments will it
take on runtime, or of which type). To manage it, we make a for loop for
checking each argument one by one in a single iteration. Now, we have switch
cases ahead. Cases are to be judged on type of arg . The first case will be true if
arg is of Stringer type. In this case, a decision will be made whether to call
String() for Day or Celsius . The second case will be true if arg is of int type.
The third case will be true if arg is of string type. What if no case is found
true? In this case, we have a default case that prints the message of ???.
Now, look at main . At line 40, we are calling print function with arguments
as: print(Day(1), "was", Celsius(18.36)) . Now, control will transfer to line
26. As there are three arguments, for loop will cover three iterations. For the
first iteration, the first case Stringer will be true, and the String() method
for type Day will be called (at line 22), and Tuesday will be printed on the
screen. For the second iteration, the third case string will be true, and the
WriteString() method from the os package will print was will be printed on
the screen. For the third iteration, the first case Stringer will be true, and the
String() method for type Celsius will be called (at line 22), and 18.4 will be
printed on the screen.
Empty interface and function overloading: #
In Chapter 4, we saw that function overloading is not allowed. In Go, this can
be accomplished by using a variable number of parameters with ...T as the
last parameter. If we take T to be the empty interface and we know that a
variable of any type satisfies T , then this allows us to pass any number of
parameters of any type to that function, which is what overloading means.
This is applied in the definition of the function
fmt.Printf(format string, a ...interface{}) (n int, errno error)
This function iterates over the slice a , discovering the type of its arguments
dynamically. For each type, it looks if a method String() is implemented; if
so, this is used for producing the output.
That is it much about the reflect package. Now in the next lesson, let s see
how well Go handles dynamic typing.
Interfaces and Dynamic Typing
This lesson shows how well Go handles dynamic typing as compared to other languages and reveals how Go
saves us from a hustle with the implementation of interfaces.
WE'LL COVER THE FOLLOWING
• Dynamic typing in Go
• Dynamic method invocation
• Extraction of an interface
• Type implements an interface
• Inheritance of interfaces
Dynamic typing in Go #
In classical OO languages (like C++, Java, and C#) data and the methods which
act upon that data are united in the class-concept: a class contains them both,
they cannot be separated. In Go, there are no classes: data (structures, or more
general types) and methods are treated orthogonally. They are much more
loosely coupled.
Interfaces in Go are similar to their Java/C# counterparts; both specify a
minimum set of methods that an implementer of an interface must provide.
However, they are also more fluid and generic: any type that provides code
for the methods of an interface implicitly implements that interface, without
having to say explicitly that it does.
Compared to other languages, Go is the only one that combines interface
values, static type checking (does a type implement the interface?), dynamic
runtime conversion, and no requirement for explicitly declaring that a type
satisfies an interface. This property also allows interfaces to be defined and
used without having to modify existing code.
A function that has one or more parameters of an interface type can be called
u ct o t at as o e o
o e pa a ete s o a
te ace type ca be ca ed
with a variable whose type implements that interface. Types implementing an
interface can be passed to any function that takes that interface as an argument.
This resembles the duck typing in dynamic languages like Python and Ruby;
this can be defined to mean that objects can be handled (e.g., passed to
functions) based on the methods they provide, regardless of their actual types:
what they are is less important than what they can do.
This is illustrated in the following program:
package main
import "fmt"
type IDuck interface {
Quack()
Walk()
}
func DuckDance(duck IDuck) {
for i := 1; i <= 3; i++ {
duck.Quack()
duck.Walk()
}
}
type Bird struct {
// ...
}
func (b *Bird) Quack() {
fmt.Println("I am quacking!")
}
func (b *Bird) Walk() {
fmt.Println("I am walking!")
}
func main() {
b := new(Bird)
DuckDance(b)
}
Duck Dance
In the code above, at line 4, we define an IDuck interface with two methods:
Quack() and Walk() . Look at the header of function DuckDance : func
DuckDance(duck IDuck) at line 9. It takes the parameter of the IDuck interface
type. It calls the functions Quack() and Walk() 3 times in a row using a for
loop.
Type Bird is defined at line 16. We leave out properties here because they are
not needed for the example. However, Bird implements the two methods of
the interface IDuck , Quack() (from line 20 to line 22) and Walk() (from line
24 to line 26).
We then create a variable b of type Bird in main() at line 29. Now, we can
call DuckDance on b (see line 30).
Dynamic method invocation #
However, in Python, Ruby, and the like, duck-typing is performed as late
binding (during runtime). The methods are called on those arguments and
variables and resolved at runtime (they mostly have methods like responds-to
to check whether the object knows this method, but this amounts to more
coding and testing).
On the contrary, in Go, the implementation requirements (most often) get
statically checked by the compiler. It checks if a type implements all the
functions of an interface when there is an assignment of a variable to a
variable of that interface. If the method call is on a more general interface
type, you can check whether the variable implements the interface by doing a
type assertion. As an example, suppose you have different entities
represented as types that have to be written out as XML streams. Then, we
define an interface for XML, writing with the one method you are looking for
(it can even be defined as a private interface):
type xmlWriter interface {
WriteXML(w io.Writer) error
}
Now, we can make a function StreamXML for the streaming of any variable,
testing with a type assertion if the variable passed implements the interface; if
not we call its own function encodeToXML to do the job:
// Exported XML streaming function.
func StreamXML(v interface{}, w io.Writer) error {
if xw, ok := v.(xmlWriter); ok {
// It's an xmlWriter, use method of asserted type.
return xw.WriteXML(w)
}
// No implementation, so we have to use our own function (with perhaps r
eflection):
return encodeToXML(v, w)
}
// Internal XML encoding function.
func encodeToXML(v interface{}, w io.Writer) error {
// ...
}
Go uses the same mechanism in their encoding/gob package. Here, they have
defined the two interfaces GobEncoder and GobDecoder . These allow types to
define their way to encode and decode their data to and from byte streams;
otherwise, the standard way with reflection is used. So, Go provides the
advantages of a dynamic language, but it does not have its disadvantages of
run-time errors! This alleviates for a part they need for unit-testing, which is
very important in dynamic languages but also presents a considerable
amount of effort. Go interfaces promote separation of concerns, improve code
reuse, and make it easier to build on patterns that emerge as the code
develops. With Go-interfaces, the Injection Dependency pattern can also be
implemented.
Extraction of an interface #
A refactoring pattern that is very useful is extracting interfaces, reducing
thereby the number of types and methods needed, without having the need to
manage a whole class-hierarchy as in more traditional class-based OOlanguages. The way interfaces behave in Go allows developers to discover
their programs’ types as they write them. If there are several objects that all
have the same behavior, and a developer wishes to abstract that behavior,
they can create an interface and then use that.
package main
import "fmt"
type Shaper interface {
Area() float32
}
type TopologicalGenus interface {
Rank() int
}
type Square struct {
side float32
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
func (sq *Square) Rank() int {
return 1
}
type Rectangle struct {
length, width float32
}
func (r Rectangle) Area() float32 {
return r.length * r.width
}
func (r Rectangle) Rank() int {
return 2
}
func main() {
r := Rectangle{5, 3} // Area() of Rectangle needs a value
q := &Square{5}
// Area() of Square needs a pointer
shapes := []Shaper{r, q}
fmt.Println("Looping through shapes for area ...")
for n, _ := range shapes {
fmt.Println("Shape details: ", shapes[n])
fmt.Println("Area of this shape is: ", shapes[n].Area())
}
topgen := []TopologicalGenus{r, q}
fmt.Println("Looping through topgen for rank ...")
for n, _ := range topgen {
fmt.Println("Shape details: ", topgen[n])
fmt.Println("Topological Genus of this shape is: ", topgen[n].Rank())
}
}
Extracting Interface
For the program above we found out that, we need a new interface
TopologicalGenus , defined at line 9, which gives the rank of a shape (here,
simply implemented as returning an int).
At line 40, we create an array shaper of type Shaper and populate it with r
and q . Then, from line 42 to line 44, we loop through this array and call the
Area() method on both.
In the exactly same way, we can now use our new TopologicalGenus type: at
line 46, we create an array shaper of type TopologicalGenus , and populate it
with r and q . Then, we loop through this array and call the Rank() method
on both.
Notice that in order to introduce this new interface, we didn’t need to change
the Square and Rectangle types themselves. We only needed to make them
implement the method Rank() of TopologicalGenus . You may have noticed
that for Square the method Rank() is implemented which returns 1 (from line
21 to line23). Similarly for Rectangle , the method Rank() is implemented
which returns 2 (from line 33 to line35).
So you don’t have to work all your interfaces out ahead of time; the whole
design can evolve without invalidating early decisions. If a type must
implement a new interface, the type itself doesn’t have to be changed; you
must only make the new method(s) on the type.
Type implements an interface #
If you wish that the types of an interface explicitly declare that they
implement it, you can add a method with a descriptive name to the interface’s
method set. For example:
type Fooer interface {
Foo()
ImplementsFooer()
}
A type Bar must then implement the ImplementsFooer method to be a Fooer ,
clearly documenting the fact.
type Bar struct{}
func (b Bar) ImplementsFooer() {}
func (b Bar) Foo() {}
Most code doesn’t make use of such constraints since they limit the utility of
the interface idea. Sometimes, however, they can be necessary to resolve
ambiguities among similar interfaces.
Inheritance of interfaces #
When a type includes (embeds) another type (which implements one or more
interfaces) as a pointer, then the type can use all of the interface’s methods.
For example:
type Task struct {
Command string
*log.Logger
}
A factory for this type could be:
func NewTask(command string, logger *log.Logger) *Task {
return &Task{command, logger}
}
When log.Logger implements a Log() method, then a value task of Task can
call it:
task.Log()
A type can also inherit from multiple interfaces providing something like
multiple inheritance:
type ReaderWriter struct {
io.Reader
io.Writer
}
The principles outlined above are applied throughout all Go-packages, thus
maximizing the possibility of using polymorphism and minimizing the
amount of code. This is considered an important best practice in Goprogramming.
Useful interfaces can be detected when the development is already underway.
It is easy to add new interfaces because existing types don’t have to change
(they only have to implement their methods). Current functions can then be
generalized from having a parameter(s) of a constrained type to a parameter
of the interface type: often, only the signature of the function needs to be
changed. Contrast this to class-based OO-languages, where, in such a case, the
design of the whole class-hierarchy has to be adapted.
Interfaces providing ease of modifications make Go a popular language. In the
next lesson, we’ll see how the structs, interfaces, and high-order functions go
along so well.
Structs, Collections and Higher-Order Functions
This lesson is an implementation of the example that covers the concepts of structs, interfaces, and higher-order
functions studied so far.
Often, when you have a struct in your application, you also need a collection
of (pointers to) objects of that struct, like:
type Any interface{}
type Car struct {
Model string
Manufacturer string
BuildYear int
// ...
}
type Cars []*Car
We can then use the fact that higher-order functions can be arguments to
other functions when defining the needed functionality, e.g.:
1. When defining a general Process() function, which itself takes a function
f which operates on every car:
// Process all cars with the given function f:
func (cs Cars) Process(f func(car *Car)) {
for _, c := range cs {
f(c)
}
}
2. Building upon this, make Find-functions to obtain subsets, and call
Process() with a closure (so it knows the local slice cars):
// Find all cars matching given criteria.
func (cs Cars) FindAll(f func(car *Car) bool) Cars {
cars := make([]*Car, 0)
ca s :
a e([] Ca , 0)
cs.Process(func(c *Car) {
if f(c) {
append(cars,c)
}
})
return cars
}
3. And make a Map-functionality producing something out of every car
object:
// Process cars and create new data.
func (cs Cars) Map(f func(car *Car) Any) []Any {
result := make([]Any, 0)
ix := 0
cs.Process(func(c *Car) {
result[ix] = f(c)
ix++
})
return result
}
Now, we can define concrete queries like:
allNewBMWs := allCars.FindAll(func(car *Car) bool {
return (car.Manufacturer == "BMW") && (car.BuildYear > 2010)
})
4. We can also return functions based on arguments. Maybe we would like
to append cars to collections based on the manufacturers, but those may
be varying. So, we define a function to create a special append function
as well as a map of collections:
func MakeSortedAppender(manufacturers []string) (func(car *Car), map[strin
g]Cars) {
// Prepare maps of sorted cars.
sortedCars := make(map[string]Cars)
for _, m := range manufacturers {
sortedCars[m] = make([]*Car, 0)
}
sortedCars["Default"] = make([]*Car, 0)
// Prepare appender function:
appender := func(c *Car) {
if _, ok := sortedCars[c.Manufacturer]; ok {
sortedCars[c.Manufacturer] = append(sortedCars[c.Manufacturer], c)
} else {
sortedCars["Default"] = append(sortedCars["Default"], c)
}
}
return appender, sortedCars
}
We now can use it to sort our cars into individual collections, like in:
manufacturers := []string{"Ford", "Aston Martin", "Land Rover", "BMW", "Ja
guar"}
sortedAppender, sortedCars := MakeSortedAppender(manufacturers)
allUnsortedCars.Process(sortedAppender)
BMWCount := len(sortedCars["BMW"])
Now that you are familiar with a lot of concepts related to interfaces, the next
lesson brings you a challenge to solve.
Challenge: Make a Stack with Variable Internal Types
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
In the last chapter, we developed some Stack struct-types. However, they
were limited to a certain fixed internal type. Now, develop a general stack
type using a slice. That slice should be holding elements of type interface{ } .
Implement the following stack-methods: Len() int , IsEmpty() bool , Push(x
interface{}) and Pop()(x interface{}, error) .
Pop() returns the top most element and removes it from the stack. Also, write
a method Top() , which only returns this element and does not remove it. Note
that the stack will be implemented in the file mystack.go, and its functions
will be called in main.go.
Try to solve the challenge below. Good Luck!
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package mystack
import "errors"
type Stack []interface{}
func (stack Stack) Len() int {
return 0
}
func (stack Stack) Cap() int {
return 0
}
func (stack Stack) IsEmpty() bool {
return true
}
func (stack *Stack) Push(e interface{}) {
return
}
func (stack Stack) Top() (interface{}, error) {
return nil, nil
}
func (stack *Stack) Pop() (interface{}, error) {
return nil, nil
}
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Make a Stack with Variable Internal
Types
This lesson discusses the solution to the challenge given in the previous lesson.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"mystack"
)
var st1 mystack.Stack
func main() {
st1.Push("Brown")
st1.Push(3.14)
st1.Push(100)
st1.Push([]string{"Java", "C++", "Python", "C#", "Ruby"})
for {
item, err := st1.Pop()
if err != nil {
break
}
fmt.Println(item)
}
}
In this program, we develop a generic stack type using a slice holding
elements of type interface{ } . This is done in mystack.go in the folder
mystack. The stack type is defined at line 4 as: type Stack []interface{} .
This type is an array of items of a generic type interface{} ; that means the
items can be of any type like ints, strings, booleans, arrays, etc. The following
functions are implemented in the file mystack.go:
The Len() method amounts to len of the stack array (see line 7).
The IsEmpty() method is defined as true when the len(stack) equals 0
(see line 15).
Look at the header of the Push() method: func (stack *Stack) Push(e
interface{}) at line 18. It takes a *Stack as a receiver variable stack, and
a parameter e of type interface{} . At line 19, it defines the new value of
stack ( *stack ) by appending e to the old value of the stack.
Look at the header of the Top() method: func (stack Stack) Top()
(interface{}, error) at line 22. It takes a Stack . As a receiver variable
stack, has no parameters, but it returns an item of the Stack type or an
error: (interface{}, error) . It first tests whether the stack is empty; in
this case, it returns nil for the item and an error: errors.New("stack is
empty"). If not empty, it returns the stack item at the end (or top) and
nil for the error: stack[len(stack)-1], nil .
Look at the header of the Pop() method: func (stack *Stack) Pop()
(interface{}, error) at line 29. It has the same return type as Top() , but
it has a *Stack as a receiver variable stack because this method has to
change stack. It dereferences this pointer to a local variable stk (see line
30) so that the code is somewhat easier to read. The same test is
performed on an empty stack as in Top() , and the stack item at the end
(or top) is named top . Then, at line 35, the stack is changed by setting its
new value *stack to stk[:len(stk)-1] .
In the main.go file a variable st1 of the mystack.Stack type is declared at
line 7. In order to be able to use the package mystack , we have to first import
it at line 4. Then, in main() we use the Push() method to add a number of
items of different types from line 10 to line 13. Then, we loop over the array
with for. We Pop() each item from the stack st1 at line 15. If the stack
becomes empty, the error is not nil , and we break from the loop at line 17.
In the case there was still an item left, we print it out at line 19.
That’s it about the solution. In the next lesson, OO behavior in Go is
summarized.
The Object Orientation in Go
This lesson summarizes how object orientation is handled by Go and upto which extent object orientation can be
handled by Go.
WE'LL COVER THE FOLLOWING
• Summary on object orientation
Summary on object orientation #
Let us summarize what we have seen about object orientation in Golang so
far: Go has no classes, but instead, it has loosely coupled types and their
methods to implement interfaces. The three important aspects of OOlanguages are encapsulation, inheritance, and polymorphism. How are they
realized in Go?
Encapsulation (data hiding): in contrast to other OO languages where
there are four or more access-levels, Go simplifies this to only two levels:
package scope: object is only known in its package, it starts with a
lowercase letter
exported: object is visible outside of its package because it starts with
an uppercase letter. A type can only have methods defined in its
package.
Inheritance: via composition that is embedding of 1 (or more) type(s)
with the desired behavior (fields and methods); multiple inheritance is
possible through embedding various types.
Polymorphism: via interfaces in which a variable of a type can be
assigned to a variable of any interface it implements. Types and
interfaces are loosely coupled; again, multiple inheritance is possible
through implementing various interfaces. Go’s interfaces aren’t a variant
on Java or C# interfaces, they’re much more. They are independent and
are key to large-scale programming and adaptable, evolutionary design.
So in effect, we can say that Go implements all important object-oriented
features.
Even without classes and inheritance, Go has managed so well to be an objectoriented language. There is a quiz in the next lesson for you to solve.
Quiz: Deduce the Outputs
In this lesson, your concepts will be evaluated through a quiz.
Choose one possible correct answer
1
Deduce the output of the following program:
package main
import "fmt"
type Any interface {}
type Anything struct {}
func main() {
any := getAny()
if any == nil {
fmt.Println("any is nil")
} else {
fmt.Println("any is not nil")
}
}
func getAny() Any {
return getAnything()
}
func getAnything() *Anything {
return nil
}
COMPLETED 0%
1 of 3
We hope that you performed well in the quiz. That’s it about the interfaces. In
the next chapter, we’ll discuss reading and writing the information in detail.
Reading Input from the User
This lesson explains how to receive input from the user through the keyboard.
WE'LL COVER THE FOLLOWING
• Reading input from the keyboard
Apart from the packages fmt and os , we will also need to import and use the
package bufio for buffered input and output.
Reading input from the keyboard #
How can we read user input from the keyboard (console)? Input is
read from the keyboard or standard input, which is os.Stdin . The
simplest way is to use the Scan- and Sscan-family of functions of
the package fmt , as illustrated in this program:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import "fmt"
var (
firstName, lastName, s string
i int
f float32
input = "56.12 / 5212 / Go"
format = "%f / %d / %s"
)
func main() {
fmt.Println("Please enter your full name: ")
fmt.Scanln(&firstName, &lastName)
// fmt.Scanf("%s %s", &firstName, &lastName)
fmt.Printf("Hi %s %s!\n", firstName, lastName) // Hi Chris Naegels
fmt.Sscanf(input, format, &f, &i, &s)
fmt.Println("From the string we read: ", f, i, s)
}
Click the RUN button, and wait for the terminal to start. Type go run main.go
and press ENTER.
The Scan functions require us to first define the variables in which the input
is to be stored. So from line 5 to line 7, we declare a total of 5 variables:
At line 5, we have three string variables: firstname , lastname , and s .
At line 6, we have one integer variable: i .
At line 7, we have one float32 type variable: f .
At line 14, we use fmt.Scanln to read in a line from the keyboard until the
user types ENTER. We asked the user to type in their full name so in the
format firstname SPACE lastname . These strings will be stored in the same
order in the parameters to Scanln : fmt.Scanln(&firstName, &lastName) .
Scanln needs references to the storage variables, that’s why it takes the
parameters &firstName and &lastName . To verify that, we print out the
variables at line 16.
Go has also the Scanf , Fscanf , and Sscanf functions that parse the arguments
according to a format string, analogous to that of Printf . Scanf reads input
from the keyboard, but Sscanf reads from another string.
At line 17, we see that Sscanf takes the following parameters, which were
also defined in the variables section:
input: the string to be read
format: the format to read and parse input
This contains (a list of) the storage variables &f , &i , and &s for storing the
input. So, the input e.g., 56.12 / 5212 / Go is parsed according to format %f /
%d / %s and stored in the variables &f , &i , and &s , respectively.
Scanln scans the text read from standard input, storing successive space-
separated values into successive arguments; it stops scanning at a newline.
Scanf does the same, but it uses its first parameter as the format string to
read the values in the variables. Sscan and friends work the same way but
read from an input string instead of reading from standard input. You can
check on the number of items read in and the error when something goes
wrong. However, it can also be done with a buffered reader from the package
bufio , as is demonstrated in the following program:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"bufio"
"os"
)
var inputReader *bufio.Reader
var input string
var err error
func main() {
inputReader = bufio.NewReader(os.Stdin)
fmt.Println("Please enter some input: ")
input, err = inputReader.ReadString('\n')
if err == nil {
fmt.Printf("The input was: %s\n", input)
}
}
Click the RUN button, and wait for the terminal to start. Type go run main.go
and press ENTER.
The variable inputReader is a pointer to a Reader in bufio . This reader is
created and linked to the standard input in the line: inputReader :=
bufio.NewReader(os.Stdin) . The bufio.NewReader() constructor has the
signature: func NewReader(rd io.Reader) *Reader . It takes as an argument, any
object that satisfies the io.Reader interface (any object that has a suitable
Read() method) and returns a new buffered io.Reader that reads from the
given reader, os.Stdin satisfies this requirement.
The reader has a method ReadString(delim byte) , which reads the input until
delim is found. The delim is included; what is read is put into a buffer.
ReadString returns the string read and nil for the error. When it reads until
the end of a file, the string read is returned and io.EOF ; if delim is not found,
an error err != nil is returned. In our case, the input from the keyboard is
read until the ENTER key (which contains ‘\n’) is typed. Standard output
os.Stdout is on the screen. os.Stderr is where the error-info can be written
to, and it is mostly equal to os.Stdout .
The package bufio allows you to read input from the keyboard or any other
source with its Reader type, which is declared at line 8, and created at line 13.
The Reader has all kinds of methods to read in. The simplest is ReadString ,
which reads into the input as one big string until a newline character is found,
and returns an error err if there was a problem (line 16). If no error is found,
the input is printed at line 18.
In normal Go-code, the var-declarations are omitted and the variables are
declared with := , like:
inputReader := bufio.NewReader(os.Stdin)
input, err := inputReader.ReadString('\n')
We will apply this idiom from now on. The second example reads input from
the keyboard with a few different switch-versions:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"os"
"bufio"
)
func main() {
inputReader := bufio.NewReader(os.Stdin)
fmt.Println("Please enter your name:")
input, err := inputReader.ReadString('\n')
if err != nil {
fmt.Println("There were errors reading, exiting program.")
return
}
fmt.Printf("Your name is %s", input)
// For Unix: test with delimiter "\n", for Windows: test with "\r\n"
switch input {
case "Philip\n": fmt.Println("Welcome Philip!")
case "Chris\n": fmt.Println("Welcome Chris!")
case "Ivo\n": fmt.Println("Welcome Ivo!")
default: fmt.Printf("You are not welcome here! Goodbye!\n")
}
// version 2:
switch input {
case "Philip\n": fallthrough
case "Ivo\n": fallthrough
case "Chris\n": fmt.Printf("Welcome %s\n", input)
default: fmt.Printf("You are not welcome here! Goodbye!\n")
}
// version 3:
switch input {
case "Philip\n", "Ivo\n": fmt.Printf("Welcome %s\n", input)
default: fmt.Printf("You are not welcome here! Goodbye!\n")
}
}
Click the RUN button, and wait for the terminal to start. Type go run main.go
and press ENTER.
This example shows input obtained with ReadString at line 11 being tested in
a few switch constructs. Note how it is important to distinguish between the
line-endings on Unix and Windows. For example, on Windows, you would
code this as:
case "Philip\r\n": fmt.Println("Welcome Philip!")
Now that you know how to take input from the keyboard, the next lesson
brings you a challenge to solve.
Challenge: Analyzing Input from Keyboard
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Write a program that reads the text from the keyboard.
When the user enters S (in a new line) in order to signal the
end of the input, the program shows 3 numbers:
The number of characters including spaces (but
excluding ‘\r’ and ‘\n’)
The number of words
The number of lines
Note: For Windows, the comparison for S can be checked as S\r\n . On
Linux and terminal, use S\n .
Try to attempt the challenge below. Feel free to view the solution, after giving
some shots. Good Luck!
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
var nrchars, nrwords, nrlines int
func main() {
}
func Counters(input string) {
}
Click the RUN button, and wait for the terminal to start. Type go run main.go
and press ENTER.
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Analyzing Input from Keyboard
This lesson discusses the solution to the challenge given in the previous lesson.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"bufio"
"os"
"strings"
)
var nrchars, nrwords, nrlines int
func main() {
nrchars, nrwords, nrlines = 0, 0, 0
inputReader := bufio.NewReader(os.Stdin)
fmt.Println("Please enter some input, type S in the new line to stop: ")
for {
input, err := inputReader.ReadString('\n')
if err != nil {
fmt.Printf("An error occurred: %s\n", err)
return
}
if input == "S\n" { // Windows it is "S\r\n", on Linux it is "S\n"
fmt.Println("Here are the counts:")
fmt.Printf("Number of characters: %d\n", nrchars)
fmt.Printf("Number of words: %d\n", nrwords)
fmt.Printf("Number of lines: %d\n", nrlines)
os.Exit(0)
}
Counters(input)
}
}
func Counters(input string) {
nrchars += len(input) - 2 // -2 for \r\n
// count number of spaces, nr of words is +1
nrwords += strings.Count(input, " ") + 1
nrlines++
}
Click the RUN button, and wait for the terminal to start. Type go run main.go
and press ENTER.
The variables nrchars , nrwords and nrlines contain the counts we must
make: they are declared at line 8, and initialized to 0 at line 11. Then, we
construct at line 12 a new buffered reader to read from the keyboard. Line 14
starts an infinite loop:
The keyboard input is read in a string input until a new line is found at
line 15. At line 16, we check the error variable err : if it contains an
error, we jump out of the loop.
Then, we check if the input contains the letters S at line 20. If so, we
print out the counts found (from line 21 to line 24) and exit the program
at line 25.
At line 27, the function Counters is called with input as a parameter.
The function of Counters is defined as follows:
nrchars is incremented with the length of the input string, -2 for \r\n on
Windows, or -1 for \n on Linux (see line 32).
nrwords is incremented with the number of words; this is the number of
spaces in input + 1 (see line 34).
ReadString just reads one line, so nrlines is simply incremented with 1
(see line 35).
That’s it for the solution. In the next lesson, you’ll see how Go provides
support for reading from a file.
Reading from a File
This lesson provides coding examples and their explanations for reading from a le.
WE'LL COVER THE FOLLOWING
• Reading data from a le
• Some alternatives
• Reading contents of an entire le in a string
• Buffered read
• Reading columns of data from a le
• Reading from a zipped le
Reading data from a le #
Files in Go are represented by pointers to objects of type os.File , also called
file handles. The standard input os.Stdin and output os.Stdout we used in
the previous lesson are both of type *os.File . This is used in the following
program:
main.go
input.dat
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
inputFile, inputError := os.Open("input.dat")
if inputError != nil {
fmt.Printf("An error occurred on opening the inputfile\n" +
"Does the file exist?\n" +
"Have you got access to it?\n")
return // exit the function on error
}
defer inputFile.Close()
inputReader := bufio.NewReader(inputFile)
for {
inputString, readerError := inputReader.ReadString('\n')
if readerError == io.EOF {
return
}
fmt.Printf("The input was: %s", inputString)
}
}
Input from file
In the code above, at line 10, a call to os.Open creates a variable inputFile of
type *os.File : this is a struct that represents an open file descriptor (a
filehandle). The Open function accepts a parameter filename of type string
(here as input.dat) and opens the file in read-only mode.
This can, of course, result in an error when the file does not exist or the
program does not have sufficient rights to open the file: inputFile, inputError
= os.Open("input.dat") . From line 11 to line 17, we are handling the errors.
The defer keyword is very useful for ensuring that the opened file will also be
closed at the end of the function with defer inputFile.Close() at line 18.
Here is a code snippet, where this is used in a function data() :
func data(name string) string {
f := os.Open(name, os.O_RDONLY, 0)
defer f.Close() // idiomatic Go code!
contents := io.ReadAll(f)
return contents
}
Then, at line 19, we apply bufio.NewReader to get a reader variable. Using
bufio's reader (and the same goes for writer), as we have done here, means
that we can work with convenient high-level string objects, completely
insulated from the raw bytes which represent the text on disk.
Then, from line 20 to line 26, we read each line of the file (delimited by ‘\n’ or
\r\n )) in an infinite for loop with the method ReadString( \n ) or
ReadBytes('\n') . ReadString returns an io.EOF error when the end of the
input file is reached, which we test from line 22 to line 24.
Remark: In a previous example, we saw text-files in Unix end on \n but
in Windows; this is \r\n. By using the method ReadString or ReadBytes
with \n as a delimiter, you don’t have to worry about this. The use of the
ReadLine() method could also be a good alternative.
When we read the file past the end, the readerError is not nil (actually io.EOF
is true), and the infinite for-loop is left through the return statement.
Some alternatives #
Reading contents of an entire le in a string #
If this is sufficient for your needs, you can use the ioutil.ReadFile() method
from the package io/ioutil , which returns a []byte containing the bytes read
and nil or a possible error.
main.go
products.txt
package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
inputFile := "products.txt"
buf, err := ioutil.ReadFile(inputFile)
if err != nil {
fmt.Fprintf(os.Stderr, "File Error: %s\n", err)
}
fmt.Printf("%s\n", string(buf))
}
Reading from and Writing in File
The name of the file is stored in the variable inputfile at line 9. At line 10,
we use the ReadFile method from the package ioutil to read in the file as a
whole into a variable buf . If there is an error, we print this out (see line 12).
Finally, at line 14, we convert buf to a string with string(buf) and print it
out.
Remark: Don’t use ReadFile for big files because one large string uses a
lot of memory!
Buffered read #
Instead of using ReadString() , in the more general case of a file not divided in
lines or a binary file, we could have used the Read() method on the
bufio.Reader , with a slice of bytes to read into as an input parameter:
buf := make([]byte, 1024)
...
n, err := inputReader.Read(buf)
if (n == 0) { break}
n is the number of bytes read.
Reading columns of data from a le #
If the data columns are separated by a space, you can use the FScan -function
series from the fmt package. This is applied in the following program:
main.go
products2.txt
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("products2.txt")
if err != nil {
panic(err)
}
defer file.Close()
var col1, col2, col3 []string
for {
var v1, v2, v3 string
, err := fmt.Fscanln(file, &v1, &v2, &v3) // scans until newline
_, e
:
t. sca
if err != nil {
break
}
(
e, &
, &
, & 3) // sca s u t
e
e
col1 = append(col1, v1)
col2 = append(col2, v2)
col3 = append(col3, v3)
}
fmt.Println(col1)
fmt.Println(col2)
fmt.Println(col3)
}
Reading Columns of Data
This program opens a file products2.txt at line 8; it panics if there is an error
(see the implementation from line 8 to line 11). We make sure the file is
closed at line 12 with defer . When you open the file products2.txt, you see
that it contains columnar data, separated by a space:
ABC 40 150
FUNC 56 280
GO 45 356
We define the variables col1, col2, and col3 to hold the data from each
column at line 13, each as an []string . The contents of the file are read in an
infinite loop from line 14 to line 19.
The Fscanln function of package fmt reads in a line from the file at line 16.
Because we know that there are three columns, Fscanln splits the line into the
columns and reads their contents in the strings v1 , v2 and v3 . On the lefthand side, you see _, err . The _ means that we neglect the first return value,
which is the number of bytes read in. From line 17 to line 19, we detect that
we have come at the end of the file: then, err is not nil and we break from
the loop.
From line 20 to line 23, we append the strings v1 , v2 and v3 to the
respective column slices col1 , col2 , and col3 . These are then printed out at
the end (from line 24 to line 26).
Remark: The sub-package filepath of the package path provides
functions for manipulating filenames and paths that work across OS
platforms. For example, the function Base() returns the last element of a
path without trailing separator:
import "path/filepath"
filename := filepath.Base(path)
Reading from a zipped le #
The package compress , from the standard library, provides facilities for
reading compressed files in the following formats:
bzip2
flate
gzip
lzw
zlib
The following program illustrates the reading of a gzip file:
main.go
Example.json.gz
package main
import (
"fmt"
"bufio"
"os"
"compress/gzip"
)
func main() {
fName := "Example.json.gz"
var r *bufio.Reader
fi, err := os.Open(fName)
if err != nil {
fmt.Fprintf(os.Stderr, "%v, Can't open %s: error: %s\n", os.Args[0], fName, err)
os.Exit(1)
}
fz, err := gzip.NewReader(fi)
if err != nil {
r = bufio.NewReader(fi)
} else {
r = bufio.NewReader(fz)
}
for {
line, err := r.ReadString('\n')
if err != nil {
fmt.Println("Done reading file")
os.Exit(0)
}
fmt.Println(line)
}
}
Reading from a Compressed File
To use that functionality, we need to import the package compress/gzip (see
line 6). The filename of the zipped file is contained in the variable fName (see
line 10). At line 11, we open the file returning a filehandle fi . The usual
error-handling is done from line 13 to line 16.
At line 17, we construct with the filehandle fi , a gzip Reader named fz .
From line 18 to line 22, we test whether there is an error or not. In both cases,
we construct a buffered reader r , but we use fi as a parameter in case of an
error, and fz means everything is ok.
Then, in an infinite loop (see the implementation from line 23 to line 30), we
read line by line from r with the ReadString method into the line variable
and print it out. When the end of the file is reached, err is not nil anymore,
and the program is stopped (see the implementation from line 25 to line 28).
Now that you are familiar with the read operations, in the next lesson, you’ll
learn some write operations in Golang.
Writing to a File
This lesson provides code examples and their explanations for writing to a le.
WE'LL COVER THE FOLLOWING
• Writing data to a le
Writing data to a le #
Writing data to a file is demonstrated in the following program:
package main
import (
"os"
"bufio"
"fmt"
)
func main () {
outputFile, outputError := os.OpenFile("output/output.dat", os.O_WRONLY|os.O_CREATE, 0666)
if outputError != nil {
fmt.Printf("An error occurred with file creation\n")
return
}
defer outputFile.Close()
outputWriter:= bufio.NewWriter(outputFile)
outputString := "hello world!\n"
for i:=0; i<10; i++ {
outputWriter.WriteString(outputString)
}
outputWriter.Flush()
}
Writing using bufio Package
Apart from a filehandle, we now need a writer from bufio . We open a file
output.dat for write-only at line 9. The file is created if it does not exist.
The os.OpenFile is a function that allows more control over the opening mode
of a file. We see that the OpenFile function takes a filename, one or more flags
(logically OR-d together using the | bitwise OR operator if more than one) and
the file permissions to use. The following flags are commonly used:
os.O_RDONLY : the read flag for read-only access
os.O_WRONLY : the write flag for write-only access
os.O_RDWR : the flag that provides both read and write access
os.O_CREATE : the create flag to create the file if it doesn’t exist
os.O_TRUNC : the truncate flag to truncate to size 0 if the file already exists
When reading, the file permissions are ignored so we can use a value of 0.
When writing, we use the standard Unix file permissions of 0666 (even on
Windows).
The error-handling is done from line 10 to line 13, and at line 14, we assure
the file will be closed with the defer keyword.
Then at line 15, we make the writer-object (the buffer) called outputWriter .
We make a simple hello world!\n string, which will be written ten times to the
buffer in the for-loop (see line 18). The buffer is then written completely to
the file at line 20. In simple write tasks, this can be done more efficiently with:
fmt.Fprintf(outputFile, "Some test data.\n")
Using the F version of the fmt print functions that can write to any io.Writer ,
including a file.
The program below illustrates an alternative to fmt.Fprintf :
package main
import "os"
func main() {
os.Stdout.WriteString("hello, world\n")
f, _ := os.OpenFile("output/test.txt", os.O_CREATE|os.O_WRONLY, 0)
defer f.Close()
f.WriteString("hello, world in a file\n")
}
Writing using io Package
In the code above, at line 5, instead of printing with fmt , we use the
Stdout.WriteString method from os to display a string. At line 6, we open a
file test.txt for writing. At line 9, we are writing a string to it. Because of the
defer at line 7, the file will be closed at line 10.
With os.Stdout.WriteString("hello, world\n") , we can also write to the
screen. In f, _ := os.OpenFile("test", os.O_CREATE|os.O_WRONLY, 0) we
create or open for write-only a file test.txt. A possible error is disregarded
with _ . We don’t make use of a buffer, we write immediately to the file with:
f.WriteString( ) .
Now that you are familiar with the write operations, the next lesson brings
you a challenge to solve.
Challenge: Read CSV File
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
We are given the file products.txt with the content:
"The ABC of Go";25.5;1500
"Functional Programming with Go";56;280
"Go for It";45.9;356
"The Go Way";55;500
The first field of each line is a title, the second is a price, and the third is a
quantity. Read in the data but make a struct type to gather all the data of one
line and use a slice of structs to print the data. For more functionality in
parsing csv-files, see the package encoding/csv.
Try to attempt the challenge below. Feel free to view the solution, after giving
some shots. Good Luck!
main.go
products.txt
package main
type Book struct {
}
func main() {
}
Reading CSV File
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Read CSV File
This lesson discusses the solution to the challenge given in the previous lesson.
main.go
products.txt
package main
import (
"bufio"
"fmt"
"log"
"io"
"os"
"strconv"
"strings"
)
type Book struct {
title
string
price
float64
quantity
int
}
func main() {
bks := make([]Book, 1)
file, err := os.Open("products.txt")
if err != nil {
log.Fatalf("Error %s opening file products.txt: ", err)
}
defer file.Close()
reader := bufio.NewReader(file)
for {
// read one line from the file:
line, err := reader.ReadString('\n')
if err == io.EOF {
break
}
// remove \r and \n so 2 in Windows, in Linux only \n, so 1:
line = string(line[:len(line)-2])
//fmt.Printf("The input was: -%s-", line)
strSl := strings.Split(line, ";")
book := new(Book)
book.title = strSl[0]
book.price, err = strconv.ParseFloat(strSl[1], 32)
if err!=nil {
fmt.Printf("Error in file: %v", err)
}
//fmt.Printf("The quan was:-%s-", strSl[2])
book.quantity, err = strconv.Atoi(strSl[2])
if err!=nil {
fmt.Printf("Error in file: %v", err)
}
if bks[0].title == "" {
bks[0] = *book
} else {
bks = append(bks, *book)
}
}
fmt.Println("We have read the following books from the file: ")
for _, bk := range bks {
fmt.Println(bk)
}
}
Reading CSV File
Look at the file products.txt. The first field of each line in the file is a title, the
second is a price, and the third is a quantity. Whereas, the columns are
separated by a ; .
Now, look at the file main.go. First, we define a struct of type Book at line 13.
It contains the fields according to the specifications of the data in our file:
title , price , and quality .
At line 20, we make a slice bks of Book , with a length of 1. At line 21, we open
the products.txt file. The usual error-handling is done from line 22 to line 24.
Line 25 makes sure the file is closed at the end of the function. At line 27, a
buffered reader called reader is created. This is used in the infinite for loop (
see implementation from line 28 to line 33). Here, we read in a line (line 30)
and jump out of the for loop when the end of the file is reached.
At line 38, the line that is read in, is split on the character ; . The result is an
array strSl . Let’s see how the fields are assigned some values:
The first element of strSl is the book’s title , which we assign at line 40.
The price (which is a floating-point number) is in the field strSl[1] , so
we have to convert the string input to a float (see line 41). The
ParseFloat method can return an error when the field is not a number
format, and this is handled from line 41 to line 44.
In the same way, the book’s quantity (which is an integer number) is in
the field strSl[2] , so we have to convert the string input to an integer
(see line 46). The Atoi method can return an error when the field is not
an integer number, and this is handled from line 47 to line 49.
At line 50, we test if there is a title . If that’s ok (else clause), we append the
book struct to the bks slice. If not ok (if clause), we put that data at the start of
the slice, perhaps for review. Then, the slice bks is printed out via a for-range
loop at line 58.
That’s it about the solution. In the next lesson, you’ll be solving another
challenge.
Challenge: Writing to a WIKI Page
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
The data structure of our program is a struct containing the following fields:
type Page struct {
Title string
Body []byte
}
Make a save method on this struct to write a text-file with Title as its name
and Body as its content. Make a load function which, given the title string,
reads the corresponding text-file. Use *Page as an argument because the
structs could be quite large and we don’t want to make copies of them in
memory. Use the ioutil functions to read and write from/to a file.
Note: Here’s a link to the detailed documentation of ioutil library.
Try to attempt the challenge below. Good Luck!
package main
type Page struct {
Title string
Body []byte
}
func (p *Page) save() error {
return nil
}
func load(title string) (*Page, error) {
return nil, nil
}
func main() {
}
Writing to a File
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Writing to a WIKI Page
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"fmt"
"io/ioutil"
)
type Page struct {
Title string
Body []byte
}
func (p *Page) save() error {
filename := p.Title + ".txt"
return ioutil.WriteFile(filename, p.Body, 0600)
}
func load(title string) (*Page, error) {
filename := title + ".txt"
body, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return &Page{Title: title, Body: body}, nil
}
func main() {
p1 := &Page{Title: "TestPage", Body: []byte("This is a sample Page.")}
p1.save()
p2, _ := load("TestPage")
fmt.Println(string(p2.Body))
}
Writing to a File
In the code above, we define a struct of type Page struct at line 7 with two
fields: Title and Body .
Now, look at the header of the load method at line 17. It takes title , the
filename as a parameter (apart from the extension .txt), and returns a pointer
to a Page struct and an error. At line 19, the file is read into a variable body
with the ioutil.ReadFile method. The usual error-handling is done from line
20 to line 22, returning a non-nil err variable in case of an error. At line 23, a
Page struct is made with title and body , and a reference to it is returned
from the function together with nil for the err variable.
Look at the header of the save method at line 12. It works on a pointer to a
Page struct as a receiver. It constructs the filename at line 13 with the Title
field and writes the Body field out to the file at line 14.
In the main() function, we first initialize a Page struct at line 27 and save it to
a file at line 28. Then, to test our code, we load it at line 29 and print its
contents out at line 30.
That’s it for the solution. In the next lesson, you’ll see how Go provides
support for copying mechanisms.
Copying Files
This lesson provides an implementation of copying les in Go.
How do you copy a file to another file? The simplest way is using Copy from
the io package:
main.go
source.txt
package main
import (
"fmt"
"io"
"os"
)
func main() {
CopyFile("output/target.txt", "source.txt")
fmt.Println("Copy done!")
}
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()
dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return
}
defer dst.Close()
return io.Copy(dst, src)
}
Copy the Files
In the code above, we isolate the copying of the file in a function CopyFile ,
which takes as parameters first the destination file, and secondly the source
file. (see line 9 where the function is called).
Look at the header of the CopyFile function at line 13. It simply opens the
source file at line 14, handles an eventual error from line 15 to line 17 and
assures the file is closed at the end of the function at line 18. In the same way,
the destination file is opened at line 19 for writing. An eventual error is
handled from line 20 to line 22, and it assures the file is closed at the end of
the function at line 18. Then, at line 24, io.Copy(dst, src) is called and its
values returned: the number of bytes written and a possible error. Note that,
because of defer , the src and dst files are only closed after the copy is done
and these values returned.
Note that by deferring src.Close() before the destination file is open ensures
that, even if the opening of dst fails, src.Close() is still executed, closing the
src file, which otherwise would remain open, using resources.
Now, you see how simple it is to copy content from one file to another in Go.
In the next lesson, you’ll learn how to read arguments from the command
line.
Reading Arguments from the Command-Line
This lesson explains two packages of Go that let us read arguments from the command line.
WE'LL COVER THE FOLLOWING
• With the os package
• With the flag package
• go run main.go -h command
• go run main.go command
• go run main.go -lang="V" -num=42 command
With the os package #
The package os also has a variable os.Args of type slice of strings that can be
used for elementary command-line argument processing, which is reading
arguments that are given on the command-line when the program is started.
Look at the following greetings-program:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"os"
"strings"
)
func main() {
who := "Alice "
if len(os.Args) > 1 {
who += strings.Join(os.Args[1:], " ")
}
fmt.Println("Good Morning", who)
}
When we run this program, the output is: Good Morning Alice. The same
output we get when we run it on the command-line as:
go run main.go
But, when we give arguments on the command-line like:
go run main.go John Bill Marc Luke
we get Good Morning Alice John Bill Marc Luke as an output.
When there is at least one command-line argument, the slice os.Args[] takes
in the arguments (separated by a space), starting from index 1 since
os.Args[0] contains the name of the program, os_args in this case.
At line 10, we test if there is more than one command-line argument with
len(os.args) > 1 . In that case, all the remaining arguments given by the slice
os.Args[1:] are joined together at line 11 and added to Alice. The
strings.Join function glues them all together with space in between. The
string who , which contains the names of all the people to be greeted, is then
printed from line 13.
With the flag package #
The package flag has extended functionality for parsing of command-line
options, but it is also often used to replace ordinary constants, e.g. when we
want to give a different value for our constant on the command-line. A Flag is
defined in the package flag as a struct with the following fields:
type Flag struct {
Name string // name as it appears on command line
Usage string // help message
Value Value // value as set
DefValue string // default value (as text); for usage message
}
The usage of the flags package is demonstrated in the following program:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"flag"
"fmt"
)
func main() {
strPtr := flag.String("lang", "Go", "a string")
numPtr := flag.Int("num", 108, "an int")
boolPtr := flag.Bool("truth", false, "a bool")
var str string
flag.StringVar(&str, "str", "Crystal", "a string variable")
flag.Parse()
fmt.Println("lang:", *strPtr)
fmt.Println("num:", *numPtr)
fmt.Println("truth:", *boolPtr)
fmt.Println("str:", str)
fmt.Println("tail:", flag.Args())
}
Click the RUN button, and wait for the terminal to start. There are three ways
to run the program:
Type go run main.go and press ENTER.
Type go run main.go -h and press ENTER.
Type go run main.go -lang="V" -num=42 and press ENTER.
go run main.go -h command #
The flags are defined for strings, integers or booleans with the respective
functions flag.String , flag.Int and flag.Bool . They all return a pointer as
you can see from line 8 to line 10. Their first argument is the flag name, then
comes the default value, and then a help info string. This is shown when you
call this program with –h , like:
go run main.go –h
The following output is shown on the screen:
Usage of /tmp/go-build001915860/command-line-arguments/_obj/exe/main:
-lang string
a string (default "Go")
-num int
an int (default 108)
-str string
a string variable (default "Crystal")
-truth
a bool
go run main.go command #
You can also use flag.StringVar to define a string flag, with the first
argument as a pointer to the string that contains the value. If you call the
program without arguments, you get the default values:
lang: Go
num: 108
truth: false
str: Crystal
tail: []
go run main.go -lang="V" -num=42 command #
By giving flags on the command-line as:
go run main.go -lang="V" -num=42
the values can be changed:
lang: V
num: 42
truth: false
str: Crystal
tail: []
All the flags that this program can handle are defined from line 8 to line 12. A
call to flag.Parse() at line 13 is necessary to scan the argument list (or list of
constants) and set up the flags. After parsing, the flags or constants can be
used.
From line 14 to line 17, we dereference the pointers ( *strPtr and so on) to
get their value and print it out.
You can iterate over the flags like this:
var s string
for i := 0; i < flag.NArg(); i++ {
s += flag.Arg(i)
}
flag.Arg(i) is the i-th argument. All flag.Arg(i) are available after Parse() .
The flag.Arg(0) is the first real flag, not the name of the program in contrast
to os.Args(0) . The flag.NArg() is the number of arguments, and flag.Args()
is an array containing the remaining arguments.
flag.VisitAll(fn func(*Flag)) is another useful function: it visits the set flags
in lexicographical order, calling fn for each.
With flag.Bool you can make boolean flags, which can be tested against in
your code. For example, define a flag processedFlag which:
var processedFlag = flag.Bool("proc", false, "nothing processed yet")
Test it later in the code by dereferencing it:
if *processedFlag {
r = process()
}
// found flag -proc
Reading arguments from the command line makes programming so much
more flexible. In the next lesson, you’ll learn how to use a buffer.
Reading Files with a Buffer
This lesson explains how to use a buffer for reading purposes in Go.
We combine the techniques of buffered reading of a file and command-line
flag parsing in the following example. If there are no arguments, what is
typed in is shown again in the output. Arguments are treated as filenames,
and when the files exist, their content is shown. Test it out with:
cat test.txt
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
Welcome to Educative
Click the RUN button, and wait for the terminal to start. Type cat test.txt
and press ENTER.
The main() function starts at line 21. We parse command-line flags at line 22.
At line 23, we test the case that there are no flags. Then, we make a new
buffered reader that will take input from the keyboard, and call the cat
function on that reader at line 24. Starting a for-loop at line 26 over all the
flag arguments, we open the file with the name given in the i-th argument at
line 27. Error-handling is done from line 29 to line 33. In case of an error, we
print out an error message (from line 30 to line 31) and continue with the
next flag.
Then, at line 34, we construct a buffered reader on that file and call the cat
function on this reader. Look at the header of the cat function at line 10. It
takes a pointer to a buffered reader as an argument. In an infinite for-loop
(see implementation from line 11 to line 17), it reads all bytes from the buffer
until a newline \n is encountered. When the end of the file is reached, we
return from the function to main() (from line 13 to line 15). Then, at line 16,
we print out what was read.
Apart from buffer, Go also provides functionality of reading and writing files
with slices. Move on to the next lesson to see how this works.
Reading and Writing Files with Slices
This lesson explains in detail how to use a slice for reading and writing to les.
Slices provide the standard-Go way to handle I/O buffers; they are used in the
following second version of the function cat , which reads a file in an infinite
for-loop (until end-of-file EOF) in a sliced buffer, and writes it to standard
output.
func cat(f *os.File) {
const NBUF = 512
var buf [NBUF]byte
for {
switch nr, err := f.Read(buf[:]); true {
case nr < 0:
fmt.Fprintf(os.Stderr, "cat: error reading: %s\n", err.Error())
os.Exit(1)
case nr == 0: // EOF
return
case nr > 0:
if nw, ew := os.Stdout.Write(buf[0:nr]); nw != nr {
fmt.Fprintf(os.Stderr, "cat: error writing: %s\n")
}
}
}
}
Look at the following example:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
Welcome to Educative
Click the RUN button, and wait for the terminal to start. Type go run main.go
test.txt and press ENTER.
This program is mostly the same as the one covered in the previous lesson.
The main() follows the same structure, but instead of calling cat on a
buffered reader to a file, we call it on the file itself; we refer to the explanation
there.
If the ith command-line argument is a file f , we call cat on that file at line 37.
The cat is defined from line 8 to line 24. It takes a pointer to a file as an
argument. In an infinite for-loop (from line 11 to line 23), it reads NBUF bytes
from the file into a new byte slice buf at line 12.
Here, nr, err := f.Read(buf[:]); is an initialization statement, and true
simply ensures that the switch will execute. Whereas, nr is the number of
bytes read. When nr is negative, a problem has occurred, and we stop the
program with os.Exit(1) at line 15. When nr is 0, it means we are at the end
of the file, and we return to main() (see line 17). In the case that we read
some bytes (see line 18), we show the buffer on display. The number of bytes
written to display is nw . Line 19 tests that if the number of bytes read is not
the same as the number of bytes written, there is a problem. Then we display
an error message at line 20.
When the end of the file is reached, we return from the function to main()
(from line 13 to line 15). Then at line 16, we print out what was read.
Now that you’re familiar with the file reading and file writing, let’s learn how
to scan inputs in the next lesson.
Scanning Inputs
This lesson introduces a new method of reading input from the user, i.e., scanning the data from the user.
We already saw how to read from input. Another way to get input is by using
the Scanner type from the bufio package:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
fmt.Println("Please enters some input: ")
if scanner.Scan() {
fmt.Println("The input was", scanner.Text())
}
}
Click the RUN button, and wait for the terminal to start. Type go run main.go
and press ENTER.
At line 9, we construct a Scanner instance from package bufio with input
coming from the keyboard ( os.Stdin ). At line 11, scanner.Scan() takes any
input (until ENTER key is pressed) and stores it into its Text() property, which
is printed out at line 12. Scan() returns true when it gets input, and returns
false when the scan stops, either by reaching the end of the input or an error.
So, at the end of input, the if-statement is finished.
After the prompt Please enter some input: , the program waits on Scan()
for input. If, for example, a text “Hi there” is entered, it is read in with the
Text() function, and the program outputs: The input was: Hi there. The
Scan() returns false when the scan stops, either by reaching the end of the
input or an error.
The bufio package also has a Split function, which can work at the rune,
word or line-level:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
fmt.Println("Please enter some input: ")
// scanner.Split(bufio.ScanRunes) // <-- scan runes (newline included)
scanner.Split(bufio.ScanWords) // <-- scan words (sequence of characters delimited by space
// scanner.Split(bufio.ScanLines) // <-- the default: scan lines
for scanner.Scan(){ // The for loop stops when a EOF is given
fmt.Printf("Token ->%s<-\n", scanner.Text())
}
}
Click the RUN button, and wait for the terminal to start. Type go run main.go
and press ENTER.
At line 9, we construct a Scanner instance from package bufio with input
coming from the keyboard ( os.Stdin ). Code shows the different ways the
Split method can be used:
Line 11 shows splitting on UTF8 characters (runes).
Line 12 shows splitting on spaces, getting the individual words.
Line 13 shows splitting on lines (the default).
Here, we used the second option at line 12. At line 14, the Scan() method is
called. As we know, Scan() returns true when it gets input, and returns false
when the scan stops, either by reaching the end of the input or an error. Put
inside an infinite for-loop (from line 14 to line 16), this executes by printing
out the input found until the end of input or an error condition is reached.
We can customize the splitting. For example, adapt it, so that split words are
seen as a sequence of alphanumeric characters only; so " Hello, how are
you?" would be split in six tokens.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"bufio"
"fmt"
"os"
"unicode"
"unicode/utf8"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
fmt.Println("Please enter some input: ")
// scanner.Split(bufio.ScanWords)
scanner.Split(AlphanumericWord)
for scanner.Scan() {
fmt.Printf("Token ->%s<-\n", scanner.Text())
}
}
func AlphanumericWord(data []byte, atEOF bool) (int, []byte, error) {
// Skip leading spaces
start := 0
for width := 0; start < len(data); start += width {
var r rune
r, width = utf8.DecodeRune(data[start:]) // Read a rune
if !unicode.IsSpace(r) {
break // Break if a non-space character has been found (beginning of a word)
}
}
// Start reading a word
for width, i := 0, start; i < len(data); i += width {
var r rune
r, width = utf8.DecodeRune(data[i:]) // Read a rune
// Stop when you read something that is not alphanumeric
if !(unicode.IsLetter(r) || unicode.IsDigit(r)) {
if i == 0 {
// if the first character is not a space (already skipped)
// but it is not an alphanumeric character, print that character alone
return i + width, data[start : i+width], nil
} else {
// Otherwise you just completed a word
return i, data[start:i], nil
}
}
}
if atEOF && len(data) > 0 {
return len(data), data[0:], nil
}
return 0, nil, nil
}
Click the RUN button, and wait for the terminal to start. Type go run main.go
and press ENTER.
This program has the exact same structure as the previous code, except line
15, where Split() is called. It is now called with a function AlphanumericWord
as an argument, which is defined from line 21 to line 51. This is the way to
customize our Split() : the input will be stored in the variable data which is
of type []byte .
Now, let’s see the implementation of AlphaNumericWord() . The major part is
about skipping initial spaces getting to the first word. It goes through the data
with a for-loop starting at line 24. Then, at line 27, when the first non-space
character is reached ( !unicode.IsSpace(r) ), the loop stops with a break . Then,
we start a for-loop at line 32 to read that word: from the i position we were
on ( i = start , see line 32), we read a character (rune) r . If r is not a letter
or not a digit (line 36) and we are not at the first character (else at line 41), we
return a complete word data[start:i] that we found at line 43. This
continues until i >= len(data) . Then, we are at the end of the input at
EOF (see line 47), and the remaining input text is returned (see line 48).
Now that you are familiar with the different concepts of reading and writing,
how about designing interfaces for such purposes? In the next lesson, you will
learn building interfaces for reading and writing purposes.
A Practical Example of Interfaces
This lesson provides an explanation of the use of interfaces for writing to the console by using a buffer.
The following program is a nice illustration of the concept of interfaces in io :
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
// unbuffered: os.Stdout implements io.Writer
fmt.Fprintf(os.Stdout, "%s\n", "hello world! - unbuffered")
// buffered:
buf := bufio.NewWriter(os.Stdout)
// and now so does buf:
fmt.Fprintf(buf, "%s\n", "hello world! - buffered")
buf.Flush()
}
Interfaces in io Package
Here is the actual signature of fmt.Fprintf() :
func Fprintf(w io.Writer, format string, a ...interface {}) (n int, err er
ror)
It doesn’t write to a file, it writes to a variable of type io.Writer , that is:
Writer defined in the io package:
type Writer interface {
Write(p []byte) (n int, err error)
}
The function fmt.Fprintf() writes a string according to the format string of
its first argument, which must implement io.Writer . Fprintf() can write to
any type that has a Write method, including os.Stdout , files (like os.File ),
pipes, network connections, channels, and buffers from the bufio package.
This package defines a type Writer struct { ... } . The bufio.Writer
implements the Write method:
func (b *Writer) Write(p []byte) (nn int, err error)
It also has a factory: give it an io.Writer , it will return a buffered io.Writer
in the form of a bufio.Writer :
func NewWriter(wr io.Writer) (b *Writer)
Buffering works for anything that writes.
Remark: Never forget to use Flush() when terminating buffered
writing, else the last output won’t be written.
To transmit a data structure across a network or to store it in a file, it must be
encoded and then decoded again. Many encodings exist, e.g., JSON, XML, gob,
Google’s protocol buffers, and others. Go has implementations for all of them;
in the next three sections, we will discuss them.
The JSON Data Format
This lesson discusses speci cally the JSON type of data, and how Go reads and writes data in the JSON format
ef ciently.
WE'LL COVER THE FOLLOWING
• Introduction
• Explanation
• Marshal
• UnMarshal
Introduction #
Structs can contain binary data; if this were printed as the
text, it would not be readable for a human. Moreover, the
data does not include the name of the struct field, so we
don’t know what the data means. To remedy this, several
formats have been devised, which transform the data into
plain text, but annotated by their field names, so humans
can read and understand the data.
Data in these formats can be transmitted over a network. In this way, the data
is platform-independent and can be used as input/output in all kinds of
applications, no matter what the programming language or the operating
system is. The following terms are common here:
data structure to special format string = marshaling or encoding
(before transmission at the sender or source)
special format string to data structure = unmarshaling or decoding
(after transmission at the receiver or destination)
Marshaling is used to convert in-memory data to the special format (data ->
string), and vice versa for unmarshaling (string -> data structure).
Explanation #
Encoding does the same thing, but the output is a stream of data
(implementing io.Writer); decoding starts from a stream of data
(implementing io.Reader ) and populates a data structure. Well known is the
XML-format (which we’ll study later). Another popular format sometimes
preferred for its simplicity is JSON (JavaScript Object Notation, see here). It is
most commonly used for communication between web back-ends and
JavaScript programs running in the browser, but it is used in many other
places too. This is a short piece of JSON:
{
"Person":
{ "FirstName": "Laura",
"LastName": "Lynn"
}
}
Though XML is widely used, JSON is less verbose (thus taking less memory
space, disk space, and network bandwidth) and more readable. This explains
why it is the de facto standard in most web applications for async
communication.
The JSON package provides an easy way to read and write JSON data from
your Go programs. We will use it in the following example:
package main
import (
"fmt"
"encoding/json"
"os"
"log"
)
type Address struct {
Type string
City string
Country string
}
type VCard struct {
FirstName string
LastName string
Addresses []*Address
Remark string
}
func main() {
pa := &Address{"private", "Aartselaar","Belgium"}
wa := &Address{"work", "Boom", "Belgium"}
vc := VCard{"Jan", "Kersschot", []*Address{pa,wa}, "none"}
// fmt.Printf("%v: \n", vc) // {Jan Kersschot [0x126d2b80 0x126d2be0] none}:
// JSON format:
js, _ := json.Marshal(vc)
fmt.Printf("JSON format: %s", js)
// using an encoder:
file, _ := os.OpenFile("output/vcard.json", os.O_CREATE|os.O_WRONLY, 0)
defer file.Close()
enc := json.NewEncoder(file)
err := enc.Encode(vc)
if err != nil {
log.Println("Error in encoding json")
}
}
Reading and Writing JSON
To use this functionality, we have to import the package encoding/json (see
line 4). Two struct types are defined here:
Address is defined at line 9. It contains three fields: Type , City , and
Country .
VCard is defined at line 15. It contains four fields: FirstName , LastName ,
Addresses , and Remark .
At line 22 and line 23, we make two addresses pa and wa . At line 24, a VCard
instance vc is made with the field Addresses containing the previously made
Address instances.
At line 27, we call the Marshal function to make a json string js from vc . The
second left-hand side argument is _ , thereby indicating that we discard any
errors from Marshal() . The JSON string js is printed at line 28.
At line 30, we open a file vcard.json, and close it at the end of the program
with defer at line 31. At line 32, we construct a new json encoder for this file
and call the encode method on it with the struct vc as a parameter. This
makes a json string from vc and writes it into the file. The return value is nil
or an error, on which we test from line 34 to line 36.
Marshal #
The json.Marshal() function with signature func Marshal(v interface{})
([]byte, error) encodes the data into the following json-text (in fact a
[]bytes):
{"FirstName":"Jan","LastName":"Kersschot","Addresses":[{"Type":"private",
"City":"Aartselaar","Country":"Belgium"},{"Type":"work","City":"Boo
m","Country":"Belgium"}],"Remark":"none"}
For security reasons, in web applications, it is better to use the
json.MarshalForHTML() function, which performs an HTMLEscape on the data,
so that the text will be safe to embed inside HTML <script> tags. The default
Go types used in JSON are:
bool for JSON booleans
float64 for JSON numbers
string for JSON strings
nil for JSON null.
Not everything can be JSON-encoded though; only data structures that can be
represented as valid JSON will be encoded:
JSON objects only support strings as keys; to encode a Go map type, it
must be of the form map[string]T (where T is any Go type supported by
the json package).
Channel, complex, and function types cannot be encoded.
Cyclic data structures are not supported; they will cause Marshal to go
into an infinite loop.
Pointers will be encoded as the values they point to (or ‘null’ if the
pointer is nil).
The json package only accesses the exported fields of struct types; only those
will be present in the JSON output. This is necessary because json uses
reflection on them.
UnMarshal #
The UnMarshal() function with signature func Unmarshal(data []byte, v
interface{}) error performs the decoding from JSON to program data-
structures. First, we create a struct, for example, Message , where the decoded
data will be stored in:
var m Message
and call Unmarshal() , passing it a []byte of JSON data b and a pointer to m :
err := json.Unmarshal(b, &m)
Through reflection, it tries to match the JSON-fields with the destination struct
fields; only the matched fields are filled with data. So no error occurs if there
are fields that do not match; they are disregarded.
Now that you’re familiar with the basics of JSON in Golang, let’s see how
encoding and decoding work with JSON.
Decoding with JSON
This lesson provides a detailed explanation on decoding data with JSON by providing coded examples.
WE'LL COVER THE FOLLOWING
• Decoding arbitrary data
• Decoding data into a struct
• Working with JSON and structs
• Streaming encoders and decoders
Decoding arbitrary data #
The json package uses map[string]interface{} and []interface{} values to
store arbitrary JSON objects and arrays; it will happily unmarshal any valid
JSON blob into a plain interface{} value.
Consider this JSON data, stored in the variable b :
b := []byte(`{"Name": "Wednesday", "Age": 6, "Parents": ["Gomez", "Mortici
a"]}`)
Without knowing this data’s structure, we can decode it into an interface{}
value with Unmarshal:
var f interface{}
err := json.Unmarshal(b, &f)
At this point, the value in f would be a map, whose keys are strings and
whose values are themselves stored as empty interface values:
map[string]interface{}{
"Name": "Wednesday",
"Age": 6,
Parents : []interface{}{
"Gomez",
"Morticia",
},
}
To access this data we can use a type assertion to access f 's underlying
map[string]interface{} :
m := f.(map[string]interface{})
We can then iterate through the map with a range statement and use a type
switch to access its values as their concrete types:
for k, v := range m {
switch vv := v.(type) {
case string:
fmt.Println(k, "is string", vv)
case int:
fmt.Println(k, "is int", vv)
case []interface{}:
fmt.Println(k, "is an array:")
for i, u := range vv {
fmt.Println(i, u)
}
default:
fmt.Println(k, "is of a type I don't know how to handle")
}
}
In this way, you can work with unknown JSON data while still enjoying the
benefits of type safety.
Decoding data into a struct #
If we know beforehand the semantics of the json-data, we can then define an
appropriate struct and unmarshal into it. For example, in the previous
section, we would define:
type FamilyMember struct {
Name string
Age int
Parents []string
}
and then do the unmarshaling with:
var m FamilyMember
err := json.Unmarshal(b, &m)
This allocates a new slice behind the scenes. This is typical of how unmarshal
works with the supported reference types (pointers, slices, and maps).
Here is an example that shows how it works:
package main
import (
"fmt"
"encoding/json"
)
type Node struct {
Left *Node
Value interface{}
Right *Node
}
func main() {
b := []byte(`{"Value": "Father", "Left": {"Value": "Left child"}, "Right": {"Value": "Right
child"}}`)
var f Node
json.Unmarshal(b, &f)
fmt.Println(f, f.Left, f.Right)
}
JSON Tree
In the code above, we define a struct called Node at line 7 that refers to a
binary tree. It has three fields. Left and Right being pointers to other Nodes ,
while Value contains the data. Value can contain anything; that’s why its type
is interface{} .
At line 13, we define an array of bytes b , containing raw data for a map with
keys Value , Left and Right .
We declare variable f to be of type Node at line 15, and at line 16, we decode
b into the f structure. We see that the pointers are all memory addresses:
real ones in the top nodes, and nil for the left and right nodes.
Working with JSON and structs #
The json package also allows to encode and decode json , having fields
names different from the ones in a struct, for example:
package main
import (
"fmt"
"encoding/json"
)
type Person struct {
Name string `json:"personName"`
Age int `json:"personAge"`
}
func main() {
b := []byte(`{"personName": "Obama", "personAge": 57}`)
var p Person
// Unmarshalling
json.Unmarshal(b, &p)
fmt.Println(p)
// Marshalling
js, _ := json.Marshal(p)
fmt.Printf("%s\n", js)
}
JSON and Structs
Here, we define a struct Person at line 7 while we define alternative json
names as a raw string enclosed in backticks, e.g. Name string
json:"personName" means the field Name has as name personName in JSON.
At line 13, we make some JSON data in variable b , and at line 14, we make an
instance p of struct Person . Then we decode b into p at line 16 with
Unmarshal(b, &p) and print the resulting struct out.
At line 19, we use Marshal to again make a JSON string js from the struct p ,
and we print it out, showing that the JSON field names are used:
Streaming encoders and decoders #
The json package provides Decoder and Encoder types to support the
common operation of reading and writing streams of JSON data. The
NewDecoder and NewEncoder functions wrap the io.Reader and io.Writer
interface types.
func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder
To write the json directly to a file, use json.NewEncoder on a file (or any other
type which implements io.Writer ) and call Encode() on the program data.
The reverse is done by using a json.Decoder and the Decode() function:
func NewDecoder(r io.Reader) *Decoder
func (dec *Decoder) Decode(v interface{}) error
See how the use of interfaces generalizes the implementation. The data
structures can be everything because they only have to implement interface{}.
The targets or sources to/from which the data is encoded must implement
io.Writer or io.Reader . Due to the ubiquity of readers and writers, these
Encoder and Decoder types can be used in a broad range of scenarios, such as
reading and writing to HTTP connections, web-sockets, or files.
Now that you are familiar with how encoding and decoding works with JSON,
let’s see how Go handles XML type data in the next lesson.
The XML Data Format
This lesson discusses speci cally the XML type of data, and how Go reads and writes data in the XML format
ef ciently.
WE'LL COVER THE FOLLOWING
• What is XML?
• Marshalling and unmarshaling
• Parsing XML-data
What is XML? #
XML is a markup language that sets some rules to encode
data in both human and machine-readable format. It stands
for eXtensible Markup Language. The XML equivalent for
the JSON example used in the last lesson is:
<Person>
<FirstName>Laura</FirstName>
<LastName>Lynn</LastName>
</Person>
Marshalling and unmarshaling #
Like the json package xml contains a Marshal() and an UnMarshal() function
to encode and decode data to and from XML. In the same way as with JSON,
XML-data can be marshaled or un-marshaled to/from structs.
Here is an example where we see this in action:
package main
import (
"encoding/xml"
"fmt"
)
type Person struct {
Name string `xml:"personName"`
Age int `xml:"personAge"`
}
func main() {
b := []byte(`<Person><personName>Obama</personName><personAge>57</personAge></Person>`)
var p Person
// Unmarshalling
xml.Unmarshal(b, &p)
fmt.Println(p)
// Marshalling
xmlString, _ := xml.Marshal(p)
fmt.Printf("%s\n", xmlString)
}
XML Tree
The XML functionality resides in the encoding/xml package which is imported
at line 3. At line 7, we define a struct Person ; notice that the field names have
an alias XML name, which is the name that data will have in XML strings or
files.
At line 14, we have XML data as a string b . At line 15, we declare a variable p
of type Person . At line 17, we decode the XML data from b to p (referenced
here as &p ) with Unmarshal . At line 20, we do the reverse, encoding the struct
p to an xmlString variable, which is printed out at line 21.
Remark: Use Unmarshal to deserialize a short text you already have in
memory (e.g., some user input) and NewDecoder when you are reading
from a stream (e.g., from a file).
Parsing XML-data #
The encoding/xml package also implements a simple XML parser (SAX) to read
XML-data and parse it into its constituents. The following code illustrates how
this parser can be used:
package main
import (
"fmt"
"strings"
"encoding/xml"
)
var t, token
var
err
xml.Token
error
func main() {
input := "<Person><FirstName>Laura</FirstName><LastName>Lynn</LastName></Person>"
inputReader := strings.NewReader(input)
p := xml.NewDecoder(inputReader)
for t, err = p.Token(); err == nil; t, err = p.Token() {
switch token := t.(type) {
case xml.StartElement:
name := token.Name.Local
fmt.Printf("Token name: %s\n", name)
for _, attr := range token.Attr {
attrName := attr.Name.Local
attrValue := attr.Value
fmt.Printf("An attribute is: %s %s\n", attrName, attr
// ...
}
case xml.EndElement:
fmt.Println("End of token")
case xml.CharData:
content := string([]byte(token))
fmt.Printf("This is the content: %v\n", content )
// ...
default:
// ...
}
}
}
Parsing XML
In the code above, the line 12 initializes an XML string called input . At line
13, we construct a new string reader inputReader on top of it, and at line 14,
we start the XML decoding by defining a decoder p on intputReader .
The decoding is done in a for-loop starting at line 16. We read in a token p
with the Token() method. As long as the error err returned by token remains
nil , we execute the for-loop and then read the next token.
The loop ends when the Token() method returns an error at the end of the file
because there is no token left to parse. Further processing is done in a typeswitch at line 17. It is defined according to the current kind of XML-tag that
was found in token t .
If it is a StartElement (see line 18), we print out its XML name. Then in the forloop (from line 21 to line 26), we retrieve all of its attributes and print their
names and values. When an XML element is closed, End of token is printed at
line 28. Line 29 converts the content in the XML data ( Chardata ) to a [
]bytes , making it readable with a string conversion, and prints it out.
The package defines a number of types for XML-tags: StartElement , Chardata
(this is the actual text between the start- and end-tag), EndElement , Comment ,
Directive or ProcInst . It also defines a struct Decoder . The method
NewDecoder takes an io.Reader (in this case a strings.NewReader ) and
produces an object of type Parser . This has a method Token() that returns the
next XML token in the input stream. At the end of the input stream, Token()
returns nil ( io.EOF ).
The XML-text is walked through in a for-loop, which ends when the Token()
method returns an error at the end of the file because there is no token left to
parse. Further processing can be defined according to the current kind of
XML-tag through a type-switch. Content in Chardata is just a [ ]bytes, make it
readable with a string conversion.
Now that you are familiar with how the xml package deals with XML type
data, let’s see how Go handles binary type data in the next lesson.
Data Transport through gob
This lesson discusses speci cally the binary type of data and how Go uses the binary type to transfer data using
the gob package.
WE'LL COVER THE FOLLOWING
• What is gob?
• Explanation
What is gob? #
The gob is Go’s format for serializing and deserializing
program data in binary format. It is found in the encoding
package. Data in this format is called a gob (short for Go
binary format). It is similar to Python’s pickle or Java’s
Serialization. It is typically used in transporting arguments
and results of remote procedure calls (RPCs) (see the rpc
package Chapter 13). More generally, it used for data
transport between applications and machines. In what way
is it different from JSON or XML? The gob was tailored
explicitly for working in an environment that is entirely in
Go, for example, communicating between two servers
written in Go.
This package works with the language in a way that an externally-defined,
language-independent encoding cannot. That’s why the format is binary in the
first place, not a text-format like JSON or XML. Gobs are not meant to be used
in other languages than Go because, in the encoding and decoding process,
Go’s reflection capability is used.
Explanation #
p a at o
Gob files or streams are entirely self-describing. For every type, they contain a
description of that type, and they can always be decoded in Go without any
knowledge of the file’s contents. Only exported fields are encoded; zero values
are not taken into account. When decoding structs, fields are matched by
name and compatible type, and only fields that exist in both are affected. In
this way, a gob decoder client will still function when in the source datatype
fields have been added. The client will continue to recognize the previously
existing fields. Also, there is excellent flexibility provided, e.g., integers are
encoded as unsized, and variable-length, regardless of the concrete Go type at
the sender side.
So if we have at the sender side a struct T :
type T struct { X, Y, Z int }
var t = T{X: 7, Y: 0, Z: 8}
This can be captured at the receiver side in a variable u of type struct U :
type U struct { X, Y *int8 }
var u U
At the receiver, X gets the value 7, and Y the value 0 (which was not
transmitted). In the same way as json , gob works by creating an encoder
object with a NewEncoder() function and calling Encode() , again completely
generalized by using io.Writer . The inverse is done with a decoder object
with a NewDecoder() function and calling Decode() , generalized by using
io.Reader .
In the following program, you’ll find a simple example of decoding and
encoding, simulating transmission over a network with a buffer of bytes:
package main
import (
"bytes"
"fmt"
"encoding/gob"
"log"
)
type P struct {
X, Y, Z int
Name string
}
type Q struct {
X, Y *int32
Name string
}
func main() {
// Initialize the encoder and decoder. Normally enc and dec would be
// bound to network connections and the encoder and decoder would
// run in different processes.
var network bytes.Buffer // Stand-in for a network connection
enc := gob.NewEncoder(&network) // Will write to network.
dec := gob.NewDecoder(&network)// Will read from network.
// Encode (send) the value.
err := enc.Encode(P{3, 4, 5, "Pythagoras"})
if err != nil {
log.Fatal("encode error:", err)
}
// Decode (receive) the value.
var q Q
err = dec.Decode(&q)
if err != nil {
log.Fatal("decode error:", err)
}
fmt.Printf("%q: {%d,%d}\n", q.Name, *q.X, *q.Y)
}
Encoding and Decoding with gob
In the code above, a network connection is simulated by a buffer called
network at line 23. At line 24, we initialize the encoder enc on the buffer; this
will write to the network. At line 25, we initialize the decoder dec on the
buffer; this will read from the network.
On our side, we have the data of the struct type P , defined at line 9. On the
other side, we will receive the data in the struct type Q , defined at line 14.
At line 27, we encode a P struct. At line 32 a variable q of struct type Q is
declared, and the data transmitted is decoded in q . Apart from the normal
error-handling, we print out this q value: “Pythagoras”: {3,4}.
Note that Z value 5 was not received because it wasn’t defined in Q .
The following is an example of a mixture of readable and binary data, as you
will see when you try to read it in a text editor:
package main
import (
"encoding/gob"
"log"
"os"
)
type Address struct {
Type string
City string
Country string
}
type VCard struct {
FirstName string
LastName string
Addresses []*Address
Remark string
}
func
pa
wa
vc
main() {
:= &Address{"private", "Aartselaar","Belgium"}
:= &Address{"work", "Boom", "Belgium"}
:= VCard{"Jan", "Kersschot", []*Address{pa,wa}, "none"}
// fmt.Printf("%v: \n", vc) // {Jan Kersschot [0x126d2b80 0x126d2be0] none}:
// using an encoder:
file, _ := os.OpenFile("output/vcard.gob", os.O_CREATE|os.O_WRONLY, 0)
defer file.Close()
enc := gob.NewEncoder(file)
err := enc.Encode(vc)
if err != nil {
log.Println("Error in encoding gob")
}
}
Reading and Encoding Binary Data
In the code above, we define our previous struct types Address (from line 8 to
line 12) and Vcard (from line 14 to line 19).
Then, in main() (from line 22 to line 24), we make instances of these structs.
We will write the vc value to the file output/vcard.gob. This file is opened for
writing at line 28 because the second argument is _ as error-handling is left
out. We make sure the file gets closed at line 29. Then, at line 30, we define an
encoder enc on this file. Next, we use enc at line 31 to encode the vc data in
gob format and write it to the file. Lastly, from line 32 to line 34, we check for
a possible error.
Now that you’re familiar with the gob package and how it deals with binary
data, the next lesson brings you a challenge to solve.
Challenge: Decode the Contents
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Write a program that reads in the file vcard.gob, decodes the contents and
prints it. It’s the inverse of the program written in the previous lesson that
encodes personal information of a person (first name, last name, address, and
remark).
To read the file vcard.gob, simply write file, _ := os.Open("vcard.gob") . The
file is already present behind the project but isn’t visible.
Hint: Do not forget to import bufio , io , encoding/gob , and log
packages.
Try to attempt the challenge below. Good Luck!
package main
import (
)
type Address struct {
Type
City
Country
}
type VCard struct {
FirstName
LastName
Addresses
string
string
string
string
string
[]*Address
Remark
string
}
func main() {
// write your implementation here
}
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Decode the Contents
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"bufio"
"fmt"
"encoding/gob"
"log"
"os"
)
type Address struct {
Type
City
Country
}
type VCard struct {
FirstName
LastName
Addresses
Remark
}
var content
var vc VCard
string
string
string
string
string
[]*Address
string
string
func main() {
// using a decoder:
file, _ := os.Open("vcard.gob")
defer file.Close()
inReader := bufio.NewReader(file)
dec := gob.NewDecoder(inReader)
err := dec.Decode(&vc)
if err != nil {
log.Println("Error in decoding gob")
}
fmt.Println(vc)
}
This is the inverse from the code example in the previous lesson: we have a
file vcard.gob, and we know that it contains gob data structured as VCard . We
make an instance vc of Vcard at line 24. At line 28, we open the file, discard
error-handling and close the file at the end with defer . At line 30, we
construct a reader on the file, and at line 31, we construct a new decoder dec
on that reader. We use the Decode method on dec to store our data in vc ,
which we print out at line 36. There’s usual error-handling from line 33 to
line 35.
That’s it for the solution. In the next lesson, you’ll see how Go provides
support for cryptography.
Cryptography with Go
This lesson describes a technique for secure communication of data and how Go achieves this security.
WE'LL COVER THE FOLLOWING
• Introduction
• Example
Introduction #
A technique to secure data from the third parties over
communication is called cryptography. The data
transferred over networks must be encrypted so that no
hacker can read or change it. Also, checksums calculated
over the data, before sending and after receiving, must be
identical. Given the business of its mother company Google,
it will come as no surprise to see that Go has more than 30
packages to offer in this field, all contained in its standard
library:
hash package: implements the adler32, crc32, crc64 and fnv checksums
crypto package: implements other hashing algorithms like md5, sha1,
and so on and complete encryption implementations for aes, rc4, rsa,
x509, and so on.
Example #
In the following program, we compute and output some checksums with sha1:
package main
import (
"fmt"
"crypto/sha1"
"io"
"log"
)
func main() {
hasher := sha1.New()
io.WriteString(hasher, "test")
b := []byte{}
fmt.Printf("Result: %x\n", hasher.Sum(b))
fmt.Printf("Result: %d\n", hasher.Sum(b))
hasher.Reset()
data := []byte("We shall overcome!")
n, err := hasher.Write(data)
if n!=len(data) || err!=nil {
log.Printf("Hash write error: %v / %v", n, err)
}
checksum := hasher.Sum(b)
fmt.Printf("Result: %x\n", checksum)
}
Cryptography with sha1
In the code above, we import the package crypto/sha1 at line 4. At line 10, we
make a new instance hasher of sha1 , which we use at line 11 to encrypt the
string test.
With sha1.New() , a new hash.Hash object is made to compute the SHA1
checksum. The type Hash is, in fact, an interface, itself implementing the
io.Writer interface:
type Hash interface {
// Write adds more data to the running hash.
// It never returns an error.
io.Writer
// Sum returns the current hash, without changing the
// underlying hash state.
Sum() []byte
// Reset resets the hash to one with zero bytes written.
Reset()
// Size returns the number of bytes Sum will return.
Size() int
}
Through io.WriteString or hasher.Write , the checksum of the given string is
computed. This is first done for test at line 13 and line 14, the respective
outputs are:
Result: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
Result: [169 74 143 229 204 177 155 166 28 76 8 115 211 145 233 135 152 4
7 187 211]
The hasher.Write is used at line 17 to encrypt We shall overcome! After
error-handling (from line 18 to line 20), we calculate the checksum and print
it out:
Result: e2222bfc59850bbb00a722e764a555603bb59b2a
That is how Golang implements cryptography on data with its packages to
make data secure. There is a quiz in the next lesson for you to solve.
Exercise: Deduce the Outputs
In this lesson your concepts will be evaluated through a quiz.
Choose one possible correct answer.
1
If the following program is written in file main.go, and is executed via
the command line as go run main.go Evan Michael Laura what will the
output be?
package main
import (
"fmt"
"os"
"strings"
)
func main() {
who := "World"
if len(os.Args) > 1 { // os.Args[0] == hello_who
who = strings.Join(os.Args[1:], " ")
}
fmt.Println("Hello", who, "!")
}
COMPLETED 0%
1 of 2
We hope that you performed well in the quiz. That’s it about the read and
write operations in Go. In the next chapter, we’ll discuss how to handle errors
and test the code, in detail.
Error-Handling
This lesson introduces error-handling in Go.
WE'LL COVER THE FOLLOWING
• Introduction
• De ning errors
• Making an error-object with fmt
Introduction #
Go does not have an exception-handling mechanism, like the try/catch in Java
or .NET. For instance, you cannot throw exceptions. Instead, it has a deferpanic-and-recover mechanism. The designers of Go thought that the try/catch
mechanism is overused and that the throwing of exceptions in lower layers to
higher layers of code uses too many resources. The mechanism they devised
for Go can ‘catch’ an exception, but it is much lighter. Even then, it should
only be used as a last resort.
How then does Golang deal with normal errors by default? The Go way to
handle errors is for functions and methods to return an error object as their
only or last return value—or nil if no error occurred—and for the code calling
functions to always check the error they receive.
Note: Never ignore errors because ignoring them can lead to program
crashes.
Handle the errors and return from the function in which the error occurred
with an error message to the user: that way, if something goes wrong, your
program will continue to function, and the user will be notified. The purpose
of panic-and-recover is to deal with genuinely exceptional (so unexpected)
problems and not with normal errors.
Go makes a distinction between critical and non-critical errors: non-critical
errors are returned as normal return values, whereas for critical errors, the
panic-recover mechanism is used.
Library routines often return some sort of error indication to the calling
function. In the preceding chapters, we saw the idiomatic way in Go to detect
and report error conditions:
A function which can result in an error returns two variables, a value,
and an error-code; the latter is nil in cases of success and != nil in cases of
an error condition.
After the function call, the error is checked. In case of an error (if error !=
nil), the execution of the current function (or if necessary, the entire
program) is stopped.
In the following code, Func1 from package pack1 is tested on its return code:
if value, err := pack1.Func1(param1); err != nil {
fmt.Printf("Error %s in pack1.Func1 with parameter %v", err.Error(), par
am1)
return // or: return err
}
// Process(value)
Always assign an error to a variable within a compound if-statement; this
makes for clearer code.
Instead of fmt.Printf , corresponding methods of the log package could be
used, or even a panic , if it doesn’t matter that the program aborts.
Go has a built-in error interface type:
type error interface {
Error() string
}
Error values are used to indicate an abnormal state. The package errors
contains an errorString struct, which implements the error interface. To stop
the execution of a program in an error-state, we can use os.Exit(1).
error interface
errors.New(text string) error
err.Error() string
De ning errors #
Whenever you need a new error-type, you can make one with the function
errors.New from the errors package (which you will have to import), and
give it an appropriate error-string, as follows:
err := errors.New("math - square root of negative number")
Below, you see a simple example of its use:
package main
import (
"errors"
"fmt"
)
var errNotFound error = errors.New("Not found error")
func main() {
fmt.Printf("error: %v", errNotFound)
}
// error: Not found error
Errors Definition
At line 3, we import the errors package. At line 7, we make a new error
instance errNotFound , with a specific message. Then, at line 10, we print out
this error, which displays its message (%v is the default formatting).
Applied in a function testing the parameter of a square root function, this
could be used as:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math - square root of negative number")
}
// implementation of Sqrt
}
You could call this function as follows:
if f, err := Sqrt(-1); err != nil {
fmt.Printf("Error: %s\n", err)
}
Because fmt.Printf automatically uses the Error() method for err , the
error-string “Error: math - square root of negative number” is printed out.
Because there will often be a prefix like Error: , it is preferred not to start
your error string with a capital letter.
In most cases, it is useful to make a custom error struct type, which apart from
the (low level) error-message also contains other useful information, such as
the operation which was taking place (open file, …), the full path-name or URL
which was involved, and so on. The String() method then provides an
informative concatenation of all this information. As an example, see
PathError , which can be issued from an os.Open :
// PathError records an error and the operation and file path that cause
d it.
type PathError struct {
Op string // "open", "unlink", and so on
Path string // The associated file.
Err error // Returned by the system call.
}
func (e *PathError) String() string {
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
In case different possible error-conditions occur, it may be useful to test with
a type assertion or type switch for the exact error. Possibly, try a remedy or a
recovery of the error-situation:
// err != nil
if e, ok := err.(*os.PathError); ok {
// remedy situation
}
Or:
switch err := err.(type) {
case ParseError:
PrintParseError(err)
case PathError:
PrintPathError(err)
...
default:
fmt.Printf("Not a special error, just %s\n", err)
}
As a 2nd example, consider the json package. This specifies a SyntaxError
type that the json.Decode() function returns when it encounters a syntax
error parsing a JSON document:
type SyntaxError struct {
msg string // description of error
// error occurred after reading Offset bytes, from which line and colum
n can be obtained
Offset int64
}
func (e *SyntaxError) String() string { return e.msg }
In the calling code, you could again test whether the error is of this type with
a type assertion, like this:
if serr, ok := err.(*json.SyntaxError); ok {
line, col := findLine(f, serr.Offset)
return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err)
}
A package can also define its own specific Error with additional methods, like
net.Error :
package net
type Error interface {
error
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
}
As you have seen in all the examples, the following naming convention is
applied: Error types end in Error, and error variables are called (or start with)
err or Err.
The syscall is the low-level, external package, which provides a primitive
interface to the underlying operating system’s calls; these return integer errorcodes. The type syscall.Errno implements the Error interface. Most syscall
functions return a result and a possible error, like:
r, err := syscall.Open(name, mode, perm)
if err != 0 {
fmt.Println(err.Error())
}
os also provides a standard set of error-variables like os.EINVAL , which come
from syscall - errors:
var (
EPERM Error = Errno(syscall.EPERM)
ENOENT Error = Errno(syscall.ENOENT)
ESRCH Error = Errno(syscall.ESRCH)
EINTR Error = Errno(syscall.EINTR)
EIO Error = Errno(syscall.EIO)
...
)
Making an error-object with fmt #
Often, you will want to return a more informative string with, for example,
the value of the wrong parameter inserted; this is accomplished with the
fmt.Errorf() function. It works exactly like fmt.Printf() , taking a format
string with one or more format specifiers and a corresponding number of
variables to be substituted. But, instead of printing the message, it generates
an error object with that message. Applied to our Sqrt-example from above:
if f < 0 {
return 0, fmt.Errorf("math: square root of negative number %g", f)
}
While reading from the command-line, we generate an error with a usage
message when a help-flag is given:
if len(os.Args) > 1 && (os.Args[1] == "-h" || os.Args[1] == "--help") {
err = fmt.Errorf("usage: %s infile.txt outfile.txt", filepath.Base(os.Ar
gs[0]))
return
}
Making error objects to catch errors is the basics of the error-handling. What
if we found an error at runtime? What is the behavior of a Go program in this
case? And what can we do in this regard? To get a hold on all of this
information, move to the next lesson.
Run-time Exceptions and Panic
This lesson explains runtime errors in detail and how a program responds to them.
WE'LL COVER THE FOLLOWING
• Run-time panic
• Go panicking
Run-time panic #
When an execution errors occur, such as attempting to index an array out of
bounds or a type assertion failing, the Go runtime triggers a run-time panic
with a value of the interface type runtime.Error , and the program crashes
with a message of the error. This value has a RuntimeError() method, to
distinguish it from a normal error.
Panic can also be initiated from code directly: when the error-condition
(which we are testing in the code) is so severe and unrecoverable that the
program cannot continue, the panic function is used, effectively creating a
run-time error that will stop the program. It takes one argument of any type,
usually a string, to be printed out when the program dies. So, panic resembles
the throw statement from Java or C#. The Go runtime takes care to stop the
program and issue some debug information. How it works is illustrated
below:
package main
import "fmt"
func main() {
fmt.Println("Starting the program")
panic("A severe error occurred: stopping the program!")
fmt.Println("Ending the program")
}
Panic
"Ending the program" is not printed because of panic . The call to panic at
line 6 displays the error message and then stops the program.
Here is a concrete example of checking whether the program starts with a
known user:
var user = os.Getenv("USER")
func check() {
if user == "" {
panic("Unknown user: no value for $USER")
}
}
This could be checked in an init() function of a package that is imported.
Panic can also be used in the error-handling pattern when the error must
stop the program:
if err != nil {
panic("ERROR occurred: " + err.Error())
}
Go panicking #
If panic is called from a nested function, it immediately stops the execution of
the current function; all defer statements are guaranteed to execute. Then,
control is given to the function caller, which receives this call to panic. This
bubbles up to the top level, executing defers, and at the top of the stack, the
program crashes. The error condition is reported on the command-line using
the value given to panic; this termination sequence is called panicking.
The standard library contains several functions whose name is prefixed with
Must, like regexp.MustCompile or template.Must ; these functions panic()
when converting the string into a regular expression or template produces an
error.
Of course, taking down a program with panic should not be done lightly, so
every effort must be exercised to remedy the situation and let the program
continue. In the next lesson, we’ll see how to recover a program from panic.
Recover
This lesson is a guide for programmers to recover their code from panic or any error condition.
WE'LL COVER THE FOLLOWING
• What is recover() function?
• Explanation
What is recover() function? #
As the name indicates, this built-in function can be used to recover from a
panic or an error-condition: it allows a program to regain control of a
panicking Go routine, stopping the terminating sequence and resuming
normal execution. The recover is only useful when called inside a deferred
function: it then retrieves the error value passed through the call to panic .
When used in normal execution, a call to recover will return nil and have no
other effect. The panic causes the stack to unwind until a deferred recover()
is found or the program terminates.
Explanation #
The protect function in the example below invokes the function argument g
and protects callers from run-time panics raised by g , showing the message x
of the panic:
func protect(g func()) {
defer func() {
log.Println("done") // Println executes normally even if there is a pa
nic
if err := recover(); err != nil {
log.Printf("run time panic: %v", err)
}
}()
log.Println("start")
g() // possible runtime-error
}
It is analogous to the catch block in the Java and .NET languages. The log
implements a simple logging package. The default logger writes to standard
error and prints the date and time of each logged message. Apart from the
Println and Printf functions, the Fatal functions call os.Exit(1) after
writing the log message; Exit functions identically. The Panic functions call
panic after writing the log message; use this when a critical condition occurs
and the program must be stopped, like in the case when a web server could
not be started.
The log package also defines an interface type Logger with the same methods
when you want to define a customized logging system. Here is a complete
example which illustrates how panic , defer and recover work together:
package main
import (
"fmt"
)
func badCall() {
panic("bad end")
}
func test() {
defer func() {
if e := recover(); e != nil {
fmt.Printf("Panicking %s\r\n", e);
}
}()
badCall()
fmt.Printf("After bad call\r\n");
}
func main() {
fmt.Printf("Calling test\r\n");
test()
fmt.Printf("Test completed\r\n");
}
Panic and Recover
To follow the flow of the program, look at its output:
Calling test
Panicking bad end
Test completed
Let’s see how we got here. At the start, Calling test will be printed from line
21 in main() function. Then, control goes to line 22, where test() is called.
This starts with a defer of an anonymous function (implemented from line 11
to line 15); this will not be executed now. badCall() is called at line 16, which
causes a panic at line 7, and After bad call (from line 17) is never printed.
Normally, the program stops here, but if there is a remaining defer , this is
executed first before the panic starts its actions. At line 12, the recover stops
the panic and stores its error in e . Its message is printed at line 13, which is
the 2nd output line. Because the panic is recovered, test() completes
normally, and the end message Test completed is printed.
Defer, panic and recover in a sense are also control-flow mechanisms, like if,
for, etc. This mechanism is used at several places in the Go standard library,
e.g., in the json package when decoding or in the regexp package in the
Compile function. The convention in the Go libraries is that even when a
package uses panic internally, a recover is done so that its external API still
presents explicit error return values.
Until now, we have seen a program panicking in the main package and
recovering it. What if the code in a user-defined package faces an error that
causes it to get stuck. In the next lesson, you’ll learn how to handle this
situation.
Error-Handling and Panicking in a User-Defined
Package
This lesson provides an implementation and a detailed explanation about catching errors in custom packages and
recovering programs in case of a panic.
Here are a couple of best practices which every writer of custom packages
should apply:
Always recover from panic in your package: no explicit panic() should
be allowed to cross a package boundary.
Return errors as error values to the callers of your package.
This is nicely illustrated in the following code:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package parse
import (
"fmt"
"strings"
"strconv"
)
// A ParseError indicates
type ParseError struct {
Index int
//
Word string
//
Err error
//
}
an error in converting a word into an integer.
The index into the space-separated list of words.
The word that generated the parse error.
The raw error that precipitated this error, if any.
// String returns a human-readable error message.
func (e *ParseError) String() string {
return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word)
}
// Parse parses the space-separated words in in put as integers.
func Parse(input string) (numbers []int, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
fields := strings.Fields(input)
numbers = fields2numbers(fields)
return
}
func fields2numbers(fields []string) (numbers []int) {
if len(fields) == 0 {
panic("no words to parse")
}
for idx, field := range fields {
num, err := strconv.Atoi(field)
if err != nil {
panic(&ParseError{idx, field, err})
}
numbers = append(numbers, num)
}
return
}
In parse.go, we implement a simple version of a parse package. From line 9
to line 13, we define a ParseError type (see the comments in the code for
more info). Then, we have a String() method (from line 16 to line 18) to
display the error info.
Now, look at the header of the Parse function at line 21. It takes a string
input , and returns a slice of int and nil or a possible error. In other words,
the input is supposed to be a number of integers, and we transform the input
string to that. The important lines in the Parse function are line 32 and line
33:
At line 32, strings.Fields splits the input around white spaces and
returns a slice of substrings.
At line 33, the function fields2numbers is called on that slice and converts
it to a slice of integers.
This fields2numbers function is defined at line 37. It iterates over the slice
fields, at line 41, and converts each field to a number num at line 42. If
strconv.Atoi results in an error because the field is not a string, this is
handled with an if condition ( see the implementation from line 43 and line
45), causing panic and displaying a ParseError with the detailed info of the
problem. If everything is ok, num is appended to the slice nums at line 46 and
returned.
The Parse function starts with a defer of an anonymous function call
(implemented from line 22 to line 30). This tries to recover from any panic
that has happened and returns the error that occurred as err .
In main.go, starting at line 9, we use the parse package we discussed above.
We build a slice of strings to be parsed. A for-loop ( from line 17 to line 25),
iterates over this slice, applying Parse to each slice ex at line 19. The slice of
integers nums that is returned is printed at line 24, unless there is an error
which is handled (from line 20 to line 23). This produces the output you can
see in the terminal.
Now that you’re familiar with the defer/panic/recover mechanisms, in the next
lesson, you’ll come across closures for error-handling purposes.
Error-Handling Scheme with Closures
This lesson discusses closures for error-handling purposes in detail.
Whenever a function returns, we should test whether it results in an error:
this can lead to repetitive and tedious code. Combining the defer/panic/recover
mechanisms with closures can result in a far more elegant scheme that we
will now discuss. However, it is only applicable when all functions have the
same signature, which is rather restrictive. A good example of its use is in web
applications, where all handler functions are of the following type:
func handler1(w http.ResponseWriter, r *http.Request) { ... }
Suppose all functions have the signature: func f(a type1, b type2) . The
number of parameters and their types is irrelevant. We give this type a name:
fType1 = func f(a type1, b type2) .
Our scheme uses 2 helper functions:
check: a function which tests whether an error occurred, and panics if so:
func check(err error) { if err != nil { panic(err) } }
errorhandler: this is a wrapper function. It takes a function fn of our
type fType1 as parameter, and returns such a function by calling fn .
However, it contains the defer/recover mechanism:
func errorHandler(fn fType1) fType1 {
return func(a type1, b type2) {
defer func() {
if e, ok := recover().(error); ok {
log.Printf("run time panic: %v", err)
}
}()
fn(a, b)
}
}
When an error occurs, it is recovered and printed on the log. Apart from
simply printing the error, the application could also produce a customized
output for the user by using the template package. In a real project, a less
obtrusive name than the errorHandler could be used, like call(f1) .
The check() function is used in every called function, like this:
func f1(a type1, b type2) {
...
f, _, err := // call function/method
check(err)
t, err = // call function/method
check(err)
_, err = // call function/method
check(err)
...
}
The main() or other caller-function should then call the necessary functions
wrapped in errorHandler , like this:
func main() {
errorHandler(f1)
errorHandler(f2)
...
}
By using this mechanism, all errors are recovered, and the error-checking
code after a function call is reduced to check(err). In this scheme, different
error-handlers have to be used for different function types; they could be
hidden inside an error-handling package. Alternatively, a more general
approach could be using a slice of empty interface as a parameter and return
type. We will apply this in the web application in Chapter 13.
That’s it about errors and handling them in different possible ways. The next
lesson brings you a challenge to solve.
Challenge: Generate Panic from Division by 0
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Use the coding scheme you learned in the Recover lesson to provoke a real
runtime panic by dividing an integer by 0.
Try to attempt the challenge below. Good Luck!
package main
func badCall() {
}
func test() {
defer func() {
}()
badCall()
}
func main() {
}
Generate Panic from Division by 0
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Generate Panic from Division by 0
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"fmt"
)
func badCall() {
a, b := 10, 0
n := a / b
// it will cause panic as it's a division by 0
fmt.Println(n)
}
func test() {
defer func() {
if e := recover(); e != nil {
fmt.Printf("Panicking %s\r\n", e);
}
}()
badCall()
fmt.Printf("After bad call\r\n"); // It won't be called, because we just recovered from a
}
func main() {
fmt.Printf("Calling test\r\n");
test()
// calling test function
fmt.Printf("Test completed\r\n");
}
Panic from Division by Zero
Following the proposed schema, we call test() from the main() at line 25.
The function test() first defers an anonymous function call at line 13, trying
to recover from any panic, and printing the error at line 15. This code is
executed whenever a panic happens, or if not at the end of the function. Then,
we execute badCall() at line 19, which is defined from line 6 to line 10. It
divides by 0 at line 8, causing a panic. The output is:
Calling test
Panicking runtime error: integer divide by zero
Test completed
We see that because of the panic, line 20 is never executed, but because we
recovered from the error, line 26 will be executed.
That is it for the solution. In the next lesson, we’ll see what it means to start a
program externally, when it is needed and how it is done.
Starting an External Command or Program
This lesson provides an explanation about how to restart a program externally in case of a panic.
WE'LL COVER THE FOLLOWING
• Restart program if panic() occurs:
The os package contains the function StartProcess to call or start external OS
commands or binary executables; its 1st argument is the process to be
executed, the 2nd can be used to pass some options or arguments, and the 3rd
is a struct that contains basic info about the OS-environment. It returns the
process id (pid) of the started process, or an error if it failed.
The exec package contains the structures and functions to accomplish the
same task more easily; most important are exec.Command(name string, arg
...string) and Run() . The function exec.Command(name string, arg
...string) needs the name of an OS command or executable and creates a
Command object, which can then be executed with Run() which uses this object
as its receiver. The following program (which only works under Linux
because Linux commands are executed) illustrates their use:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"os/exec"
"os"
)
func main() {
// 1) os.StartProcess //
/*********************/
/* Linux: */
env := os.Environ()
procAttr := &os.ProcAttr{
Env: env,
Files: []*os.File{
os.Stdin,
os.Stdout,
os.Stderr,
},
}
// 1st example: list files
pid, err := os.StartProcess("/bin/ls", []string{"ls", "-l"}, procAttr)
if err != nil {
fmt.Printf("Error %v starting process!", err) //
os.Exit(1)
}
fmt.Printf("The process id is %v", pid)
// 2nd example: show all processes
pid, err = os.StartProcess("/bin/ps", []string{"-e", "-opid,ppid,comm"}, procAttr)
if err != nil {
fmt.Printf("Error %v starting process!", err) //
os.Exit(1)
}
fmt.Printf("The process id is %v", pid)
/* Output 1st:
The process id is &{2054 0}total 2056
-rwxr-xr-x 1 ivo ivo 1157555 2011-07-04 16:48 Mieken_exec
-rw-r--r-- 1 ivo ivo
2124 2011-07-04 16:48 Mieken_exec.go
-rw-r--r-- 1 ivo ivo
18528 2011-07-04 16:48 Mieken_exec_go_.6
-rwxr-xr-x 1 ivo ivo 913920 2011-06-03 16:13 panic.exe
-rw-r--r-- 1 ivo ivo
180 2011-04-11 20:39 panic.go
*/
// 2) exec.Run //
/***************/
// Linux: OK, but not for ls ?
// cmd := exec.Command("ls", "-l") // no error, but doesn't show anything ?
// cmd := exec.Command("ls")
// no error, but doesn't show anything ?
cmd := exec.Command("gedit") // this opens a gedit-window
err = cmd.Run()
if err != nil {
fmt.Printf("Error %v executing command!", err)
os.Exit(1)
}
fmt.Printf("The command is %v", cmd)
// The command is &{/bin/ls [ls -l] [] <nil> <nil> <nil> 0xf840000210 <nil> true [0xf84000ea
}
// in Windows: uitvoering: Error fork/exec /bin/ls: The system cannot find the path specified
Click the RUN button, and wait for the terminal to start. Type go run main.go
and press ENTER.
Starting an external command or program can be done in several ways (the
ways to do that are not platform-specific, but the examples in this program
only work on Unix-like machines):
The first way is using os.StartProcess , as at line 22. This accepts a folder
where to start the command, a slice of strings with the command and its
attributes (here: ls –l ) and a variable, which is a pointer to os.ProcAttr .
A possible error causes a print of the error and an exit of the program
(from line 23 to line 26). If the command is executed successfully, we get
back its process-id pid at line 22, which is printed at line 27. In the 2nd
example, with os.StartProcess , we launch the ps command, printing its
pid at line 34.
The second way is using exec.Command . Here, we use this at line 37 to
make a Command struct, and then run the command at line 38, and print it
out at line 43. Specifically, here, we launch a gedit text-editor window,
which you will not be able to see in the course window. The usual errorhandling is done from line 51 to line 54.
Restart program if panic() occurs: #
The following code snippet recovers from the panic and restarts the program.
If this fails the error is logged:
package main
import (
"os"
"os/exec"
"log"
)
func ultraCrazyFunction() {
prog := os.Args[0]
e := recover()
if e != nil {
// In case of panic, try to restart the entire application:
cmd := exec.Command(prog)
err := cmd.Run()
// If that fails, log the error
if err != nil {
log.Fatal(err.Error())
}
}
}
func main() {
defer ultraCrazyFunction()
}
That’s it about handling errors. An excellent way to debug a program is to test
it. Go provides the support of testing an application before running it. Let’s see
how to check a program for errors in the next lesson.
Testing and Benchmarking in Go
This lesson shows how to test a program before running it and therefore how to save it from panicking
beforehand.
WE'LL COVER THE FOLLOWING
• Testing with tool
Testing with tool #
Every new package should contain sufficient documentation and test code.
We already used Go’s testing tool go test in Chapter 7. Here, we will
elaborate on its use with some more examples.
A special package called testing provides support for automated testing,
logging, and error reporting. It also contains some functionality for
benchmarking functions.
Remark: for Windows, every time you see the folder pkg/linux_amd64,
replace it with pkg/windows.
To unit-test a package, you write several tests that you can frequently run
(after every update) to check the correctness of your code in small units. For
that, we will have to write a set of Go source files that will exercise and test
our code. This test-programs must be within the same package, and the files
must have names of the form *_test.go. The test code is separated from the
actual code of the package.
These _test programs are NOT compiled with the normal Go-compiler, so they
are not deployed when you put your application into production. Only go
test compiles all programs: the normal and the test programs.
Those files must import the testing package. In them, we write global
functions with names starting with TestZzz , where Zzz is an alphabetic
description of the function to be tested, like TestFmtInterface ,
TestPayEmployees , and so on. Those test functions should have a header of the
form: func TestAbcde(t *testing.T) , where T is a struct type passed to
TestZzz functions that manages test state and supports formatted test logs,
like t.Log , t.Error , t.ErrorF , and so on. At the end of each of these
functions, the output is compared with what is expected, and if these are not
equal, an error is logged. A successful test function returns. To signal a failure,
we have the following functions:
func (t *T) Fail() : marks the test function as having failed, but
continues its execution.
func (t *T) FailNow() : marks the test function as having failed and stops
its execution. All other tests in this file are also skipped, and execution
continues with the next test file.
func (t *T) Log(args ...interface{}) : the args are formatted using
default formatting and the text is logged in the error-log.
func (t *T) Fatal(args ...interface{}) : this has the combined effect of
func (t *T) Log(args ...interface{}) followed by func (t *T)
FailNow() .
Then, run go test . This compiles the test-programs and executes all the
TestZzz-functions . If all tests are successful, the word PASS will be printed.
The go test can also take one or more test programs as parameters, and
some options. With the option –v , each test function that is run and its teststatus is mentioned. For example:
go test -v fmt_test.go
=== RUN fmt.TestFlagParser
--- PASS: fmt.TestFlagParser
=== RUN fmt.TestArrayPrinter
--- PASS: fmt.TestArrayPrinter
...
The testing package also contains some types and functions for simple
benchmarking; the test code must then contain function(s) starting with
BenchmarkZzz and take a parameter of type *testing.B , e.g.:
func BenchmarkReverse(b *testing.B) {
...
}
The command go test –test.bench=.* executes all these functions. This will
call the functions in the code N number of times (e.g., N = 1000000).
Additionally, it shows this N and the average execution time of the functions
in ns (ns/op). If the functions are called with testing.Benchmark , you can also
run the program.
Now that you know the basics of testing in Go, let’s test an application in the
next lesson.
Testing: A Concrete Example
This lesson brings an application to test according to the concepts learned in the last lesson.
Here is a concrete example for you to run and learn to test:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package even
import "testing"
func TestEven(t *testing.T) {
if !Even(10) {
t.Log(" 10 must be even!")
t.Fail()
}
if Even(7) {
t.Log(" 7 is not even!")
t.Fail()
}
}
func TestOdd(t *testing.T) {
if !Odd(11) {
t.Log(" 11 must be odd!")
t.Fail()
}
if Odd(10) {
t.Log(" 10 is not odd!")
t.Fail()
}
}
This is our test program: it imports the even package at line 4, which will
contain the functional logic and the tests. Then, we have for-loop at line 8
controlling 100 iterations. It shows the first 100 integers and whether they are
even or not (see line 9).
The package even in the subfolder even contains the logic in the two
functions Even and Odd . The Even function returns true if an integer i
passed to it is even ( i%2 == 0 ). The Odd function returns true if an integer i
passed to it is not even ( i%2 != 0 ).
The file oddeven_test.go contains the tests. It belongs to the package even
(line 1), so it can use its logic. It needs the specific functionality from the
package testing , so this is imported at line 2. It contains 2 functions TestEven
and TestOdd , both complying with the requirements for a test function, and it
must have a parameter t of type *testing.T , which contains the Log() and
Fail() methods. Then, we simply test out a few examples of which we know
the outcome. We use ! here and use only failing examples. For example:
Look at line 5. We are using !Even() and testing it for 10, which would
definitely fail because 10 is an even number.
Look at line 9. We are using Even() and testing it for 7, which would
definitely fail because 7 is not an even number.
Look at line 16. We are using !Odd() and testing it for 11, which would
definitely fail because as 11 is an odd number.
Look at line 20. We are using Odd() and testing it for 10, which would
definitely fail because as 10 is not an odd number.
In a more real and complex case, you should also test the correct outcome and
fringe cases.
Because testing uses concrete cases of input and we can never test all cases
(most likely there is an infinite number), we must give some thought to the
test cases we are going to use.
We should at least include:
A normal case
Abnormal cases (wrong input like negative numbers or letters instead of
numbers, no input)
Boundary cases (if a parameter has to have a value in the interval 0-1000,
then check 0 and 1000)
We can now test our package with the command go test –v
package1/package1 from within the folder:
$GOPATH/src/package1.
Output:
=== RUN TestEven
--- PASS: TestEven (0.00 seconds)
=== RUN TestOdd
--- PASS: TestOdd (0.00 seconds)
PASS
ok even/even 0.217s
Because the test-functions we saw in Error-Handling and Panicking in a UserDefined Package do not invoke t.Log or t.Fail , this gives us as result: PASS.
In this simple example, everything works fine.
To see output in case of a failure, change the function TestEven to:
func TestEven(t *testing.T) {
if Even(10) {
t.Log("Everything OK: 10 is even, just a test to see failed output!")
t.Fail()
}
}
Even(10) now invokes t.Log and t.Fail , giving us as a result:
--- FAIL: even.TestEven (0.00 seconds)
Everything OK: 10 is even, just a test to see failed output!
FAIL
The file main.go gets built with: go build main.go .
To run the program, invoke main on the command-line, with the output:
>main
Is the
Is the
Is the
Is the
Is the
...
integer
integer
integer
integer
integer
0
1
2
3
4
even?
even?
even?
even?
even?
true
false
true
false
True
For another concrete example of testing and benchmarking, see Chapter 12
where benchmarking is performed on an example with Goroutines. In the
next lesson, you’ll learn a technique that makes the testing systematic.
Using Table-Driven Tests
This lesson explains a method to organize the testing of Go programs via tables.
When writing tests, it is good practice to use an array to collect the test-inputs
and the expected results together. Each entry in the table then becomes a
complete test case with inputs and expected results, and sometimes with
additional information such as a test name to make the test output more
informative.
The actual test simply iterates through all table entries and performs the
necessary tests for each entry. It could be abstracted in the following snippet:
var tests = []struct{ // Test table
in string
out string
}{
{"in1", "exp1"},
{"in2", "exp2"},
{"in3", "exp3"},
...
}
func TestFunction(t *testing.T) {
for i, tt := range tests {
s := FuncToBeTested(tt.in)
if s != tt.out {
t.Errorf("%d. %q => %q, wanted: %q", i, tt.in, s, tt.out)
}
}
}
If a lot of test functions have to be written that way, it could be useful to write
the actual test in a helper function verify :
func verify(t *testing.T, testnum int, testcase, input, output, expected s
tring) {
if input != output {
t.Errorf("%d. %s with input = %s: output %s != %s", testnum, testcase,
input,
output, expected)
}
}
so that TestFunction becomes:
func TestFunction(t *testing.T) {
for i, tt := range tests {
s := FuncToBeTested(tt.in)
verify(t, i, "FuncToBeTested: ", tt.in, s, tt.out)
}
}
Now that you’re familiar with error-handling and testing, let’s see how to
judge the performance of a Go program in the next lesson.
Investigating Performance
This lesson explains the evaluation criteria of the performance of a Go program using Go tools, which includes
time and memory utilization.
WE'LL COVER THE FOLLOWING
• Time and memory consumption
• Tuning with go test
• Tuning with pprof
• top
• web or web funcname
• list funcname or weblist funcname
Time and memory consumption #
A handy script to measure memory and consumption on a Unix-like systems is
xtime :
#!/bin/sh
/usr/bin/time -f '%Uu %Ss %er %MkB %C' "$@"
Used on the command-line as xtime progexec , where prog is a Go executable
program, it shows an output like:
56.63u 0.26s 56.92r 1642640kB progexec
giving the user time, system time, real time and maximum memory usage,
respectively.
Tuning with go test #
If the code uses the Go testing package’s benchmarking support, we could use
the go test standard -cpuprofile and -memprofile flags, causing a CPU or
memory usage profile to be written to the file specified.
go test -cpuprofile cpu.prof -memprofile mem.prof -bench
This compiles and executes the tests and benchmarks in the current folder. It
also writes a CPU profile to cpu.prof and a memory profile to mem.prof.
Tuning with pprof #
For a standalone program progexec , you have to enable profiling by
importing runtime/pprof ; this package writes the runtime profiling data in the
format expected by the pprof visualization tool. For CPU profiling, you have
to add a few lines of code:
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file"
)
func main() {
flag.Parse()
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal(err)
}
defer f.close()
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
...
}
This code defines a flag named cpuprofile , calls the Go flag library to parse
the command-line flags, and then, if the cpuprofile flag has been set on the
command line, starts CPU profiling redirected to that file ( os.Create makes a
file with the given name in which the profile will be written). The profiler
requires a final call to StopCPUProfile to flush any pending writes to the file
before the program exits; we use defer to make sure this happens as main
returns. Now, run the program with this flag: progexec cpuprofile=progexec.prof , and then you can visualize the profile with:
go tool pprof progexec.prof
When CPU profiling is enabled, the Go program stops about 100 times per
second and records a sample consisting of the program counters on the
currently executing Go routine’s stack. Some of the interesting commands of
this tool are:
top
web or web funcname
list funcname or weblist funcname
top #
This shows the 10 most heavily used functions during the execution, an output
like:
Total: 3099 samples
626 20.2% 20.2%
309 10.0% 30.2%
626 20.2% scanblock
2839 91.6% main.FindLoops
...
The 5th column is an indicator of how heavy that function is used.
web or web funcname #
This command writes a graph of the profile data in SVG format and opens it in
a web browser (there is also a gv command that writes PostScript and opens
it in Ghostview. For either command, you need graphviz installed). The
different functions are shown in rectangles (the more it is called the bigger),
and the arrows point in the direction of the function calls.
list funcname or weblist funcname #
This shows a listing of the code lines in funcname, within the 2nd column the
time spent in executing that line, so this gives a very good indication of what
code is heaviest in the execution. If you see that the function runtime.mallocgc
(which both allocates and runs periodic garbage collections) is heavily used,
then it is time for memory profiling. To find out why the garbage collector is
running so much, we have to find out which code is allocating memory.
For this, the following code has to be added at a judicious place:
var memprofile = flag.String("memprofile", "", "write memory profile to th
is file")
...
CallToFunctionWhichAllocatesLotsOfMemory()
if *memprofile != "" {
f, err := os.Create(*memprofile)
if err != nil {
log.Fatal(err)
}
pprof.WriteHeapProfile(f)
f.Close()
return
}
Now, run the program with this flag: progexec -memprofile=progexec.mprof
and visualize it with:
go tool pprof progexec.mprof
The same commands apply (top, list funcname etc), but now they measure
memory allocation in Mb. Here is a sample output of top:
Total: 118.3 MB
66.1 55.8% 55.8%
30.5 25.8% 81.6%
...
103.7 87.7% main.FindLoops
30.5 25.8% main.*LSG·NewLoop
The topmost functions use the most memory, as seen in the 1st column.
For web applications running on host: port, there is also a standard HTTP
interface to profiling data. The following command will start making HTTP
requests on the specified port:
pprof -http=[host]:[port] source
Open a browser using this http://host:port to visualize the interface data. More
info can be found here.
You now know how to write a less error-prone program, how to catch errors
in case a program panics, and how to test a program for performance. There
is a quiz on what you have learned in the next lesson.
Exercise: Deduce the Outputs
In this lesson, your concepts will be evaluated through a quiz.
Choose one possible correct answer.
1
What is the outcome of a program calling the recursive function f with
value 5?
func f(n int) {
defer func() { fmt.Println(n) }()
if n == 0 {
panic(0)
}
f(n-1)
}
COMPLETED 0%
1 of 3
We hope that you performed well in the quiz. That is it about errors and
making a program stable. In the next chapter, we’ll discuss parallelism and
concurrency.
Handling Concurrency and Parallelism with Go
This lesson discusses the problem of synchronization in parallelism and concurrency. It explains how Go
introduces goroutines to solve this problem.
WE'LL COVER THE FOLLOWING
• Introduction
• What are goroutines?
• The difference between concurrency and parallelism
Introduction #
As expected of a 21st century programming language, Go comes with built-in
support for communication between applications and support for concurrent
applications. These are programs that execute different pieces of code
simultaneously, possibly on different processors or computers. The basic
building blocks for structuring concurrent programs are goroutines and
channels. Their implementation requires support from the language, the
compiler, and the runtime. The garbage collection which Go provides is also
essential for easy concurrent programming. This support must be baked into
the language with specific types (chan), keywords (go, select) and constructs
(goroutines), and not just added as a separate library (like
java.util.concurrent for Java).
Do not communicate by sharing memory. Instead, share memory by
communicating. Communication forces coordination.
What are goroutines? #
An application is a process running on a machine; a process is an
independently executing entity that runs in its own address space in memory.
A process is composed of one or more operating system threads that are
simultaneously executing entities that share the same address space. Almost
all real programs are multithreaded, so as not to introduce wait times for the
user or the computer, or to be able to service many requests simultaneously
(like web servers), or to increase performance and throughput (e.g., by
executing code in parallel on different datasets). Such a concurrent
application can execute on one processor or core using several threads. Still, it
is only when the same application process executes at the same point in time
on several cores or processors that it is truly called parallelized.
Parallelism is the ability to make things run quickly by using multiple
processors simultaneously. So concurrent programs may or may not be
parallel.
Multithreaded applications are notoriously difficult to get right. The main
problem is the shared data in memory, which can be manipulated by the
different threads in a non-predictable manner, thereby, delivering sometimes
irreproducible and random results, called racing conditions.
Remarks: Do not use global variables or shared memory because they
make your code unsafe for running concurrently.
The solution to this problem lies in synchronizing the different threads and
locking the data so that only one thread at a time can change the data. Go has
facilities for locking in its standard library through the sync package, when
locks are needed in lower-level code. However, past experience in software
engineering has shown that this leads to complex, error-prone programming
and diminishing performance. However, this classic approach is clearly not
the way to go for modern multicore and multiprocessor programming: the
thread-per-connection model is not nearly efficient enough.
Go adheres to another, and in many cases, a better-suited paradigm, known as
Communicating Sequential Processes (CSP, invented by C.A.R. Hoare). It is
also known as the message passing-model, as applied in other languages such
as Erlang.
The parts of an application that run concurrently are called goroutines in Go,
they are in effect concurrently executing computations. There is no one-to-one
correspondence between a goroutine and an operating system thread: a
goroutine is mapped onto (multiplexed, executed by) one or more threads,
according to their availability. The goroutine-scheduler accomplishes this in
the Go runtime.
Goroutines run in the same address space. Therefore, access to shared
memory must be synchronized. This could be done via the sync package, but
this is highly discouraged. Instead, Go uses channels to synchronize
goroutines.
When a system call blocks a goroutine (e.g., waiting for I/O), other goroutines
continue to run on other threads. The design of goroutines hides many of the
complexities of thread creation and management. Goroutines are lightweight,
much lighter than a thread. They have a minimal footprint, using little
memory and resources: they are created with a 4KB memory stack-space on
the heap. Because they are cheap to create, a great number of them can be
started on the fly if necessary (in the order of hundreds of thousands in the
same address space). The amount of available memory limits the number of
goroutines. Furthermore, they use a segmented stack for dynamically growing
(or shrinking) their memory-usage; stack management is automatic. The
garbage collector does not manage the stacks. Instead, they are freed directly
when the goroutine exits.
Goroutines can run across multiple operating system threads, but more
crucially, they can also run within threads, letting you handle a myriad of
tasks with a relatively small memory footprint. Goroutines time-slice on OS
threads so any number of goroutines can be serviced by a smaller number of
OS threads. The Go runtime is smart enough to realize which of those
goroutines is blocking something, and it goes off to do something else if that is
so.
Two styles of concurrency exist: deterministic (well-defined ordering) and
non-deterministic (locking/mutual exclusion but undefined ordering). Go’s
goroutines, and channels promote deterministic concurrency (e.g., channels
with one sender, and one receiver), which is easier to reason about. We will
compare both approaches in a commonly occurring algorithm (the Workerproblem).
A goroutine is implemented as a function or method (this can also be an
anonymous or lambda function) and is called (invoked) with the keyword go .
This starts the function running concurrently with the current computation
but in the same address space and with its own stack. For example:
go sum(bigArray) // calculate sum in the background
The stack of a goroutine grows and shrinks as needed. There is no possibility
for stack overflow, and the programmer needn’t be concerned about stack
size. When the goroutine finishes, it exits silently, which means nothing is
returned to the function which started it. The main() function, which every
Go program must have, can also be seen as a goroutine, although it is not
started with go . Goroutines may be run during program initialization (in the
init() function).
An executing goroutine can stop itself by calling runtime.Goexit() , although
that’s rarely necessary. When one goroutine is very processor-intensive, you
can call runtime.Gosched() periodically in your computation loops. This yields
the processor, allowing other goroutines to run. It does not suspend the
current goroutine, so execution resumes automatically. Using Gosched() ,
computations are more evenly distributed, and communication is not starved.
The difference between concurrency and
parallelism #
Go’s concurrency primitives provide the basis for a good concurrency
program design: expressing program structure so as to represent
independently executing actions. So Go’s emphasis is not in the first place on
parallelism because concurrent programs may or may not be parallel.
However, it turns out that most often a well designed concurrent program
also has excellent performing parallel capabilities.
Processes
Processes
Execution time
Concurrency
Execution time
Parallelism
That’s all about the introduction to goroutines. Let’s begin learning the
implementation of goroutine in a program.
Implementing Goroutines
WE'LL COVER THE FOLLOWING
• Using GOMAXPROCS
• The main() waiting for the goroutines to nish
• Specifying the number of cores
• Goroutines and coroutines
In the current implementation of the runtime, Go does not parallelize code by
default. Only a single core or processor is dedicated to a Go-program,
regardless of how many goroutines are started in it. Therefore, these
goroutines are _running concurrently; they are not running in parallel, which
means only one goroutine is running at a time. This will probably change, but
until then, in order to let your program execute simultaneously with more
cores so that goroutines are really running in parallel; you have to use the
variable GOMAXPROCS . This tells the run-time how many goroutines can execute
in parallel. For example, if you have 8 processors, you can at most run 8
goroutines in parallel.
Using GOMAXPROCS #
The developer must set GOMAXPROCS to more than the default value 1 to allow
the run-time support to utilize more than one OS thread. All goroutines share
the same thread unless GOMAXPROCS is set to a value greater than 1. When
GOMAXPROCS is greater than 1, they run on a thread pool with that many
threads.
Suppose n is the number of processors or cores on the machine. If you set the
environment variable GOMAXPROCS >= n , or call runtime.GOMAXPROCS(n) , then
the goroutines are divided (distributed) among the n processors.
Note: To run in parallel means a goroutine must run on a different
thread, and each thread must run on a different processor.
More processors, however, don’t necessarily mean a linear improvement in
performance, mainly because more communication is needed: the messagepassing overhead increases. An experiential rule of thumb seems to be, that
for n cores, setting GOMAXPROCS to n-1 yields the best performance, and the
following rule should also be followed number of goroutines > 1 +
GOMAXPROCS > 1
If there is only one goroutine executing at a certain point in time, don’t set
GOMAXPROCS !
Here are some other observations from experiments. On a 32 core machine,
the best performance was reached with GOMAXPROCS=8 ; a higher number didn’t
improve performance in that benchmark. Very large values of GOMAXPROCS
degraded performance only slightly; using the “H” option to “top” showed
only 7 active threads, for GOMAXPROCS=100 .
Programs that perform concurrent computation should benefit from an
increase in GOMAXPROCS . In simple words, GOMAXPROCS is equal to the number of
(concurrent) threads. On a machine with more than 1 core, as many threads
as there are cores can run in parallel, so set:
runtime.GOMAXPROCS(runtime.NumCPU()) , where NUMCPU is the number of logical
CPUs on the local machine.
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("In main()")
go longWait()
go shortWait()
fmt.Println("About to sleep in main()")
time.Sleep(10 * 1e9) // sleep works with a Duration in nanoseconds (ns) !
fmt.Println("At the end of main()")
}
func longWait() {
fmt.Println("Beginning longWait()")
time.Sleep(5 * 1e9) // sleep for 5 seconds
fmt.Println("End of longWait()")
}
func shortWait() {
fmt.Println("Beginning shortWait()")
time.Sleep(2 * 1e9) // sleep for 2 seconds
fmt.Println("End of shortWait()")
}
Goroutine
At each step, the above program prints statements to indicate which part of
the execution phase the program is in. At line 9, a goroutine starts the
function longWait (defined from line 16 to line 20), which pauses its
execution for 5s at line 18. Immediately after longWait starts, at line 10, a
goroutine starts the function shortWait (defined from line 22 to line 26),
which pauses its execution for 2s at line 24. Then, the main() goroutine
pauses itself for 10s at line 12.
The 3 functions main() , longWait() and shortWait() are started in this order
as independent processing units, and then work concurrently. Each function
outputs a message at its beginning and at the end of its processing. To
simulate their processing times, we use the Sleep function from the time
package. Sleep() pauses the processing of the function or goroutine for the
indicated amount of time, which is given in nanoseconds (ns, the notation 1e9
represents 1 times 10 to the 9th power, e = exponent). They print their
messages in the order we expect, always the same. But, we see clearly that
they work simultaneously, in parallel. We let the main() pause for 10s, so we
are sure that it will terminate after the two goroutines. If not (say if we let
main() stop for only 4s), main() stops the execution earlier and longWait()
doesn’t get the chance to complete. If we do not wait in main() , the program
stops and the goroutines die with it.
When the function main() returns, the program exits. It does not wait for
other (non-main) goroutines to complete. That is the reason why in serverprograms where each request is handled by a response started as a goroutine,
the server() function, which starts the goroutines, must be kept alive. This is
usually done by starting it as an infinite loop.
Moreover, goroutines are independent units of execution, and when a
number of them start one after the other, you cannot depend on when a
goroutine will actually be started. The logic of your code must be independent
of the order in which goroutines are invoked.
To contrast this with one thread, successive execution, remove the go
keywords, and let the program run again. Now, the output is:
In main()
Beginning longWait()
End of longWait()
Beginning shortWait()
End of shortWait()
About to sleep in main()
At the end of main() // after 17 s
A more useful example of using goroutines could be to search for an item in a
very large array. Divide the array in a number of non-overlapping slices, and
start a goroutine on each slice with the search algorithm. In this way, a
number of parallel threads can be used for the search task, and the overall
search time will certainly be decreased (divided by the number of goroutines).
If f(x) is a function, you can launch n goroutines with it through a for loop,
like this:
// launch n goroutines f
for i := 0; i < n; i++ {
go f(...)
}
The main() waiting for the goroutines to nish #
This can be accomplished with a coordinating channel: the semaphore
pattern, which we’ll study later in this chapter. Another solution is to use the
type sync.WaitGroup , which exactly serves this purpose: a WaitGroup waits for
a collection of goroutines to finish. The main goroutine calls Add on the
WaitGroup object to set the number of goroutines to wait for. Every goroutine
takes a pointer to the WaitGroup value as a parameter when it is invoked.
When each of the goroutines runs, it calls Done when finished. The main()
e eac o t e go out es u s, t ca s o e
e
s ed.
e a ()
calls the Wait() method to block itself until all the goroutines have finished.
The following code snippet illustrates this:
package main
import (
"fmt"
"sync"
)
func HeavyFunction1(wg *sync.WaitGroup) {
defer wg.Done()
// Do a lot of stuff
}
func HeavyFunction2(wg *sync.WaitGroup) {
defer wg.Done()
// Do a lot of stuff
}
func main() {
wg := new(sync.WaitGroup)
wg.Add(2)
go HeavyFunction1(wg)
go HeavyFunction2(wg)
wg.Wait()
fmt.Printf("All Finished!")
}
Waitgroup
To get a synchronized behavior that is not dependent on time, a function
launched in a goroutine can register to a Waitgroup instance, here wg , as one
of its parameters. At line 18, we construct the WaitGroup wg , and at line 19,
we say that it has to wait for two goroutines.
Line 20 and line 21 start our goroutines, passing wg as a parameter. Both
goroutines will signal wg just before their end of execution with wg.Done()
(see line 8 and line 13). Then, the WaitGroup waits in the main() function at
line 22, until it has received as many Done signals as there were goroutines
registered. Only after that, processing continues with line 23.
Specifying the number of cores #
Use the flags package, as in:
var numCores = flag.Int("n", 2, "number of CPU cores to use")
In main() :
flag.Parse()
runtime.GOMAXPROCS(*numCores)
Goroutines and coroutines #
Other languages like C#, Lua and Python have a concept of coroutines. The
name indicates that there is a similarity with goroutines, but there are 2
differences:
Goroutines imply parallelism (they can be deployed in parallel),
coroutines, in general, do not.
Goroutines communicate via channels, whereas, coroutines communicate
via yield and resume operations.
In general, goroutines are much more powerful than coroutines, and it is easy
to port coroutine logic to goroutines.
You may have noticed that the goroutines in this lesson are not
communicating with each other. See the next lesson to see how they can
communicate.
Introduction to Channels
This lesson explains how goroutines can interact with one another through channels.
WE'LL COVER THE FOLLOWING
• Concept
• Closing a channel
• Communication operator <• To a channel (sending)
• From a channel (receiving)
Concept #
Previously, the goroutines executed independently; they did not communicate.
Of course, to be more useful, they have to communicate by sending and
receiving information between them and, thereby, coordinating (synchronizing)
their efforts. Goroutines could communicate by using shared variables, but
this is highly discouraged because this way of working introduces all the
difficulties with shared memory in multi-threading.
Instead, Go has a special type, the channel, which is like a conduit (a pipe)
through which you can send typed values and which takes care of
communication between goroutines, avoiding all the pitfalls of shared
memory. The very act of communication through a channel guarantees
synchronization; there is no need for explicit synchronization through locking
anything.
Data is passed around on channels: only one goroutine has access to a data
item at any given time: so data races cannot occur, by design. The ownership of
the data (that is the ability to read and write it) is passed around. A useful
analogy is to compare a channel with a conveyor belt in a factory. One
machine (the producer goroutine) puts items onto the belt, and another
machine (the consumer goroutine) takes them off for packaging.
Channels serve the dual purpose of communication, i.e., the exchange of value
with synchronization, guaranteeing that two calculations (goroutines) are in a
known state at any time.
goroutine 1
goroutine 2
time
channel
Channels and goroutines
In the general format, the declaration of a channel is:
var identifier chan datatype
The value of an uninitialized channel is nil.
A channel can only transmit data-items of one datatype, e.g. chan int or chan
string , but a channel can be made for any type, also custom types and for the
empty interface{}. It is even possible (and sometimes useful) to create a
channel of channels.
A channel is, in fact, a typed message queue: data can be transmitted through
it. It is a First In First Out (FIFO) structure, and so it preserves the order of the
items that are sent into it (for those who are familiar with it, a channel can be
compared to a two-way pipe in Unix shells).
A channel is also a reference type, so we have to use the make() function to
allocate memory for it. Here is a declaration of a channel ch1 of strings,
followed by its creation (instantiation):
var ch1 chan string
ch1 = make(chan string)
But of course, this can be shortened to:
ch1 := make(chan string)
Here, we construct a channel of channels of int:
chanOfChans := make(chan chan int)
Or a channel of functions:
funcChan := make(chan func())
Channels are first-class objects. They can be stored in variables, passed as
arguments to functions, returned from functions, and sent themselves over
channels. Moreover, they are typed, allowing the type system to catch
programming errors like trying to send a pointer over a channel of integers.
Closing a channel #
Once we are done using a channel, it’s a good practice to close it. Let’s suppose
we have a channel ch , to close it we can do:
close(ch)
Communication operator <- #
This operator represents the transmission of data very intuitively, i.e.,
information flows in the direction of the arrow.
To a channel (sending) #
The operation ch <- int1 means that the variable int1 is sent through the
channel ch (binary operator, infix means send).
From a channel (receiving) #
The operation int2 = <- ch means that variable int2 receives data (gets a
new value) from the channel ch (unary prefix operator, prefix means
receive). This supposes int2 is already declared; if not, this can be written as:
int2 := <- ch . Another form <- ch can on itself be used to take the (next)
value from the channel. This value is effectively discarded, but it can be tested
upon. That’s why the following is legal code:
if <-ch != 1000 {
...
}
The same operator <- is used for sending and receiving, but Go figures out
what to do depending on the operands. Although not necessary, for
readability the channel name usually starts with ch or contains chan .
The channel send and receive operations are atomic which means they always
complete without interruption.
The use of the communication operator is illustrated in the following
example:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go sendData(ch) // calling goroutine to send the data
go getData(ch) // calling goroutine to receive the data
time.Sleep(1e9)
}
func
ch
ch
ch
ch
ch
}
sendData(ch chan string) { // sending data to ch channel
<- "Washington"
<- "Tripoli"
<- "London"
<- "Beijing"
<- "Tokyo"
func getData(ch chan string) {
var input string
for {
input = <-ch // receiving data sent to ch channel
fmt.Printf("%s ", input)
}
close(ch) // closed the channel
}
Goroutines' communication
In main() at line 8, a channel ch of strings is made. Then, two goroutines are
started: sendData() (at line 9) sends 5 strings over channel ch (see
implementation from line 14 to line 20), and getData() (at line 10) receives
them one by one in order in the string input (line 25) and prints what is
received (line 26). If two goroutines have to communicate, you must give
them both the same channel as a parameter for doing that. Experiment what
happens when you comment out time.Sleep(1e9) . Also, remember to
comment out the import of the time package or your code won’t compile.
Here, we see that synchronization between the goroutines becomes
important:
The main() waits for 1 second so that both goroutines can come to
completion. If this is not allowed, sendData() doesn’t have the chance to
produce its output.
getData() works with an infinite for-loop. This comes to an end when
sendData() has finished, and ch is empty.
If we remove one or all go keywords, the program doesn’t work
anymore, and the Go runtime throws a panic:
---- Error run <path> with code Crashed Fatal error: all goroutines are
asleep - deadlock!
Why does this occur? The runtime can detect that all goroutines (or perhaps
only one in this case) are waiting for something (to write to a channel or be
able to read from a channel), which means the program can’t possibly
proceed. It is a form of a deadlock, and the runtime can detect it for us.
Remark: Don’t use print statements to indicate the order of sending to
and receiving from a channel. This could be out of order with what
happens due to the time lag between the print statement and the actual
channel sending and receiving.
Channels make communication possible between goroutines. To communicate
in the best possible manner, we should know the type of connection is
required. The next lesson focuses on types of communication between
goroutines.
Synchronous and Asynchronous Communication
This lesson describes synchronous and asynchronous communication and how channels are designed to handle
both.
WE'LL COVER THE FOLLOWING
• Blocking of goroutines
• Synchronization between one (or more) channel(s)
• Asynchronous channels
Blocking of goroutines #
By default, communication is synchronous, and unbuffered, which means the
send operation does not complete until there is a receiver to accept the value.
One can think of an unbuffered channel as if there is no space in the channel
for data. There must be a receiver ready to receive data from the channel, and
then the sender can hand it over directly to the receiver. So, channel
send/receive operations block until the other side is ready:
A send operation on a channel (and the goroutine or function that contains
it) blocks until a receiver is available for the same channel ch . If there’s
no recipient for the value on ch , no other value can be put in the
channel, which means no new value can be sent in ch when the channel
is not empty. So, the send operation will wait until ch becomes available
again. This is the case when the channel-value is received (can be put in a
variable).
A receive operation for a channel blocks (and the goroutine or function that
contains it) until a sender is available for the same channel. If there is no
value in the channel, the receiver blocks. Although this seems a severe
restriction, this offers a simple form of synchronizing and which works
well in most practical situations.
This is illustrated in the following program, where the goroutine pump sends
integers in an infinite loop on the channel. However, because there is no
receiver, the only output is the number 0.
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan int)
go pump(ch1) // pump hangs
fmt.Println(<-ch1) // prints only 0
time.Sleep(1e9)
}
func pump(ch chan int) {
for i:= 0; ; i++ {
ch <- i
}
}
Channel Block
A channel for ints ch1 is made at line 8. At line 9, a goroutine pump is started,
passing ch1 as a parameter. Pump is an infinite loop (from line 15 to line 17),
trying to push numbers on the channel, but the channel only can accept one
number. This is printed out at line 10, after which, the program exits.
The pump() function, which supplies the values for the channel, is sometimes
called a generator. To unblock the channel, define the function suck , which
reads from the channel in an infinite loop. See the following snippet:
func suck(ch chan int) {
for {
fmt.Println(<-ch)
}
}
and start this as a goroutine in main() :
go pump(ch1)
go suck(ch1)
time.Sleep(1e9)
Give the program 1s to run. Now tens of thousands of integers appear on
output.
Synchronization between one (or more)
channel(s) #
Communication is, therefore, a form of synchronization. Two goroutines
exchanging data through a channel synchronize at the moment of
communication. Unbuffered channels make a perfect tool for synchronizing
multiple goroutines. It is possible that the two sides block, waiting for each
other, creating what is called a deadlock situation. The Go runtime will detect
this and panic, stopping the program with this message: all goroutines are
asleep - deadlock! A deadlock is always caused by bad program design.
Deadlocks, like infinite loops, shouldn’t happen.
We see that channel operations on unbuffered channels can block. The way to
avoid this is to design the program such that blocking does not occur
(preferably), or by using buffered channels.
Asynchronous channels #
An unbuffered channel can only contain 1 item, and for that reason, it is
sometimes too restrictive. We can provide a buffer in the channel, whose
capacity gets set in an extended make command, like this:
buf := 100
ch1 := make(chan string, buf)
The buf is the number of elements (in this example: strings) the channel can
hold.
Sending to a buffered channel will not block unless the buffer is full (the
capacity is completely used), and reading from a buffered channel will not
block unless the buffer is empty.
The buffer capacity does not belong to the type, so it is possible to assign
channels with different capacities to each other, as long as they have the same
element type. The built-in cap function on a channel returns this buffer
capacity.
If the capacity is greater than 0, the channel is asynchronous, which means
the communication operations succeed without blocking if the buffer is not
full (sends) or not empty (receives). Elements are received in the order they
are sent. If the capacity is zero or absent, the communication succeeds only
when both a sender and receiver are ready.
To synthesize:
ch := make(chan type, value)
value == 0
synchronous, unbuffered (blocking)
value > 0
asynchronous, buffered (non-blocking) up to
value elements
If you use buffers in the channels, your program will react better to sudden
increases in the number of requests. It will react more elastically, and it will
be more scalable. Despite this, design your algorithm in the first place with
unbuffered channels, and only introduce buffering when the former is
problematic.
Now that you are familiar with the basics of goroutines and channels, the next
lesson brings you a challenge to solve.
Challenge: Demonstrate the Blocking Nature
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Output
Problem statement #
Demonstrate the blocking nature of channels by making a channel and
starting a goroutine that receives the value from the channel but after waiting
for 5s. Then, put a value (of your own choice) on the channel. Print messages
at the different stages and observe the output.
Output #
sending
received
sent
The messages sending, received, and sent are printed at different stages.
Hint: Don’t forget to import time.
Try to attempt the challenge below. Good Luck!
package main
func main() {
}
Blocking Channel
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Demonstrate the Blocking Nature
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import "fmt"
import "time"
func main() {
c := make(chan int)
go func() {
time.Sleep(5 * 1e9)
x := <- c // value recieved from channel
fmt.Println("received", x)
}()
fmt.Println("sending", 10)
c <- 10 // putting 10 on the channel
fmt.Println("sent", 10)
}
Blocking Channel
At line 6, we make a channel c for integers. Then, from line 7 to line 11, we
start a goroutine with an anonymous function that we call at line 11. Main()
continues immediately. It prints sending and puts the integer 10 on the
channel. Then main() waits until the value is taken from the channel. This is
done in the goroutine at line 9, but only after 5s have passed. Then, the
goroutine prints received, and main() prints sent.
That is it for the solution. In the next lesson, we’ll discuss the semaphore
pattern, which was mentioned earlier.
Semaphore Pattern
This lesson explains an effective synchronization method that lets goroutines nish their execution rst, also
known as the semaphore pattern.
WE'LL COVER THE FOLLOWING
• Introduction
• Explanation
• Implementing a parallel for-loop
• Implementing a semaphore using a buffered channel
Introduction #
We can also use a channel for synchronization purposes, effectively using it as
what is called a semaphore in traditional computing. To put it differently, to
discover when a process (in a goroutine) is done, pass it a channel with which
it can signal it is ready. A common idiom used to let the main program block
indefinitely while other goroutines run is to place select {} as the last
statement in the main function. This can also be done by using a channel to let
the main program wait until the goroutine(s) complete(s), the so-called
semaphore pattern.
Explanation #
The goroutine compute signals its completion by putting a value on the
channel ch , and the main routine waits on <-ch until this value gets through.
On this channel, we would expect to get a result back, like in:
func
ch
}
func
ch
compute(ch chan int) {
<- someComputation() // when it completes, signal on the channel.
main() {
:= make(chan int) // allocate a channel.
go compute(ch) // start something in a goroutine
doSomethingElseForAWhile()
result := <-ch
}
But the signal could also be something else, not connected to the result, like in
this lambda function goroutine:
ch := make(chan int)
go func() {
// doSomething
ch <- 1 // send a signal; value does not matter.
}()
doSomethingElseForAWhile()
<-ch // wait for goroutine to finish; discard sent value.
Or in this snippet, where we wait for 2 sort goroutines, with each sort() sorts
a part of a slice s . To complete:
done := make(chan bool)
// doSort is a lambda function, so a closure which knows the channel done:
doSort := func(s []int) {
sort(s)
done <- true
}
i := pivot(s)
go doSort(s[:i])
go doSort(s[i:])
<-done
<-done
In the following code snippet, we have a full-blown semaphore pattern where
N computations doSomething() over a slice of float64’s are done in parallel,
and a channel sem of exactly the same length (and containing items of type
empty interface) is signaled (by putting a value on it) when each one of the
computations is finished. To wait for all of the goroutines to finish, just make a
receiving range-loop over the channel sem :
type Empty interface {}
var empty Empty
...
data := make([]float64, N)
res := make([]float64, N)
sem := make(chan Empty, N) // semaphore
...
for i, xi := range data {
go func (i int, xi float64) {
res[i] = doSomething(i,xi)
sem <- empty
} (i, xi)
}
// wait for goroutines to finish
for i := 0; i < N; i++ { <-sem }
Notice the use of the closure: the current i and xi are passed to the closure
as parameters, masking the i and xi variables from the outer for-loop. This
allows each goroutine to have its own copy of i and xi ; otherwise, the next
iteration of the for-loop would update i and xi in all goroutines. On the
other hand, the res slice is not passed to the closure, since each goroutine
does not need a separate copy of it. The res slice is part of the closure’s
environment but is not a parameter.
Implementing a parallel for-loop #
This is just what we did in the previous code-snippet: each iteration in the forloop is done in parallel:
for i, v := range data {
go func (i int, v float64) {
doSomething(i, v)
...
} (i, v)
}
Computing the iterations of a for-loop in parallel could potentially give huge
performance gains, but this is only possible when all of the iterations are
completely independent of each other. Some languages like Fortress or other
parallel frameworks implement this as a separate construct. However, in Go,
these are easily implemented with goroutines.
Implementing a semaphore using a buffered channel #
Semaphores are a very general synchronization mechanism that can be used
to implement mutexes (exclusive locks), limit access to multiple resources,
solve the readers-writers problem, and so on.
Go’s sync package contains a semaphore implementation with the Mutex type,
but they can be also emulated easily using a buffered channel:
The capacity of the buffered channel is the number of resources we wish
to synchronize.
The length (number of elements currently stored) of the channel is the
number of resources currently being used.
The capacity minus the length of the channel is the number of free
resources (the integer value of traditional semaphores).
We don’t care about what is stored in the channel, only its length; therefore,
we start by making a channel that has variable length but 0 size (in bytes):
type Empty interface {}
type semaphore chan Empty
We can then initialize a semaphore with an integer value, which encodes the
number of available resources N.
sem = make(semaphore, N)
Now, our semaphore operations are straightforward:
// acquire n resources
func (s semaphore) P(n int) {
e := new(Empty)
for i := 0; i < n; i++ {
s <- e
}
}
// release n resources
func (s semaphore) V(n int) {
for i := 0; i < n; i++ {
<-s
}
}
This can for example be used to implement a mutex:
/* mutexes */
func (s semaphore) Lock() {
s.P(1)
}
func (s semaphore) Unlock() {
s.V(1)
}
/* signal-wait */
func (s semaphore) Wait(n int) {
s.P(n)
}
func (s semaphore) Signal() {
s.V(n)
}
Now, that you’re familiar with the semaphore pattern, the next lesson
explains some more patterns.
Channel Factory and Producer-Consumer Pattern
This lesson provides detailed concepts on the channel factory and producer-consumer pattern.
WE'LL COVER THE FOLLOWING
• Channel factory pattern
• For-range applied to channels
• Producer-consumer pattern
Channel factory pattern #
Another common pattern in this style of programming is that, instead of
passing a channel as a parameter to a goroutine, the function makes the
channel and returns it (so it plays the role of a factory). Inside the function, a
lambda function is called a goroutine. The following code is an
implementation of this pattern:
package main
import (
"fmt"
"time"
)
func main() {
stream := pump()
go suck(stream)
// the above 2 lines can be shortened to: go suck( pump() )
time.Sleep(1e9)
}
func pump() chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
}()
return ch
}
func suck(ch chan int) {
u c suc (c c a
t) {
for {
fmt.Println(<-ch)
}
}
Channel Factory Pattern
At line 8, the main() goroutine starts the function pump() . As we see from line
14, pump() returns a channel of ints, which is received in the stream variable.
Look at the header of pump() at line 14. It makes a local channel ch at line 15
and then starts a goroutine in an anonymous function at line 16. This function
executes an infinite for-loop at line 17, putting successive integers onto the
channel. While this has started, ch is returned at line 21, and received in
variable stream .
At line 9, a second goroutine is started, executing the suck() function. Look at
the header of suck() at line 24. It takes ch as a parameter. This gets a value
from the channel and prints it out. At line 11, main() waits 1 second to allow
the display of the initial output. Then, the program exits, stopping all
goroutines.
For-range applied to channels #
The range clause on for loops accepts a channel ch as an operand, in which
case the for loops over the values received from the channel, like this:
for v := range ch {
fmt.Printf("The value is %v\n",v)
}
It reads from the given channel ch until the channel is closed, and then the
code following the for continues to execute. Obviously, another goroutine
must be writing to ch (otherwise the execution blocks in the for-loop) and
must close ch when it is done writing. The function suck() can apply this and
also launch this action in a goroutine. Then, the former program becomes:
package main
import (
"fmt"
"time"
)
func main() {
suck(pump())
time.Sleep(1e9)
}
func pump() chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
}()
return ch
}
func suck(ch chan int) {
go func() {
for v := range ch {
fmt.Println(v)
}
}()
}
For range on channels
The logic of this program is nearly the same as the previous code. The suck()
function calls pump() at line 8. This is possible because suck expects a
channel of ints as a parameter, and pump returns a channel of ints. Now, we
can make the design much more symmetrical: both pump() and suck() start a
goroutine. The pump() (see implementation from line 12 to line 20) is
identical to the previous version. Now, the suck() starts an anonymous
function in a goroutine. This function iterates over the channel ch (line 24),
getting, reading, and printing out each successive value (line 25).
Producer-consumer pattern #
Suppose we have a Produce() function, which delivers the values needed by a
Consume() function. Both functions could be run as a separate goroutine,
Produce putting the values on a channel which is read by Consume . The whole
process could take place in an infinite loop:
for {
Consume(Produce())
}
Now that you’re familiar with the different patterns, the next lesson brings
you a challenge to solve.
Challenge: Summing the Integers
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Write a program that starts a goroutine sum to perform a sum of 2 integers
and then waits on the result to print it.
Try to attempt the challenge below. Good Luck!
package main
func sum(x, y int, c chan int) {
}
func main() {
}
Summing Integers
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Summing the Integers
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"fmt"
)
func sum(x, y int, c chan int) {
c <- x + y
}
func main() {
c := make(chan int)
go sum(12, 13, c)
fmt.Println(<-c) // 25
}
Summing Integers
The function sum expects the two integers, and a channel to put the resulting
sum on (line 6). So, at line 11 in main() , we make a channel c . At line 12, we
call sum with two integers and this channel as parameters. At line 13, main()
tries to get a value from channel c to print it. This line blocks, until the
goroutine in which sum operates, puts its return value on the channel at line
7. Only then, it can continue. That’s why we made main() wait until the
completion of sum() .
That is it about the solution. In the next lesson, we’ll discuss the semaphore
pattern, which was mentioned earlier.
Channel Directionality
This lesson discusses different patterns for the implementation of channels for communication between
goroutines.
WE'LL COVER THE FOLLOWING
• Channel iterator pattern
• Pipe and lter pattern
• Version I
• Version II
A channel type may be annotated to specify that it may only send or only
receive data:
var send_only chan<- int // data can only be sent (written) to the channel
var recv_only <-chan int // data can only be received (read) from the chan
nel
Receive-only channels ( <-chan T ) cannot be closed, because closing a channel
is intended as a way for a sender goroutine to signal that no more values will
be sent to the channel. Therefore, it has no meaning for receive-only
channels. All channels are created bidirectional, but we can assign them to
directional channel variables, like in this code snippet:
var c = make(chan int) // bidirectional
go source(c)
go sink(c)
func source(ch chan<- int) {
for { ch <- 1 } // sending data to ch channel
}
func sink(ch <-chan int) {
for { <-ch } // receiving data from ch channel
}
Channel iterator pattern #
This pattern can be applied in the common case where we have to populate a
channel with the items of a container type, which contains index-addressable
field items. For this, we can define a method Iter() on the content type which
returns receive-only channel items ( Iter() is a channel factory), as follows:
func (c *container) Iter () <-chan items {
ch := make(chan item)
go func () {
for i := 0; i < c.Len(); i++ { // or use a for-range loop
ch <- c.items[i]
}
} ()
return ch
}
Inside the goroutine, a for-loop iterates over the elements in the container c
(for tree or graph algorithms, this simple for-loop could be replaced with a
depth-first search). The code, which calls this method can then iterate over the
container, like:
for x := range container.Iter() { ... }
which can run in its own goroutine. Then, the above iterator employs a
channel and two goroutines (which may run in separate threads).
If the program terminates before the goroutine is done writing values to the
channel, then that goroutine will not be garbage collected; this is by design.
This seems like the wrong behavior, but channels are for thread-safe
communication. In that context, a hung goroutine trying to write to a channel
that nobody will ever read from is probably a bug and not something you’d
like to be silently garbage-collected.
Pipe and lter pattern #
A more concrete example would be a goroutine processChannel , which
processes what it receives from an input channel and sends this to an output
channel:
sendChan := make(chan int)
receiveChan := make(chan string)
go processChannel(sendChan, receiveChan)
func processChannel(in <-chan int, out chan<- string) {
for inValue := range in {
result:= ... // processing inValue
out <- result
}
}
By using the directionality notation, we make sure that the goroutine will not
perform unallowed channel operations.
Here is an excellent and more concrete example taken from the Go Tutorial,
which prints the prime numbers at its output, using filters (sieves) as its
algorithm. Each prime gets its filter, like in this schema:
2
3
3
4
5
5
5
6
7
2
7
3
7
5
7
7
11
8
9
9
10
11
11
11
11
The Sieve-Prime Algorithm
Version I #
package main
import "fmt"
// Send the sequence 2, 3, 4, ... to channel ch.
func generate(ch chan int) {
11
for i := 2; ; i++ {
ch <- i // Send i to channel ch.
}
}
// Copy the values from channel in to channel out,
// removing those divisible by prime.
func filter(in, out chan int, prime int) {
for {
i := <-in // Receive value of new variable i from in.
if i%prime != 0 {
out <- i // Send i to channel out.
}
}
}
// The prime sieve
func main() {
ch := make(chan int) // Create a new channel.
go generate(ch) // Start generate() as a goroutine.
for {
prime := <-ch
fmt.Print(prime, " ")
ch1 := make(chan int)
go filter(ch, ch1, prime)
ch = ch1
}
}
Sieve Algorithm-Version 1
Note: The Execution Timed Out! is the expected behavior, so don’t panic.
Look at the main function at line 25. It starts generate as its own goroutine. It
is implemented at the top. Look at its header at line 5. It receives a channel
ch and starts sending integers on it in an infinite loop (see line 7). Let’s see
the flow of the program in detail:
In main() starts an infinite for-loop (from line 26 to line 32). It takes a
value from the channel named prime (see line 27), and prints it at line
28.
A new channel ch1 of integers is made at line 29. The filter function is
started at line 30 as its own goroutine, passing the two channels
(becoming resp. in and out ), and prime as a parameter.
Then, in an infinite for loop (from line 14 to line 19) in filter function,
an integer i is taken from in (ch) , only when it is not divisible by the
prime (line 16) and is copied to out (ch1) .
Then, channel ch is replaced by ch1 at line 31.
Thus, following the schema outlined above, the net result is that only prime
numbers remain in ch .
Version II #
In the second version, the pipe and filter pattern described above is applied.
The functions sieve , generate , and filter are factories. They make a
channel and return it, and they use lambda functions as goroutines. The main
routine is now very short and clear. It calls sieve() , which returns a channel
containing the primes, and then the channel is printed out via fmt.Println(<primes) .
package main
import "fmt"
// Send the sequence 2, 3, 4, ... to returned channel
func generate() chan int {
ch := make(chan int)
go func() {
for i := 2; ; i++ {
ch <- i
}
}()
return ch
}
// Filter out input values divisible by prime, send rest to returned channel
func filter(in chan int, prime int) chan int {
out := make(chan int)
go func() {
for {
if i := <-in; i%prime != 0 {
out <- i
}
}
}()
return out
}
func sieve() chan int {
out := make(chan int)
go func() {
ch := generate()
for {
prime := <-ch
ch = filter(ch, prime)
out <- prime
}
}()
return out
}
func main() {
primes := sieve()
for {
fmt.Println(<-primes)
}
}
Sieve Algorithm-Version 2
Note: The Execution Timed Out! is the expected behavior, so don’t panic.
Here, we refactor the code of version I. The main() calls function sieve() at
line 42, which produces the prime numbers as a channel of ints called primes .
Then in an infinite for loop (from line 43 to line 45), the values are read from
the channel and printed out.
Now, look at the header of sieve() at line 28. It constructs its return channel at
line 29 and starts at line 30 a goroutine, which executes an anonymous
function. In it, we call the function generate() at line 31. This function is
defined (from line 5 to line 13) as in Version I; this puts all integers onto its
output channel, which is captured in ch .
Then, in an infinite for loop, we take a value prime from ch , and pass it
together with ch to function filter() at line 34. The filter() works like in
Version I, making an out channel at line 17, then starting at line 18 a
goroutine, which executes an anonymous function. In it, enclosed in an
infinite for loop, we take a value from the in channel (which was ch ). Only
when it is a prime, we pass it to the out channel at line 21, which is returned
at line 25.
Now that you’re familiar with channel directionality, the next lesson brings
the information to synchronize the channels between goroutines.
Synchronization of goroutines
This lesson shows how closing or blocking a pattern, brings effective integration between goroutines.
WE'LL COVER THE FOLLOWING
• Closing a channel
• Signaling
• Detection
• Blocking and the producer-consumer pattern
Channels can be closed explicitly. However, they are not like files. You don’t
usually need to close them. Closing a channel is only necessary when the
receiver must be told that there are no more values coming. Only the sender
should close a channel, never the receiver.
Closing a channel #
The question is, how can we signal when sendData() is done with the channel,
and how can getData() detect that the channel whether closed or blocked?
Signaling #
This is done with the function close(ch) . This marks the channel as unable to
accept more values through a send operation <- . Sending to or closing a
closed channel causes a run-time panic. It is good practice to do this with a
defer statement immediately after the making of the channel (when it is
appropriate in the given situation):
ch := make(chan float64)
defer close(ch)
Detection #
This is done with the comma, ok operator. This tests whether the channel is
s s do e
t t e co a, o ope ato .
s tests
et e t e c a
e s
closed and then returns true, otherwise false. How can we test that we can
receive without blocking (or that channel ch is not closed)?
v, ok := <-ch // ok is true if v received a value
Often, this is used together with an if-statement:
if v, ok := <-ch; ok {
...
}
Or, when the receiving happens in a for loop, use break when ch is closed or
blocked:
v, ok := <-ch
if !ok {
break
}
// process(v)
We can trigger the behavior of a non-blocking send by writing: _ = ch <- v
because the blank identifier takes whatever is sent on ch .
package main
import "fmt"
func main() {
ch := make(chan string)
go sendData(ch)
getData(ch)
}
func sendData(ch chan string) {
ch <- "Washington"
ch <- "Tripoli"
ch <- "London"
ch <- "Beijing"
ch <- "Tokyo"
close(ch)
}
func getData(ch chan string) {
for {
input, open := <-ch
if !open {
break
}
fmt.Printf("%s ", input)
}
}
Synchronization
In the code above, at line 5, we make a channel ch of strings. Line 6 starts a
goroutine with function sendData() , passing ch as a parameter. From line 11
to line 15, five values are put on the channel, which is then closed. At line 7, in
the main() routine, a function getData() is called, which is defined from line
19 to line 27. This is, in fact, an infinite for-loop, which takes a value from the
channel at line 21 as long as it is open and prints it out. If closed, we break
from the loop at line 23.
Here is what is changed in the code:
Only sendData() is now a goroutine (see line 6), and getData() runs in
the same thread as main() (see line 7).
At the end of the function sendData() , the channel is closed (see line 16).
In the for-loop in getData() , after every receive the channel is tested with
if !open (see line 22).
It is even better to practice reading the channel with a for-range statement
because this will automatically detect when the channel is closed:
for input := range ch {
Process(input)
}
Blocking and the producer-consumer pattern #
In this pattern, the relationship between the two goroutines is such that one is
usually blocking the other. If the program runs on a multicore machine; only
one processor will be employed most of the time. This can be improved by
using a channel with a buffer size, greater than 0. For example, with a buffer
size of 100, the iterator can produce at least 100 items from the container
before blocking. If the consumer goroutine is running on a separate
processor, it is possible that neither goroutine will ever block.
Since the number of items in the container is generally known, it makes sense
to use a channel with enough capacity to hold all the items. This way, the
iterator will never block, though the consumer goroutine still might. However,
this effectively doubles the amount of memory required to iterate over any
given container, so channel capacity should be limited to some maximum
number. Timing or benchmarking your code will help you find the buffer
capacity for minimal memory usage and optimal performance.
Lately, we have been talking about synchronization and communication.
However, we still have not discussed how to switch from one goroutine to
another. Study the next lesson to see how to do that!
Switching between goroutines
This lesson explains how to transfer control between different goroutines via a channel.
WE'LL COVER THE FOLLOWING
• Waiting between a number of goroutines
• Nulling idiom
• Server backend pattern
Waiting between a number of goroutines #
Getting the values out of different, concurrently executing goroutines can be
accomplished with the select keyword, which closely resembles the switch
control statement, and is sometimes called the communications switch. It acts
like an are you ready polling mechanism. The select listens for incoming data
on channels, but there could also be cases where a value is not sent on a
channel:
select {
case u:= <- ch1:
...
case v:= <- ch2:
...
...
default: // no value ready to be received
...
}
The default clause is optional. The fall through behavior, like in the normal
switch, is not permitted. A select is terminated when a break or return is
executed in one of its cases. What select does is, it chooses which of the
multiple communications listed by its cases can proceed.
If all are blocked, it waits until one can proceed.
When none of the channel operations can proceed, and the default
clause is present, then this is executed because the default is always
runnable, or ready to execute.
If multiple can proceed, it chooses one at random.
Using a send operation in a select statement with a default case guarantees
that the send will be non-blocking! If there are no cases, the select blocks
execution forever.
The select statement implements a kind of listener pattern, and it is mostly
used within an (infinite) loop. When a certain condition is reached, the loop is
exited via a break statement.
Look at the following program:
package main
import (
"fmt"
"time"
"runtime"
)
func main() {
runtime.GOMAXPROCS(2)
ch1 := make(chan int)
ch2 := make(chan int)
go pump1(ch1)
go pump2(ch2)
go suck(ch1, ch2)
time.Sleep(1e9)
}
func pump1(ch chan int) {
for i:=0; ; i++ {
ch <- i*2
}
}
func pump2(ch chan int) {
for i:=0; ; i++ {
ch <- i+5
}
}
func suck(ch1 chan int,ch2 chan int) {
for {
select {
case v:= <- ch1:
fmt.Printf("Received on channel 1: %d\n", v)
case v:= <- ch2:
fmt.Printf("Received on channel 2: %d\n", v)
}
}
}
Goroutine with Select Statement
In above program, there are two channels ch1 and ch2 (defined at line 10
and line 11) and 3 goroutines pump1() (started at line 12), pump2() (started at
line 13) and suck() (started at line 14): a typical producer-consumer pattern.
Now, look at the header of the function pump1() at line 18. In an infinite loop
at line 20, ch is filled with integers ( i*2 ). Similarly, look at the header of the
function pump2() at line 24. In an infinite loop at line 26, ch is filled with
integers ( i+5 ).
The suck() function polls for input also in a non-ending loop (from line 31 to
line 38), and takes in the integers from ch1 and ch2 in the select clause
(line 32), and outputs them. If the first case : case v:= <- ch1: is true then we
get an output from line 35. But, if the second case : case v:= <- ch2: is true
then we get an output from line 37.
At line 15, the program is terminated in the main() function after 1 second.
Nulling idiom #
A channel that gets the value nil blocks further reading from it. This is applied
in the so-called nulling idiom, which can be applied when reading from a
number of channels inside a select statement. As soon as all the values in a
channel have been read, we set the channel to nil, effectively blocking further
reading from it:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import "fmt"
import "os"
import "strconv"
func iter(b int, c chan int) {
for i := 0; i < b; i++ {
c <- i // put integers from 0 to n-1 on channel c
}
close(c)
}
func main() {
n, _ := strconv.Atoi(os.Args[1]) // line arg. converted to integer
a := make(chan int)
b := make(chan int)
go iter(n, a)
go iter(n, b)
for a != nil || b != nil {
select {
case x, ok := <- a: // takes int value from channel a
if ok {
go
fmt.Println(x)
} else { // if channel a is closed
a = nil
}
case y, ok := <- b: // takes int value from channel b
if ok {
fmt.Println(y)
} else { // if channel b is closed
b = nil
}
}
}
}
Click the RUN button, and wait for the terminal to start. Type go run main.go
10 and press ENTER.
Note: You can also try any other number as a command-line argument
instead of 10 to check the variation.
At line 14, the command-line argument is converted to an integer n , ignoring
possible errors. At line 15 and line 16, we make two channels a and b ,
respectively. Then, at line 17, we start a goroutine with the iter() function,
passing n and channel a . Similarly, at line 18, we start a goroutine with the
iter() function, passing n , and channel b .
The iter() function is defined from line 6 to line 11. It puts integers from 0
to n-1 on its channel and then closes it.
Back in main() , a for-loop is started at line 19, which continues as long as one
of the two channels has a value different from nil . The select at line 20
chooses between two cases:
x, ok := <- a: : takes an integer value x from channel a . If it’s true,
then x will be printed.
y, ok := <- b: : takes an integer value y from channel b . If it’s true,
then y will be printed.
If reading of the channel fails because it is closed, ok will be false, and the
channel will get the value nil . When both channels are closed, they become
nil , and the for-loop stops.
Server backend pattern #
Often a server is implemented as a background goroutine which loops forever
and processes values of channels via a select from within that loop:
// Backend goroutine.
func backend() {
for {
select {
case cmd := <-ch1:
// Handle ...
case cmd := <-ch2:
...
case cmd := <-chStop:
// stop server
}
}
}
Other parts of the application send values on the channels ch1 , ch2 , and so
on. A stop channel is used for a clean termination of the server process.
Another possible (but less flexible) pattern is that all clients post their request
on a chRequest , and the backend routine loops over this channel, processing
requests according to their nature in a switch :
func backend() {
for req := range chRequest {
switch req.Subject() {
case A1: // Handle case ...
case A2: // Handle case ...
default:
// Handle illegal request ...
// ...
}
}
}
Now that you’re familiar with organizing channel reception via the select
construct, the next lesson brings you a challenge to solve.
Challenge: Devise Random Bit Generator
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Create a random bit generator that is a program that produces a sequence of
100000 randomly generated 1’s and 0’s using a goroutine.
Hint: You can use the select statement to randomly choose the cases for
random generation.
Try to attempt the challenge below. Good Luck!
package main
func main() {
}
Random Bit Generator
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Devise Random Bit Generator
This lesson discusses the solution to the challenge given in the previous lesson.
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
// consumer:
go func() {
for {
fmt.Print(<-ch, " ")
}
}()
// producer:
for i:=0; i<=100000; i++ {
select {
case ch <- 0:
case ch <- 1:
}
}
}
Random Bit Generator
In the code above, at line 7, we make a channel ch for integers. Then at line
9, we start an anonymous function as goroutine: this continually gets values
from the channel at line 11, in the infinite for-loop (from line 10 to line 12).
The for-loop (from line 15 to line 20), puts 100000 bits (0 or 1) on the channel.
To do that, it uses a select (see line 16) and chooses randomly one of the case
statements (either from line 17 or line 18).
That is it about the solution. In the next lesson, there’s another challenge for
you to solve.
Challenge: Map Polar Points to Cartesian Points
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
• Formulae
Problem statement #
Write an interactive console program that asks the user for the polar
coordinates of a 2-dimensional point (radius and angle (degrees)). Calculate
the corresponding Cartesian coordinates x and y , and print out the result.
Use structs called polar and Cartesian to represent each coordinate system.
Use channels and a goroutine:
A channel1 to receive the polar coordinates
A channel2 to receive the Cartesian coordinates
The conversion itself must be done with a goroutine, which reads from
channel1 and sends it to channel2 . In reality, for such a simple calculation it is
not worthwhile to use a goroutine and channels, but this solution would be
quite appropriate for heavy computation.
Formulae #
Θ = Angle of polar coordinates * π / 180.0 , where π=3.1417…
x of Cartesian = Radius of polar coordinates * Cos(Θ)
y of Cartesian = Radius of polar coordinates * Sin(Θ)
Note: To understand the conversion, you can read this Wikipedia page.
Try to attempt the challenge below. Good Luck!
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
type polar struct {
}
type cartesian struct {
}
func main() {
}
func createSolver(questions chan polar) chan cartesian {
}
func interact(questions chan polar, answers chan cartesian) {
}
Click the RUN button, and wait for the terminal to start. Type go run main.go
and press ENTER.
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Map Polar Points to Cartesian Points
This lesson discusses the solution to the challenge given in the previous lesson.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"bufio"
"fmt"
"math"
"os"
"runtime"
"strconv"
"strings"
)
type polar struct {
radius, Θ float64
}
// greek character!
type cartesian struct {
x, y float64
}
const result = "Polar: radius=%.02f angle=%.02f degrees -- Cartesian: x=%.02f y=%.02f\n"
var prompt = "Enter a radius and an angle (in degrees), e.g., 12.5 90, " + "or %s to quit."
func init() {
if runtime.GOOS == "windows" {
prompt = fmt.Sprintf(prompt, "Ctrl+Z, Enter")
} else { // Unix-like
prompt = fmt.Sprintf(prompt, "Ctrl+C")
}
}
func main() {
questions := make(chan polar)
defer close(questions)
answers := createSolver(questions)
defer close(answers)
interact(questions, answers)
}
func createSolver(questions chan polar) chan cartesian {
answers := make(chan cartesian)
go func() {
for {
polarCoord := <-questions
Θ := polarCoord.Θ * math.Pi / 180.0 // degrees to radians
x := polarCoord.radius * math.Cos(Θ)
y := polarCoord.radius * math.Sin(Θ)
answers <- cartesian{x, y}
}
}()
return answers
}
func interact(questions chan polar, answers chan cartesian) {
reader := bufio.NewReader(os.Stdin)
fmt.Println(prompt)
for {
fmt.Printf("Radius and angle: ")
line, err := reader.ReadString('\n')
if err != nil {
break
}
line = line[:len(line)-1] // chop off newline character
if numbers := strings.Fields(line); len(numbers) == 2 {
polars, err := floatsToStrings(numbers)
if err != nil {
fmt.Fprintln(os.Stderr, "invalid number")
continue
}
questions <- polar{polars[0], polars[1]}
coord := <-answers
fmt.Printf(result, polars[0], polars[1], coord.x, coord.y)
} else {
fmt.Fprintln(os.Stderr, "invalid input")
}
}
fmt.Println()
}
func floatsToStrings(numbers []string) ([]float64, error) {
var floats []float64
for _, number := range numbers {
if x, err := strconv.ParseFloat(number, 64); err != nil {
return nil, err
} else {
floats = append(floats, x)
}
}
return floats, nil
}
Click the RUN button, and wait for the terminal to start. Type go run main.go
and press ENTER.
The above program includes two major structs:
polar : takes two float64 variables as fields: radius and θ .
cartesian : takes two float64 variables as fields: x and y .
Line 20 defines a constant result string for the formatted output, and line 22
defines a string prompt for the command-line. This program also shows the
use of an init function (from line 24 to line 30). This is used here to test for the
operating system on which the program runs, and changes the prompt
variable accordingly (that’s why it had to be a var).
In main() , line 33 makes a channel questions of type polar , and closes this at
the end of the program with a defer at line 34. At line 35, questions is then
passed as a parameter to function createSolver() , which returns a channel of
type cartesian , which is captured in variable answers at line 35. answers is
also closed at the end of the program with a defer at line 36.
Function createSolver() makes a local channel answers at line 41. Then, it
starts an anonymous function call (from line 42 to line 50). In an infinite forloop starting at line 43, a polarCoord struct is taken from the channel
questions . Using the given mathematical functions, the polar coordinates are
converted to cartesian coordinates from line 45 to line 47. Then, the
cartesian struct is made with the transformed coordinates and put on the
answers channel. Then, at line 51, the channel of the cartesian values is
returned.
Finally, at line 37, the interact() function is called, passing the two channels
questions and answers as parameters. At line 55, the program makes a
buffered reader to read from the keyboard. In an infinite for-loop (from line
57 to line 63), it reads a string with polar coordinates (line 59) with errorhandling (from line 60 to line 76). It splits up the string on spaces to a string
array numbers . As long as there are 2 fields ( len(numbers)==2 ), it converts the
strings to polars at line 65 with floatsToStrings() . Look at the header of this
function at line 80. It converts each string into a float and returns an array of
floats, or a possible error while converting.
Then, back at line 70, we put a struct of polar coordinates onto the questions
channel. At line 71, we read the cartesian coordinates from the answers
channel and print them together at line 72. Note that invalid number and
invalid input messages are printed to the error output at line 67 and line 74,
respectively, without leaving the for-loop.
That is it about the solution. In the next lesson, we’ll discuss how to handle
communication over time.
Channels, Timeouts, and Tickers
This lesson explains how to set the responses of goroutines with time as a controlling factor.
WE'LL COVER THE FOLLOWING
• Simple timeout pattern
• 1st variant
• 2nd variant-abandoning synchronous calls
• 3rd variant
• Caching data in applications
The time package has some interesting functionality to use in combination
with channels. It contains a struct time.Ticker , which is an object that
repeatedly sends a time value on a contained channel C at a specified time
interval:
type Ticker struct {
C <-chan Time // the channel on which the ticks are delivered.
// contains filtered or unexported fields
...
}
A Ticker can be very useful when, during the execution of goroutines,
something (logging a status, a printout, a calculation, and so on) has to be
done periodically at a certain time interval. It is stopped with Stop() ; use this
in a defer statement. All this fits nicely in a select statement:
ticker := time.NewTicker(updateInterval)
defer ticker.Stop()
...
select {
case u:= <- ch1:
...
case v:= <- ch2:
...
case <- ticker.C:
logState(status) // e.g. call some logging function logState
default: // no value ready to be received
...
}
The time.Tick() function with signature func Tick(d Duration) <-chan Time
is useful when you only need access to the return channel and don’t need to
shut it down. It sends out the time on the return channel with periodicity d ,
which is a number of nanoseconds. It is handy to use when you have to limit
the rate of processing per unit time like in the following code snippet (the
function client.Call( ) is an RPC-call, the details are not further specified
here):
import "time"
rate_per_sec := 10
var dur Duration = 1e9 / rate_per_sec
chRate := time.Tick(dur) // a tick every 1/10th of a second
for req := range requests {
<- chRate // rate limit our Service.Method RPC calls
go client.Call("Service.Method", req, ...)
}
The net effect is that new requests are only handled at the indicated rate,
which means the channel chRate blocks higher rates. The rate per second can
be increased or decreased according to the load and / or the resources of the
machine.
A tick type looks exactly the same as a Ticker type (it is constructed with
time.Tick(d Duration) ), but it sends the time only once, after a Duration d .
There is also a function time.After(d) with the signature: func After(d
Duration) <-chan Time . After Duration d , the current time is sent on the
returned channel; this is equivalent to NewTimer(d).C It resembles Tick() , but
After() sends the time only once. The following listing shows a concrete
example, and also nicely illustrates the default clause in select :
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(1e8)
boom := time.After(5e8)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(5e7)
}
}
}
Timer goroutines
In the code above, look at line 8. time.Tick generates a tick every 1/8th of a
second, sending the current time on the return channel. At line 9, time.After
generates the same, but only one time is sent after 5/8th of a second, where
tick and boom are the receiving channels.
The select , in the infinite for-loop starting at line 10, by default prints out a
. and sleeps for a while. If there is a value on the tick channel (line 12), it
prints tick. When there is a value on the boom channel (line 14), it prints
boom and returns, stopping the program.
Simple timeout pattern #
1st variant #
We want to receive from a channel ch , but we want to wait at most 1 second
for the value to arrive. Start by creating a signaling channel and launching a
lambda goroutine that sleeps before sending on the channel:
timeout := make(chan bool, 1)
go func() {
time.Sleep(1e9) // one second
timeout <- true
}()
Then, use a select statement to receive from either ch or timeout: if nothing
arrives on ch in the 1 s time period, the timeout case is selected and the
attempt to read from ch is abandoned.
select {
case <-ch:
// a read from ch has occurred
case <-timeout:
// the read from ch has timed out
break
}
2nd variant-abandoning synchronous calls #
We could also use the time.After() function instead of a timeout-channel.
This can be used in a select to signal a timeout or stop the execution of
goroutines. When in the following code snippet, client.Call does not return
a value to channel ch after timeoutNs ns ; the timeout case is executed in the
select :
ch := make(chan error, 1)
go func() { ch <- client.Call("Service.Method", args, &reply) } ()
select {
case resp := <-ch:
// use resp and reply
case <-time.After(timeoutNs):
// call timed out
break
}
Note that the buffer size of 1 is necessary to avoid deadlock of goroutines and
guarantee garbage collection of the timeout channel.
3rd variant #
Suppose we have a program that reads from multiple replicated databases
simultaneously. The program needs only one of the answers, and it should
accept the answer that arrives first. The function Query takes a slice of
database connections and a query string. It queries each of the databases in
parallel and returns the first response it receives:
func Query(conns []Conn, query string) Result {
ch := make(chan Result, 1)
for _, conn := range conns {
go func(c Conn) {
select {
case ch <- c.DoQuery(query):
default:
}
}(conn)
}
return <- ch
}
Here, again the result channel ch has to be buffered; this guarantees that the
first send has a place to put the value and ensures that it will always succeed.
Therefore, the first value to arrive will be retrieved regardless of the order of
execution.
Caching data in applications #
Applications working with data coming from a database (or in general a
datastore) will often cache that data in memory because retrieving a value
from a database is a costly operation. When the data in the database does not
change, there is no problem with costliness. However, for values that can
change, we need a mechanism that periodically rereads the value in the
database. The cached value, in that case, becomes invalid (it has expired), and
we don’t want our application to present an old value to the user. This can be
solved with a goroutine and a Ticker object.
In the last chapter, we studied how to recover from a panic. What if we need
to recover when we are using goroutines? See the next lesson to learn how to
utilize the functionality of recover along with goroutines.
Using recover with goroutines
This lesson covers the advantages of recovering when using goroutines and how it doesn't affect the rest of the
program.
WE'LL COVER THE FOLLOWING
• Recovering with goroutines
Recovering with goroutines #
One application of recover is to shut down a failing goroutine inside a server
without killing the other executing goroutines.
func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work) // start the goroutine for that work
}
}
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Printf("work failed with %s in %v:", err, work)
// the failing goroutine is stopped
}
}()
do(work)
}
In the code snippet above, if do(work) panics, the error will be logged and the
goroutine will exit cleanly without disturbing the other goroutines.
Because recover always returns nil unless called directly from a deferred
function, the deferred code can call the library routines that themselves use
panic and recover without failing. As an example, the deferred function in
safelyDo() might call a logging function before calling recover, and that
logging code would run unaffected by the panicking state. With our recovery
pattern in place, the do function (and anything it calls) can get out of any bad
situation cleanly by calling panic . However, the recovery has to take place
inside the panicking goroutine. It cannot be recovered by a different
goroutine.
Now that you are familiar with recovering from panicking goroutines, in the
next lesson, you’ll learn how to choose the best pattern and the method of
communication between goroutines, depending on the scenarios.
Tasks and Worker Processes
This lesson lists the possible ways of communication and their pros and cons in different situations.
WE'LL COVER THE FOLLOWING
• 1st paradigm: use shared memory to synchronize
• 2nd paradigm: channels
• Use an in-channel and out-channel instead of locking
• What to use: a sync.Mutex or a channel?
• Using locking (mutexes)
• Using channels
Suppose we have to perform several tasks; a task is performed by a worker
(process). A Task can be defined as a struct (the concrete details are not
important here):
type Task struct {
// some state
}
1st paradigm: use shared memory to synchronize
#
The pool of tasks is shared memory. To synchronize the work and to avoid
race conditions, we have to guard the pool with a Mutex lock:
type Pool struct {
Mu sync.Mutex
Tasks []Task
}
A sync.Mutex (as mentioned earlier in the course) is a mutual exclusion lock. It
serves to guard the entrance to a critical section in code. Only one goroutine
(thread) can enter that section at one time. If more than one goroutine is
allowed, a race-condition can exist, which means the Pool struct can no
longer be updated correctly.
In the traditional model (applied in most classic OO-languages like C++, Java,
and C#) the Worker process could be coded as:
func Worker(pool *Pool) {
for {
pool.Mu.Lock()
// begin critical section:
task := pool.Tasks[0] // take the first task
pool.Tasks = pool.Tasks[1:] // update the pool of tasks
// end critical section
pool.Mu.Unlock()
process(task)
}
}
Many of these worker processes can run concurrently. They can certainly be
started as goroutines. A worker locks the pool, takes the first task from the
pool, unlocks the pool, and then processes the task. The lock guarantees that
only one worker process at a time can access the pool. A task is assigned to
one and only one process. If the lock is not there, the processing of the
worker-routine can be interrupted in the lines task := pool.Tasks[0] and
pool.Tasks = pool.Tasks[1:] with abnormal results. Some workers do not get
a task, and several workers obtain some tasks. This locking synchronization
works well for a few worker processes. Still, if the Pool is very big and we
assign a large number of processes to work on it, the efficiency of the
processing will be diminished by the overhead of the lock-unlock mechanism.
This is the bottle-neck that causes performance to certainly decrease when the
number of workers increases drastically at a certain threshold.
2nd paradigm: channels #
In this case, channels of Tasks are used to synchronize. A pending channel
receives the requested tasks, and a done channel receives the performed tasks
(with their results). The worker processes are started as goroutines; their
number N should be adjusted to the number of tasks. The main routine, which
performs the function of Master , can be programmed as:
func main() {
pending, done := make(chan *Task), make(chan *Task)
go sendWork(pending) // put tasks to do on the channel
for i := 0; i < N; i++ { // start N goroutines to do work
go Worker(pending, done)
}
consumeWork(done) // continue with the processed tasks
}
The worker process is very simple as it takes a task from the pending channel,
processes it, and puts the finished task on the done channel:
func Worker(in, out chan *Task) {
for {
t := <-in
process(t)
out <- t
}
}
There is no locking; the process of getting a new task involves no contention.
If the amount of tasks increases, the number of workers can be increased
accordingly, and the performance will not degrade nearly as badly as in the 1st
solution. From the pending channel, there is, of course, only one copy in
memory. Still, there is no contention because, for the first Worker to finish the
1st pending task, it will have to process it completely since reading from and
sending to a channel are atomic operations. It is impossible to predict which
task will be performed by which process and vice versa. With an increasing
number of workers, there is also an increasing communication overhead,
which has a slight impact on performance.
In this simple example, it is perhaps difficult to see the advantage of the 2nd
model, but applications with complex lock-situations are tough to program
and to get right. A great deal of this complexity in the software is not needed
in a solution that applies the 2nd model.
Thus not only performance is a significant advantage, but the clearer and
more elegant code is perhaps an even bigger advantage. It is undoubtedly a
Go idiomatic way of working:
Use an in-channel and out-channel instead of locking #
func Worker(in, out chan *Task) {
for {
t := <-in
process(t)
out <- t
}
}
For any problem, which can be modeled as such a Master-Worker paradigm,
an analogous solution with Workers as goroutines communicating through
channels and the Master as a coordinator is a perfect fit. If the system
distributes over several machines, a number of machines could execute the
Worker goroutines, and the Master and Workers could communicate amongst
themselves through netchan or rpc .
What to use: a sync.Mutex or a channel? #
Although in this chapter, we put a strong emphasis on goroutines using
channels, because this is quite new in system languages, this doesn’t mean
that the classic approach with locking is now taboo. Go has both and gives you
a choice according to the problem being solved. Construct the solution that is
the most elegant, simplest, and most readable, and in most cases, performance
will follow automatically. Don’t be afraid to use a Mutex if that fits your
problem best. Go is pragmatic in letting you use the tools that solve your
problem best and not forcing you into one style of code. As a general rule of
thumb:
Using locking (mutexes) #
Use mutex when:
Caching information in a shared data structure
Holding state information, i.e., the context or status of a running
application
Using channels #
Use channels when:
Communicating asynchronous results
Distributing units of work
Passing ownership of data
If you find your locking rules are getting too complicated, ask yourself if using
channel(s) might be simpler.
Thus, when it comes to designing a way of communication, whether it be a
shared memory or a channel, a lot has to be taken into consideration to get
the most out of the process. In the next lesson, you’ll learn the basics of lazy
evaluation.
Implementing a Lazy Generator
This lesson gives detailed knowledge on generators and lazy evaluation. It explains how to write a generator for
lazy evaluation.
WE'LL COVER THE FOLLOWING
• Introduction
• Explanation
Introduction #
A generator is a function that returns the next value in a sequence each time
the function is called, like:
generateInteger() => 0
generateInteger() => 1
generateInteger() => 2
....
It is a producer that only returns the next value, not the entire sequence. This
is called _lazy evaluations, which means only computing what you need at the
moment, therefore saving valuable resources (memory and CPU). It is
technology for the evaluation of expressions on demand.
Explanation #
An example (of lazy generation) would be the generation of an endless
sequence of even numbers. To generate it and use those numbers one by one
would be difficult and certainly would not fit into memory! But, a simple
function per type with a channel and a goroutine can do the job.
For example, in the following program, we see a Go implementation with a
channel of a generator of ints. The channel is named yield and resume , terms
commonly used in coroutine code.
package main
import (
"fmt"
)
var resume chan int
func integers() chan int {
yield := make (chan int)
count := 0
go func () {
for {
yield <- count
count++
}
} ()
return yield
}
func generateInteger() int {
return <-resume
}
func main() {
resume = integers()
fmt.Println(generateInteger()) //=> 0
fmt.Println(generateInteger()) //=> 1
fmt.Println(generateInteger()) //=> 2
}
Lazy Evaluation
In the code above, at line 6, resume is declared as a channel of integers. This is
initialized at line 24, where it gets the return value of the integers()
function.
Look at the header of integers() function at line 7. At line 8, it makes a
channel yield , which will be returned at line 16 to resume . It populates the
channel by starting a goroutine with an anonymous function. This contains an
infinite for-loop (from line 11 to line 14), which puts successive integers on
the channel.
Then, from line 25 to line 27, back in main() , the function generateInteger()
is called successively. As we see, line 20 takes one value from the channel
resume , and returns it. This prints out the numbers 0, 1 and 2.
A subtle difference is that the value read from the channel could have been
generated a while ago, rather than at the time of reading. If you need such
behavior, you have to implement a request-response mechanism. When the
generator’s task is computationally expensive, and the order of generating
results does not matter, then the generator can be parallelized internally by
using goroutines. However, be careful that the overhead generated by
spawning many goroutines does not outweigh any performance gain.
These principles can be generalized, by making clever use of the empty
interface, closures and higher-order functions. We can implement a generic
builder BuildLazyEvaluator for the lazy evaluation function (this should best
be placed inside a utility package). The builder takes a function that has to be
evaluated and an initial state as arguments and returns a function without
arguments, returning the desired value. The passed evaluation function has to
calculate the next return value as well as the next state based on the state
argument. Inside the builder, a channel and a goroutine with an endless loop
are created. The return values are passed to the channel from which they are
fetched by the returned function for later usage. Each time a value is fetched,
the next one will be calculated.
The following code is an implementation of the mentioned concept:
package main
import (
"fmt"
)
type Any interface{}
type EvalFunc func(Any) (Any, Any)
func main() {
evenFunc := func(state Any) (Any, Any) {
os := state.(int)
ns := os + 2
return os, ns
}
even := BuildLazyIntEvaluator(evenFunc, 0)
for i := 0; i < 10; i++ {
fmt.Printf("%vth even: %v\n", i, even())
}
}
func BuildLazyEvaluator(evalFunc EvalFunc, initState Any) func() Any {
retValChan := make(chan Any)
loopFunc := func() {
var actState Any = initState
var retVal Any
for {
retVal, actState = evalFunc(actState)
retValChan <- retVal
}
}
retFunc := func() Any {
return <-retValChan
}
go loopFunc()
return retFunc
}
func BuildLazyIntEvaluator(evalFunc EvalFunc, initState Any) func() int {
ef := BuildLazyEvaluator(evalFunc, initState)
return func() int {
return ef().(int)
}
}
General Lazy Evaluation
This example uses several higher-order function techniques and is highly
abstract. You can safely skip it on first reading if you want to.
Line 6 defines an alias Any for the empty interface. Line 7 defines a function
type EvalFunc that takes an Any and returns a tuple of two Any’s . In main()
(from line 10 to line 14), an anonymous function with type EvalFunc is
defined and assigned to the name evenFunc . Line 11 effectively does a
conversion to int assigning to integer os , which is returned together with os
+ 2 at the end of the function.
BuildLazyIntEvaluator(evenFunc, 0) is called at line 16, and assigned to even .
The even() is a function, so it is called 10 times in the loop (see line 18), and
the result of its call is printed.
Now, look at the header of BuildLazyIntEvaluator at line 39. It takes a
function of type EvalFunc as a parameter, as well as a variable of any type
initState . Its return value is a function that has no parameters and returns
an int. It calls on its turn BuildLazyEvaluator at line 40. The result is assigned
to ef . Then, we return an anonymous function that returns an integer,
namely ef converted to int, at line 42.
Now, look at the header of BuildLazyEvaluator at line 22. It is the generalized
version of BuildLazyIntEvaluator , which works for any type. At line 23, a
channel retValChan of type Any is made. Then, we define an anonymous
function at line 24, assigned to loopFunc . At line 28 (which is contained in an
infinite for loop) we see this call: retVal, actState = evalFunc(actState) . The
evalFunc is the even function, so the return values are an integer and integer
+ 2. The integer is put on the channel retValChan , and the 2nd integer
actState becomes the new first integer.
loopFunc is a function, which is started in a goroutine at line 35. Just before
that at line 32, we defined a function as an anonymous function that gets and
returns a value from retValChan . retFunc is then returned from
BuildLazyEvaluator at line 36. The combined result of all this code is that we
generate a series of even numbers in a lazy way with goroutines.
That is it on lazy generators. In the next lesson, you’ll learn how to handle an
anonymous function with a channel.
Anonymous Closure
In this lesson there is a detailed description of how to use closures along with a channel.
WE'LL COVER THE FOLLOWING
• Passing anonymous closure through a channel
Passing anonymous closure through a channel #
Functions are values in Go, and so are closures. So, we can construct a
channel c , whose data type is a function, as in the following example:
package main
import "fmt"
func prod(c chan func()) {
f := <- c
f()
c <- nil
}
func main() {
c := make(chan func())
go prod(c)
x := 0
c <- func() { // puts a closure in channel c
x++
}
fmt.Println(x)
}
Channel Closure
The output of this program is 1. At line 11, you can see how to make channel
c with lambdas: c := make(chan func()) . Then, at line 12, we start a
goroutine by passing c to it. Executing the prod() function, this is
implemented from line 4 to line 8.
Back to main() at line 13, an integer x is defined. In the commented line 14,
an anonymous closure, capturing the value of x , is sent to the channel c . The
closure increments x .
The goroutine that executes the function prod reads that closure f from the
channel at line 5 and calls it at line 6. This increments the value of x from 0
to 1. Then at line 7, we put nil on the channel. The net result is that x gets
the value 1.
Now that you’re familiar with how anonymous closures work with channels,
in the next lesson, you’ll learn another combined concept that involves
closures and lazy generators.
Implementing Futures
This lesson describes Go’s “futures”, which allow the calculation of values beforehand using lazy evaluation and
channels.
WE'LL COVER THE FOLLOWING
• Introduction
• Explanation
Introduction #
A related idea to lazy evaluation is that of futures. Sometimes, you know you
need to compute a value before you actually need to use the value. In this
case, you can potentially start computing the value on another processor and
have it ready when you need it. Futures are easy to implement via closures
and goroutines, and the idea is similar to generators, except a future needs
only to return one value.
Explanation #
Suppose we have a type Matrix , and we need to calculate the inverse of the
product of 2 matrices a and b . First, we have to invert both of them through
a function Inverse(m) , and then take the Product of both results. This could
be done with the following function InverseProduct() :
func InverseProduct(a Matrix, b Matrix) {
a_inv := Inverse(a)
b_inv := Inverse(b)
return Product(a_inv, b_inv)
}
In this example, it is known initially that the inverse of both a and b must be
computed.
Why should the program wait for a_inv to be computed before starting the
computation of b_inv ? These inverse computations can be done in parallel.
On the other hand, the call to Product needs to wait for both a_inv and
b_inv . This can be implemented as follows:
func InverseProduct(a Matrix, b Matrix) {
a_inv_future := InverseFuture(a) // started as a goroutine
b_inv_future := InverseFuture(b) // started as a goroutine
a_inv := <-a_inv_future
b_inv := <-b_inv_future
return Product(a_inv, b_inv)
}
InverseFuture() launches a closure as a goroutine, which puts the resultant
inverse matrix on a channel future , as a result:
func InverseFuture(a Matrix) (chan Matrix) {
future := make(chan Matrix)
go func() { future <- Inverse(a) }()
return future
}
When developing a computationally intensive package, it may make sense to
design the entire API around the futures. The futures can be used within your
package while maintaining a friendly API. In addition, the futures can be
exposed through an asynchronous version of the API. This way, the
parallelism in your package can be lifted into the user’s code with minimal
effort. A discussion applied to this example can be found here.
That is it for futures. In the next lesson, you’ll learn about another
phenomenon known as multiplexing through the client-server model.
Multiplexing
This lesson brings attention to the client-server model that best utilizes the goroutines and channels. It provides
the code and explanation of how goroutines and channels together make a client-server application.
WE'LL COVER THE FOLLOWING
• A typical client-server pattern
• Teardown: shutting down the server
A typical client-server pattern #
Client-server applications are the kind of applications where goroutines and
channels shine. A client can be any running program on any device that needs
something from a server, so it sends a request. The server receives this
request, does some work, and then sends a response back to the client. In a
typical situation, there are many clients (so many requests) and one (or a few)
server(s). An example we use all the time is the client browser, which requests
a web page. A web server responds by sending the web page back to the
browser.
In Go, a server will typically perform a response to a client in a goroutine, so a
goroutine is launched for every client-request. A technique commonly used is
that the client-request itself contains a channel, which the server uses to send
in its response.
For example, the request is a struct like the following which embeds a reply
channel:
type Request struct {
a, b int;
replyc chan int; // reply channel inside the Request
}
Or more generally:
O
o e ge e a y:
type Reply struct { ... }
type Request struct {
arg1, arg2, arg3 some_type
replyc chan *Reply
}
Continuing with the simple form, the server could launch for each request a
function run() in a goroutine that will apply an operation op of type binOp to
the ints and then send the result on the reply channel:
type binOp func(a, b int) int
func run(op binOp, req *Request) {
req.replyc <- op(req.a, req.b)
}
The server routine loops forever, receiving requests from a chan *Request
and, to avoid blocking due to a long-running operation, it starts a goroutine
for each request to do the actual work.
func server(op binOp, service chan *Request) {
for {
req := <-service; // requests arrive here
// start goroutine for request:
go run(op, req); // don't wait for op to complete
}
}
The server is started in its own goroutine by the function startServer :
func startServer(op binOp) chan *Request {
reqChan := make(chan *Request);
go server(op, reqChan);
return reqChan;
}
Here, startServer will be invoked in the main routine.
In the following test-example, 100 requests are posted to the server, only after
they all have been sent do we check the responses in reverse order:
func main() {
adder := startServer(func(a, b int) int { return a + b })
const N = 100
var reqs [N]Request
for i := 0; i < N; i++ {
req := &reqs[i]
req.a = i
req.b = i + N
req.replyc = make(chan int)
adder <- req // adder is a channel of requests
}
// checks:
for i := N - 1; i >= 0; i-- { // doesn't matter what order
if <-reqs[i].replyc != N+2*i {
fmt.Println("fail at", i)
} else {
fmt.Println("Request ", i, " is ok!")
}
}
fmt.Println("done")
}
The following is the resultant program of combining the above snippets into
an executable format:
package main
import "fmt"
type Request struct {
a, b int
replyc chan int // reply channel inside the Request
}
type binOp func(a, b int) int
func run(op binOp, req *Request) {
req.replyc <- op(req.a, req.b)
}
func server(op binOp, service chan *Request) {
for {
req := <-service // requests arrive here
// start goroutine for request:
go run(op, req) // don't wait for op
}
}
func startServer(op binOp) chan *Request {
reqChan := make(chan *Request)
go server(op, reqChan)
return reqChan
}
func main() {
adder := startServer(func(a, b int) int { return a + b })
const N = 100
var reqs [N]Request
for i := 0; i < N; i++ {
req := &reqs[i]
req.a = i
req.b = i + N
req.replyc = make(chan int)
adder <- req
}
// checks:
for i := N - 1; i >= 0; i-- { // doesn't matter what order
if <-reqs[i].replyc != N+2*i {
fmt.Println("fail at", i)
} else {
fmt.Println("Request ", i, " is ok!")
}
}
fmt.Println("done")
}
Multiplex Server
This program has been completely described in the introduction above. It has
the following basic parts:
We define the Request struct at line 4. It has two integer fields a and b ,
and a channel of integers named replyc .
We define a function type binOp for any function that takes two integers
as input and returns an integer (see line 9).
We define the run() function, which takes an object of type binOp and a
pointer to the object of type Request (see its header at line 10). The run()
function puts the result of the operation on the reply channel replyc of
the request struct req .
We define the server() function, which takes an object of type binOp and
a pointer to the channel of type Request (see its header at line 14). The
server() function is an infinite for loop that takes a request from the
service channel (line 16), and starts a goroutine at line 19 with that
request.
At line 30, in main() , the server is started. startServer() (from line 23 to line
27) starts the server in its own goroutine, and returns the request channel.
From line 31 to line 39, 100 requests are constructed and sent to the request
channel. Finally, from line 41 to line 47, we check the correctness of the result
of each request, displaying messages in the ok and error cases.
Teardown: shutting down the server #
In the previous version, the server does not perform a clean shutdown when
main returns; it is forced to stop. To improve this, we can provide a second,
quit channel to the server:
func startServer(op binOp) (service chan *Request, quit chan bool) {
service = make(chan *Request)
quit = make(chan bool)
go server(op, service, quit)
return service, quit
}
Then, the server function uses a select to choose between the service
channel and the quit channel:
func server(op binOp, service chan *request, quit chan bool) {
for {
select {
case req := <-service:
go run(op, req)
case <-quit:
return
}
}
}
When a true value enters the quit channel, the server returns and terminates.
In main , we change the following line:
adder, quit := startServer(func(a, b int) int { return a + b })
At the end of main , we place the line: quit <- true . The complete code can be
found in the following program, with the same output as the above.
package main
import "fmt"
type Request struct {
a, b int
replyc chan int // reply channel inside the Request
}
type binOp func(a, b int) int
func run(op binOp, req *Request) {
req.replyc <- op(req.a, req.b)
}
func server(op binOp, service chan *Request, quit chan bool) {
for {
select {
case req := <-service:
go run(op, req)
case <-quit:
return
}
}
}
func startServer(op binOp) (service chan *Request, quit chan bool) {
service = make(chan *Request)
quit = make(chan bool)
go server(op, service, quit)
return service, quit
}
func main() {
adder, quit := startServer(func(a, b int) int { return a + b })
const N = 100
var reqs [N]Request
for i := 0; i < N; i++ {
req := &reqs[i]
req.a = i
req.b = i + N
req.replyc = make(chan int)
adder <- req
}
// checks:
for i := N - 1; i >= 0; i-- { // doesn't matter what order
if <-reqs[i].replyc != N+2*i {
fmt.Println("fail at", i)
} else {
fmt.Println("Request ", i, " is ok!")
}
}
quit <- true
fmt.Println("done")
}
Teardown of Multiplex Server
Adding to the explanations of the previous example, the server has a quit
channel, defined at line 28, which startServer() returns (see line 26). The
server() function itself, now, contains a select clause inside the for (from
line 17 to line 22). If there is a request, run it in a separate goroutine (line 19).
If a value has been put on the quit channel (line 20), return from the
function and stop the program. The value true is put on the quit channel at
line 52, so this is an ordered shutdown of our server.
It’s essential to have a balance between clients and the server. A considerable
number of requests may burden the server. In the next lesson, you’ll learn
how to limit the number of requests.
Limiting the Number of Requests
Multiple clients may make their requests to a server all at once, which can result in a massive number of requests
that burden the server. This lesson brings the solution to this problem: a workaround in Go.
WE'LL COVER THE FOLLOWING
• Bounding requests processed concurrently
Bounding requests processed concurrently #
This is easily accomplished using a channel with a buffer, whose capacity is
the maximum number of concurrent requests. The following program does
nothing useful, but contains the technique to bound the requests. No more
than MAXREQS requests will be handled and processed simultaneously because,
when the buffer of the channel sem is full, the function handle blocks and no
other requests can start until a request is removed from sem . The sem acts
like a semaphore that is a technical term for a flag variable in a program that
signals a certain condition.
package main
const (
AvailableMemory = 10 << 20 // 10 MB, for example
AverageMemoryPerRequest = 10 << 10 // 10 KB
MAXREQS = AvailableMemory / AverageMemoryPerRequest // here amounts to 1000
)
var sem = make(chan int, MAXREQS)
type Request struct {
a, b int
replyc chan int
}
func process(r *Request) {
// Do something
// May take a long time and use a lot of memory or CPU
}
func handle(r *Request) {
process(r)
<-sem // signal done: enable next request to start by making 1 empty place in the buffer
}
func Server(queue chan *Request) {
for {
sem <- 1 // blocks when channel is full (1000 requests are active)
// so wait here until there is capacity to process a request
// (doesn't matter what we put in it)
request := <-queue
go handle(request)
}
}
func main() {
queue := make(chan *Request)
go Server(queue)
}
Maximum Tasks
In the constants section from line 3 to line 7, we calculate the maximum
number of requests MAXREQS as the available memory is divided by the
memory every request needs. Then, at line 9, we make a channel of integers
called sem , which has a buffer just to that amount. We define a typical
Request struct ( from line 11 to line 14). We define stub functions process()
and handle() , which take a request, process it, and remove an item for sem to
make a place for the next request (line 23).
The main() function makes a queue of Request and starts Server with it. The
Server() function (defined from line 26 to line 35), starts an infinite for-loop,
putting 1 on the semaphore channel. Then, at line 32 and line 33, the
Server() tries to get a request from the queue . As soon as it gets one, it starts
handling this request in a separate goroutine (line 33).
When sem is full, line 28 blocks until a process in action gets a value from sem
at line 23. That way, only a maximum of MAXREQS processes can be handled at
a time.
Reducing the limit of the number of requests allowed enables the server to
provide services in the best manner. Parallelizing the computation over a
large amount of data within the limit, optimizes the process. See the next
lesson to learn how this works.
Chaining and Parallelizing Computation
This lesson explains how to chain goroutines and parallelize computation over multiple machines.
WE'LL COVER THE FOLLOWING
• Chaining goroutines
• Parallelizing a computation over several cores
• Parallelizing a computation over a large amount of data
Chaining goroutines #
The following demo-program demonstrates how easy it is to start a massive
number of goroutines.
package main
import (
"flag"
"fmt"
)
var ngoroutine = flag.Int("n", 100000, "how many goroutines")
func f(left, right chan int) { left <- 1+<-right }
func main() {
flag.Parse()
leftmost := make(chan int)
var left, right chan int = nil, leftmost
for i := 0; i < *ngoroutine; i++ {
left, right = right, make(chan int)
go f(left, right)
}
right <- 0 // start the chaining
x := <-leftmost // wait for completion
fmt.Println(x) // 100000, approx. 1.5 s
}
Chaining
In this little program, you can indicate how many goroutines you want at the
command-line. This is captured in the flag ngoroutine (line 7) when the flags
are parsed (line 12). Then, at line 14, we define two integer channels left
and right , initializing them. In a for-loop (starting at line 15) with the given
number of requests as maxim iteration, we first advance the channels from
right to left at line 16. Then, we call the function f , with both channels as a
goroutine at line 17. The function f() defined at line 9, takes a value from
right , increments it, and puts it on left . Line 19 starts the chaining by
putting 0 in the right channel. Line 20 waits until the last item can be
retrieved from the leftmost channel.
Chaining happens in for-loop in main . After the loop, 0 is inserted in the
rightmost channel, the 100000 goroutines execute, and the result (which is
100000) is printed in less than 1.5 s. This program also demonstrates how the
number of goroutines can be given on the command-line and parsed in
through flag.Int , e.g. chaining -n=7000, generates 7000 goroutines.
Parallelizing a computation over several cores #
Suppose we have NCPU number of CPU cores:
const NCPU = 4 // e.g. 4 for a quadcore processor
Additionally, we want to divide a computation in NCPU parts, each running in
parallel with the others. This could be done schematically (we leave out the
concrete parameters) as follows:
func DoAll() {
sem := make(chan int, NCPU) // Buffering optional but sensible.
for i := 0; i < NCPU; i++ {
go DoPart(sem)
}
// Drain the channel sem, waiting for NCPU tasks to complete
for i := 0; i < NCPU; i++ {
<-sem // wait for one task to complete
}
// All done.
}
func DoPart(sem chan int) {
// do the part of the computation
}
sem <- 1 // signal that this piece is done
}
func main() {
runtime.GOMAXPROCS = NCPU
DoAll()
}
The function DoAll() makes a channel sem upon which each of the
parallel computations will signal its completion. In a for loop, NCPU
goroutines are started, each performing 1/NCPU –th part of the total
work.
Each DoPart() goroutine signals its completion on sem .
DoAll() waits in a for-loop until all NCPU goroutines have completed. The
channel sem acts as a semaphore. This code shows a typical semaphore
pattern.
In the present model of the runtime, you also have to set GOMAXPROCS to NCPU .
Parallelizing a computation over a large amount
of data #
Suppose we have to process a large number of data-items independent of each
other, coming in through a channel in, and when completely processed, put
on a channel out, much like a factory pipeline. The processing of each dataitem will also probably involve a number of steps: Preprocess / StepA / StepB
/ … / PostProcess
A typical sequential pipelining algorithm for solving this, executing each step
in order, could be written as follows:
func SerialProcessData (in <- chan *Data, out <- chan *Data) {
for data := range in {
tmpA := PreprocessData(data)
tmpB := ProcessStepA(tmpA)
tmpC := ProcessStepB(tmpB)
out <- PostProcessData(tmpC)
}
}
Only one step is executed at a time, and each item is processed in sequence.
Processing the 2nd item is not started before the 1st item is post-processed
and the result is put on the out channel. If you think about it, you will soon
realize that this is a great waste of time. A much more efficient computation
would be to let each processing step work independent of each other as a
goroutine. Each step gets its input data from the output channel of the
previous step. That way, the least amount of time will be lost, and most of the
time, all steps will be busy executing:
func ParallelProcessData (in <- chan *Data, out <- chan *Data) {
// make channels:
preOut := make(chan *Data, 100)
stepAOut := make(chan *Data, 100)
stepBOut := make(chan *Data, 100)
stepCOut := make(chan *Data, 100)
// start parallel computations:
go PreprocessData(in, preOut)
go ProcessStepA(preOut, stepAOut)
go ProcessStepB(stepAOut, stepBOut)
go ProcessStepC(stepBOut, stepCOut)
go PostProcessData(stepCOut, out)
}
The channels’ buffer capacities could be used to further optimize the whole
process.
Now that you’re familiar with chaining and parallelizing over multiple cores,
in the next lesson, you’ll learn an algorithm known as the leaky-bucket
algorithm.
The Leaky Bucket Algorithm
This lesson gives an implementation and explanation of an algorithm that controls client-server communication
using a buffer.
WE'LL COVER THE FOLLOWING
• Introduction
• Example
Introduction #
Consider the following client-server configuration. The client goroutine
performs an infinite loop that is receiving data from some source, perhaps a
network; the data are read in buffers of type Buffer . To avoid allocating and
freeing buffers too much, it keeps a free list of them, and uses a buffered
channel to represent them:
var freeList = make(chan *Buffer, 100)
This queue of reusable buffers is shared with the server.
When receiving data, the client tries to take a buffer from freeList . If this
channel is empty, a new buffer gets allocated. Once the message buffer is
loaded, it is sent to the server on serverChan :
var serverChan = make(chan *Buffer)
Example #
Here is the algorithm for the client code:
func client() {
for {
var b *Buffer
// Grab a buffer if available; allocate if not
select {
case b = <-freeList:
// Got one; nothing more to do
default:
// None free, so allocate a new one
b = new(Buffer)
}
loadInto(b) // Read next message from the network
serverChan <- b // Send to server
}
}
The server loop receives each message from the client, processes it, and tries
to return the buffer to the shared free list of buffers:
func server() {
for {
b := <-serverChan // Wait for work.
process(b)
// Reuse buffer if there's room.
select {
case freeList <- b:
// Reuse buffer if free slot on freeList; nothing more to do
default:
// Free list full, just carry on: the buffer is ‘dropped’.
}
}
}
However, this doesn’t work when freeList is full, in which case the buffer is
dropped on the floor (hence the name leaky bucket) to be reclaimed by the
garbage collector.
Before summing up the goroutines and channels, it’s important to learn how
to benchmark goroutines, provide concurrent access to the objects through
channels, and listen through different channels. See the next lesson to get a
grasp of these concepts.
Benchmarking, Accessing and Listening
This lesson provides an explanation on how to benchmark a goroutine, run a backend goroutine, and use re ection
on channels.
WE'LL COVER THE FOLLOWING
• Benchmarking goroutines
• Concurrent access to objects by using a channel
• Using re ection to listen to a dynamically created set of channels
Benchmarking goroutines #
In Chapter 11, we mentioned the principle of performing benchmarks on your
functions in Go. Here, we apply it to a concrete example of a goroutine that is
filled with ints and then read. The functions are called N times (e.g. N
=1000000) with testing.Benchmark . The BenchMarkResult has a String()
method for outputting its findings. The number N is decided upon by go test .
It is taken to be high enough to get a reasonable benchmark result.
Of course, the same way of benchmarking also applies to ordinary functions.
If you want to exclude certain parts of the code or you want to be more
specific in what you are timing, you can stop and start the timer by calling the
functions testing.B.StopTimer() and testing.B.StartTimer() as appropriate.
The benchmarks will only be run if all your tests pass!
package main
import (
"fmt"
"testing"
)
func main() {
fmt.Println(" sync", testing.Benchmark(BenchmarkChannelSync).String())
fmt.Println("buffered", testing.Benchmark(BenchmarkChannelBuffered).String())
}
func BenchmarkChannelSync(b testing.B) { // makes buffered channel
ch := make(chan int)
go func() {
for i := 0; i < b.N; i++ {
ch <- i
}
close(ch)
}()
for _ = range ch { // iterating over channel without doing anything
}
}
func BenchmarkChannelBuffered(b *testing.B) { // makes buffered channel with capacity of 128
ch := make(chan int, 128)
go func() {
for i := 0; i < b.N; i++ {
ch <- i
}
close(ch)
}()
for _ = range ch {
}
}
Benchmark Channels
In order to do benchmarking, you have to import the testing package (see line
4). The main() calls first the BenchmarkChannelSync() function, and then the
BenchmarkChannelBuffered() function; both require a parameter of type
*testing.B .
Look at the header of the function BenchmarkChannelSync() at line 12. It makes
an unbuffered channel ch and then starts a goroutine with an anonymous
function, which puts a large number of successive integers on ch . In the
main() routine at line 20, it iterates over the channel without doing anything.
Look at the header of function BenchmarkChannelBuffered() at line 24. It makes
a buffered channel ch with a capacity of 128 and then performs the same
action as BenchmarkChannelSync()
The results show clearly that using a buffered channel is much more efficient.
Concurrent access to objects by using a channel
#
To safeguard concurrent modifications of an object instead of using locking
with a sync Mutex, we can also use a backend goroutine for the sequential
execution of anonymous functions. Here is an example program:
package main
import (
"fmt"
"strconv"
)
type Person struct {
Name string
salary float64
chF chan func()
}
func NewPerson(name string, salary float64) *Person {
p := &Person{name, salary, make(chan func())}
go p.backend()
return p
}
func (p *Person) backend() {
for f := range p.chF {
f()
}
}
// Set salary.
func (p *Person) SetSalary(sal float64) {
p.chF <- func() { p.salary = sal }
}
// Retrieve salary.
func (p *Person) Salary() float64 {
fChan := make(chan float64)
p.chF <- func() { fChan <- p.salary }
return <-fChan
}
func (p *Person) String() string {
return "Person - name is: " + p.Name + " - salary is: " +
strconv.FormatFloat(p.Salary(), 'f', 2, 64)
}
func main() {
bs := NewPerson("Smith Bill", 2500.5)
fmt.Println(bs)
bs.SetSalary(4000.25)
fmt.Println("Salary changed:")
fmt.Println(bs)
}
Concurrent Access
In the program above, we have a type Person defined at line 7, which now
contains a field chF , a channel of anonymous functions. A Person is
initialized in the constructor method NewPerson at line 14. This method also
starts a function backend() as a goroutine (line 15), and returns a pointer to a
Person at line 16.
The backend() function executes (from line 20 to line 22) all the functions
placed on chF in an infinite loop, effectively serializing them and thus
providing safe concurrent access. The methods that change ( SetSalary , see its
header at line 26) and retrieve the salary (Salary, see its header at line 31)
create an anonymous function that does the changing or retrieving (resp. at
line 27 and line 33) and put this function on chF .
As we saw above, backend() will sequentially execute functions. Notice how
in the method Salary (at line 32 and line 33), the created closure function
includes the channel fChan . The String() method on Person ( from line 37 to
line 40), simply gets all info from a Person and puts that into a formatted
string.
In main() , we create a specific Person at line 43, then print out the info. Then,
we change the salary at line 45 and again print the new info. Retrieving and
changing the info is performed by backend() in the separate goroutine started
at line 15. Because this goroutine can only execute one function at a time, a
concurrency problem can not occur.
This is, of course, a simplified example, and it should not be applied in such
simple cases. However, it shows how the problem could be tackled in more
complex situations.
Using re ection to listen to a dynamically created
set of channels #
As the following example shows, you can combine the use of channels with
reflection:
package main
import (
"fmt"
"reflect"
)
func produce(ch chan<- string, i int) {
for j := 0; j < 5; j++ {
ch <- fmt.Sprint(i*10 + j)
}
close(ch)
}
func main() {
numChans := 4
//I keep the channels in this slice, and want to "loop" over them in the select statement
var chans = []chan string{}
for i := 0; i < numChans; i++ {
ch := make(chan string)
chans = append(chans, ch)
go produce(ch, i+1)
}
cases := make([]reflect.SelectCase, len(chans))
for i, ch := range chans {
cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch)}
}
remaining := len(cases)
for remaining > 0 {
chosen, value, ok := reflect.Select(cases)
if !ok {
// The chosen channel has been closed, so zero out the channel to disable the case
cases[chosen].Chan = reflect.ValueOf(nil)
remaining-continue
}
fmt.Printf("Read from channel %#v and received %s\n", chans[chosen], value.String())
}
}
Reflection on Channels
In this code example, we define numChans channels of string and keep them in
a slice chance , defined at line 17. From line 19 to line 23, we make the
channels, put them in the slice, and then for each channel, we start a
goroutine produce on it at line 22.
Now, look at the header of the produce() function at line 7. It takes the
channel ch for write-only, together with an integer i as a parameter. At line
8, a for-loop starts, which puts 5 strings on the channel (see line 9). Then, the
channel is closed at line 11.
At line 25 and beyond, we start using reflection, so we have to import that
package first at line 4. We make a slice of reflect.SelectCase instances called
cases at line 25, and use reflection on it, using for loop (from line 26 to line
28). Then, in the for loop starting at line 31, we use reflect.Select to get the
value out of the instance, to be printed out at line 39. If ok is false, then the
channel has been closed. Therefore, we put nil in the reflection instance,
count down (line 36), and continue the loop.
The use of reflection always has a negative impact on performance, so don’t
overuse it.
Now, that you know all the important concepts of goroutines and channels,
there is a quiz in the next lesson for you to solve.
Exercise: Deduce the Outputs
In this lesson your concepts will be evaluated through a quiz.
Choose one correct answer.
1
Deduce the output of the following program
package main
import (
"fmt"
)
func tel(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
}
}
func main() {
ok := true
ch := make(chan int)
go tel(ch)
for ok {
i, ok := <-ch
fmt.Printf("ok is %t and the counter is at %d\n", ok,
i)
}
}
COMPLETED 0%
1 of 5
We hope that you performed well in the quiz. That is it about the goroutines
and enabling parallelism with different techniques using channels. In the next
chapter, we’ll discuss networking and web applications.
A TCP Server
This lesson introduces TCP servers and provides an implementation and detailed explanation of a TCP server and
client in Go.
WE'LL COVER THE FOLLOWING
• A client-server application
• The server side
• Running locally
• Improvements
• net.Error
Go is very usable for writing web applications. Making HTML-screens with
strings or templating is a good way to write apps that need a graphical
interface.
A client-server application #
We will develop a simple client-server application using the
TCP-protocol and the goroutine paradigm from Chapter 12.
A (web) server application has to respond to requests from
many clients simultaneously. In Go, for every client-request,
a goroutine is spawned to handle the request. We will need
the package net for networking communication
functionality. It contains methods for working with TCP/IP
and UDP protocols, domain name resolution, and so on.
The server side #
The server-code resides in its own program as follows:
package main
import (
"fmt"
"net"
)
func main() {
fmt.Println("Starting the server ...")
// create listener:
listener, err := net.Listen("tcp", "0.0.0.0:3001")
if err != nil {
fmt.Println("Error listening", err.Error())
return // terminate program
}
// listen and accept connections from clients:
for {
conn, err := listener.Accept()
if err != nil {
return // terminate program
}
go doServerStuff(conn)
}
}
func doServerStuff(conn net.Conn) {
for {
buf := make([]byte, 512)
_, err := conn.Read(buf)
if err != nil {
fmt.Println("Error reading", err.Error())
return // terminate program
}
fmt.Printf("Received data: %v", string(buf))
}
}
Server
In main() , we make a net.Listener variable listener, which is the basic
function of a server: to listen for and accept incoming client requests (on IPaddress 0.0.0.0 on port 3001 via the TCP-protocol). This Listen() function can
return a variable err of type error. The waiting for client requests is
performed in an infinite for-loop with listener.Accept() . A client request
makes a connection variable conn of type net.Conn . On this connection, a
separate goroutine doServerStuff() is started which reads the incoming data
in a buffer of size 512 bytes and outputs them on the server terminal; when all
the data from the client has been read, the goroutine stops. For each client, a
separate goroutine is created. The server-code must be executed before any
client can run.
Remark: If you’re running locally, then replace line 10 with listener,
err := net.Listen("tcp", "localhost:50000")
The code for the client is in a separate file as follows:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"net"
)
func main() {
fmt.Println("Starting the server ...")
// create listener:
listener, err := net.Listen("tcp", "0.0.0.0:3001")
if err != nil {
fmt.Println("Error listening", err.Error())
return // terminate program
}
// listen and accept connections from clients:
for {
conn, err := listener.Accept()
if err != nil {
return // terminate program
}
go doServerStuff(conn)
}
}
func doServerStuff(conn net.Conn) {
for {
buf := make([]byte, 512)
_, err := conn.Read(buf)
if err != nil {
fmt.Println("Error reading", err.Error())
return // terminate program
}
fmt.Printf("Received data: %v", string(buf))
}
}
Press the RUN button and wait for the server to get started. Then, open 2 or 3
separate console-windows. In each window, a client process is started by
performing the following steps for each separate terminal:
Type cd usr/local/go/src and press ENTER.
Type go run client.go and press ENTER.
Remark: If you’re running it locally, then only perform the second step:
go run client.go
Running locally #
Of course, the server must be started first. If it is not listening, it can’t be
dialed by a client. If a client process would start without a server listening, the
client stops with the following error message: Error dialing dial tcp :3001:
getsockopt: connection refused .
Open a command prompt in the directory where the server and client
executables are, type server.exe (or just server) on Windows, and ./server on
Linux and ENTER.
The following message appears in the console: Starting the server … This
process can be stopped with CTRL/C. The error: Error listening listen tcp
:3001: bind: Only one usage of each socket address (protocol/network
address/port) is normally permitted means that port 3001 is already occupied
on your system. Change the port number to 3002, for example, compile and
start the server again.
Here is some output of the server (after removing the empty space from the
512-byte string):
Starting the server ...
Received data: IVO says: Hi Server, what's up?
Received data: CHRIS says: Are you a busy server?
Received data: MARC says: Don't forget our appointment tomorrow!
The client establishes a connection with the server through net.Dial . It
receives input from the keyboard os.Stdin in an infinite loop until Q is
entered. Notice the trimming of \n and \r (both only necessary on Windows).
The trimmed input is then transmitted to the server via the Write method of
the connection.
When a client enters Q and stops, the server outputs the following message:
Error reading read EOF .
The net.Dial function is one of the most important functions of networking.
When you Dial into a remote system, the function returns a Conn interface
type, which can be used to send and receive information. The function Dial
neatly abstracts away the network family and transport. So, IPv4 or IPv6 ,
TCP or UDP can all share a common interface. Dialing a remote system on port
80 over TCP, then UDP and lastly TCP over IPv6 looks like this:
package main
import (
"fmt"
"net"
"os"
)
func main() {
conn, err:= net.Dial("tcp", "192.0.32.10:80") // tcp ipv4
checkConnection(conn, err)
conn, err = net.Dial("udp", "192.0.32.10:80") // udp
checkConnection(conn, err)
conn, err = net.Dial("tcp", "[2620:0:2d0:200::10]:80") // tcp ipv6
checkConnection(conn, err)
}
func checkConnection(conn net.Conn, err error) {
if err!= nil {
fmt.Printf("error %v connecting!")
os.Exit(1)
}
fmt.Println("Connection is made with %v", conn)
}
The following program is another illustration of the use of the net package
for opening, writing to and reading from a socket :
package main
import (
"fmt"
"net"
"io"
)
func main() {
var (
host = "www.apache.org"
port = "80"
remote = host + ":" + port
msg string = "GET / \n"
data = make([]uint8, 4096)
read = true
count = 0
)
// create the socket
con, err := net.Dial("tcp", remote)
// send our message. an HTTP GET request in this case
io.WriteString(con, msg)
// read the response from the webserver
for read {
count, err = con.Read(data)
read = (err == nil)
fmt.Printf(string(data[0:count]))
}
con.Close()
}
Socket
The following version has a much better code structure and improves on our
first example of a TCP-server in many ways, using only some 80 lines of code!
package main
import (
"flag"
"net"
"fmt"
"syscall"
)
const maxRead = 25
func main() {
flag.Parse()
if flag.NArg() != 2 {
panic("usage: host port")
}
hostAndPort := fmt.Sprintf("%s:%s", flag.Arg(0), flag.Arg(1))
listener := initServer(hostAndPort)
for {
conn, err := listener.Accept()
checkError(err, "Accept: ")
go connectionHandler(conn)
}
}
func initServer(hostAndPort string) *net.TCPListener {
serverAddr, err := net.ResolveTCPAddr("tcp", hostAndPort)
checkError(err, "Resolving address:port failed: `" + hostAndPort + "'")
listener, err := net.ListenTCP("tcp", serverAddr)
checkError(err, "ListenTCP: ")
println("Listening to:
", listener.Addr().String())
return listener
}
func connectionHandler(conn net.Conn) {
connFrom := conn.RemoteAddr().String()
println("Connection from: ", connFrom)
sayHello(conn)
for {
var ibuf []byte = make([]byte, maxRead + 1)
length, err := conn.Read(ibuf[0:maxRead])
ibuf[maxRead] = 0 // to prevent overflow
switch err {
case nil:
handleMsg(length, err, ibuf)
case syscall.EAGAIN: // try again
continue
default:
goto DISCONNECT
}
}
DISCONNECT:
err := conn.Close()
println("Closed connection: ", connFrom)
checkError(err, "Close: ")
}
func sayHello(to net.Conn) {
obuf := []byte{'L', 'e', 't', '\'', 's', ' ', 'G', 'O', '!', '\n'}
wrote, err := to.Write(obuf)
checkError(err, "Write: wrote " + string(wrote) + " bytes.")
}
func handleMsg(length int, err error, msg []byte) {
if length > 0 {
print("<", length, ":")
for i := 0; ; i++ {
if msg[i] == 0 {
break
}
fmt.Printf("%c", msg[i])
}
print(">")
}
}
func checkError(error error, info string) {
if error != nil {
panic("ERROR: " + info + " " + error.Error())
}
}
// terminate program
Simple TCP Server
Improvements #
What are the improvements?
The server address and port are not hard-coded in the program, but are
given on the command-line and read in via the flag package. Note the
use of flag.NArg() to signal when the expected 2 arguments are not
given:
if flag.NArg() != 2 {
panic("usage: host port")
}
The arguments are then formatted into a string via the fmt.Sprintffunction :
hostAndPort := fmt.Sprintf("%s:%s", flag.Arg(0), flag.Arg(1))
The server address and port are controlled in the function initServer
through net.ResolveTCPAddr , and this function returns a
*net.TCPListener .
For each connection, the function connectionHandler is started as a
goroutine. This begins by showing the address of the client with
conn.RemoteAddr() .
It writes a promotional Go-message to the client with the function
conn.Write .
It reads from the client in chunks of 25 bytes and prints them one by one;
in case of an error in the read, the infinite read-loop is left via the default
switch clause and that client-connection is closed. In case the OS issues an
EAGAIN error, the read is retried.
All error-checking is refactored in a separate function checkError , which
issues a panic with a contextual error-message in the case of an error
occurring.
Start this server program on the command-line with: simple_tcp_server
localhost 50000 and start a few clients with client.go in separate command-
windows. A typical server output from 2 client-connections follows, where we
see that the clients each have their own address:
E:\Go\GoBoek\code examples\chapter 14>simple_tcp_server localhost 50000
Listening to: 127.0.0.1:50000
Connection from: 127.0.0.1:49346
<25:Ivo says: Hi server, do y><12:ou hear me ?>
Connection from: 127.0.0.1:49347
<25:Marc says: Do you remembe><25:r our first meeting serve><2:r?>
net.Error #
The net package returns errors of type error, following the usual convention,
but some of the error implementations have additional methods defined by
the net.Error interface:
package net
type Error interface {
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
...
}
Client code can test for a net.Error with a type assertion and then distinguish
transient network errors from permanent ones. For instance, a web crawler
might sleep and retry when it encounters a temporary error and give up
otherwise.
// in a loop - some function returns an error err
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
time.Sleep(1e9)
continue // try again
}
if err != nil {
log.Fatal(err)
}
To make communication interactive between the client and server, it’s a good
idea to use a web server, and log the responses as HTML forms that are much
more readable and responsive. In the next lesson, you’ll learn how to devise a
web server.
A Simple Web Server
This lesson explains how to design a web server that provides services over the web.
WE'LL COVER THE FOLLOWING
• Simple web server
Simple web server #
HTTP is a higher-level protocol than TCP, and it describes how a web server
communicates with client-browsers. Specifically for that purpose, Go has
net/http package, which we will now explore.
We will start with some really simple concepts. First, let’s write a Hello
world! web server. We import http , and our web server is started with the
function http.ListenAndServe("0.0.0.0:3000", nil) , which returns nil if
everything is OK or an error otherwise (0.0.0.0 can be omitted from the
address, 3000 is the chosen port number).
A web-address is represented by the type http.URL , which has a Path field
that contains the URL as a string. Client-requests are described by the type
http.Request , which has a URL field.
If the request req is a POST of an Html-form, and var1 is the name of an Html
input-field on that form, then the value entered by the user can be captured in
Go-code with: req.FormValue("var1") . This also works when var1 is a
parameter given via the url : https://1dkne4jl5mmmm.educative.run/?
var1=value (in your case URL will be different). An alternative is to first call
request.ParseForm() , and the value can then be retrieved as the 1st return
parameter of request.Form["var1"] , like in:
var1, found := request.Form["var1"]
Remark: If you’re running it locally, http://localhost:nnnn/?var1=value
will work.
The 2nd parameter found is then true. If var1 was not on the form, found
becomes false. The Form field is in fact of type map[string][]string . The web
server sends an http.Response , its output is sent on an http.ResponseWriter
object. This object assembles the HTTP server’s response; by writing to it, we
send data to the HTTP client. Now we still have to program what the web
server must do, how it handles a request. This is done through the function
http.HandleFunc . In this example, it says that if the root “/” is requested (or
any other address on that server) the function HelloServer is called. This
function is of the type http.HandlerFunc , and they are most often named
Prefhandler with some prefix Pref . The http.HandleFunc registers a handler
function (here HelloServer ) for incoming requests on /. The / can be replaced
by more specific URL’s like /create, /edit, and so on; for each specific URL, you
can then define its corresponding handler-function.
This function has as 2nd parameter the request req . Its first parameter is the
ResponseWriter , to which it writes a string composed of Hello and
r.URL.Path[1:] . The trailing [1:] means create a sub-slice of Path from the 1st
character to the end. It drops the leading / from the pathname. This writing is
done with the function fmt.Fprintf() ; another possibility is io.WriteString(w,
"hello, world!\n")
The 1st parameter is a requested path and the 2nd parameter is a reference to
a function to call when the path is requested.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"net/http"
"log"
)
func HelloServer(w http.ResponseWriter, req *http.Request) {
fmt.Println("Inside HelloServer handler")
fmt.Fprint(w, "Hello, " + req.URL.Path[1:])
}
func main() {
http.HandleFunc("/",HelloServer)
err := http.ListenAndServe("0.0.0.0:3000", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err.Error())
}
}
Remark: Change line 15 as err :=
http.ListenAndServe("localhost:8080", nil) , if you’re running the server
locally.
Click the Run button, and wait for the terminal to start. Once it starts, perform
the following steps:
Click on this link
Step 1
1 of 4
Broswer opens a page
Step 1
2 of 4
Add "/world" at the end of the URL and reload the page
Step 2
3 of 4
Server appends the variable after "hello,"
Step 2
4 of 4
Remark: If you’re running locally, open your browser with the
address(URL): http://localhost:8080 and then append /world at the end of
URL. After reloading, in the browser window the text: Hello, world
appears.
The fmt.Println statement prints on the server console; logging what was
requested inside every handler could be somewhat more useful.
Now that you’re familiar with how to devise a web server, in the next lesson,
you’ll learn how to poll and read a website with Go.
Polling Websites and Reading Web Page
This lesson gives insight into how to check the status of a website with Go and how to read a page on the web.
WE'LL COVER THE FOLLOWING
• Introduction
• Explanation
Introduction #
Sending a website a very simple request and seeing how the website responds
is known as polling a website.
Explanation #
Consider the following example where a request includes only an HTTP
header.
package main
import (
"fmt"
"net/http"
)
var urls = []string{
"http://www.google.com/",
"http://golang.org/",
"http://blog.golang.org/",
}
func main() {
// Executes an HTTP HEAD request for all URL's
// and returns the HTTP status string or an error string.
for _, url := range urls {
resp, err := http.Head(url)
if err != nil {
fmt.Println("Error:", url, err)
}
fmt.Println(url, ": ", resp.Status)
}
}
Requesting Polled URLs
In the program above, we import the package net/http (see line 4). All URLs
in an array of strings urls (defined at line 7) are polled. At line 16, we start
an iteration over urls with a for-range loop. At line 17, a simple http.Head()
request is sent to each url to see how they react. The function’s signature is:
func Head(url string) (r *Response, err error) . When there is an error, we
print it at line 19. If no error, the Status of resp is printed at line 21.
package main
import (
"fmt"
"net/http"
"io/ioutil"
"log"
)
func main() {
res, err := http.Get("http://www.google.com")
CheckError(err)
data, err := ioutil.ReadAll(res.Body)
CheckError(err)
fmt.Printf("Got: %q", string(data))
}
func CheckError(err error) {
if err != nil {
log.Fatalf("Get: %v", err)
}
}
Fetch
In the program above, we show the Html content of a web page by calling
http.Get() at the front page of google at line 10. Get returns a result and a
possible error. The error-handling here is performed by calling a function
CheckError(err) at line 11, passing it the error as a parameter.
Now, look at the header of the function CheckError(err error) at line 17.
When there is an error (line 18), we log it as fatal which stops the program
(see line 19). When the program arrives at line 12, there is no error. The
response res returned from Get has the content in a field Body . We read this
content in one slice of bytes called data with ioutil.ReadAll at line 12. A
possible error is again handled by CheckError at line 13. Then, we convert the
slice to a string and print it out at line 14.
Here is a sample error output from CheckError when trying to read a nonexisting web’s site homepage: 2011/09/30 11:24:15 Get: Get
http://www.google.bex: dial tcp www.google.bex:80: GetHostByName: No such
host is known .
Other useful functions in the http package which we will be using are:
http.Redirect(w ResponseWriter, r *Request, url string, code int) : this
redirects the browser to url (can be a path relative to the request path)
and a statuscode code.
http.NotFound(w ResponseWriter, r *Request) : this replies to the request
with an HTTP 404 not found error.
http.Error(w ResponseWriter, error string, code int) : this replies to the
request with the specified error message and HTTP code.
A useful field of an http.Request object req is: req.Method , this is a
string which contains GET or POST according to how the web page was
requested.
All HTTP status codes are defined as Go-constants, for example:
http.StatusContinue = 100
http.StatusOK = 200
http.StatusFound = 302
http.StatusBadRequest = 400
http.StatusUnauthorized = 401
http.StatusForbidden = 403
http.StatusNotFound = 404
http.StatusInternalServerError = 500
You can set the content header with w.Header().Set("Content-Type", "../..") ,
e.g., when sending Html-strings in a web application, execute
w.Header().Set("Content-Type", "text/html") before writing the output, but
this is normally not necessary.
Now that you’re familiar with the basics of a web server, you’re ready to learn
how to make a simple web application of your own.
Writing a Simple Web Application
This lesson provides a program, and detailed description to design a web application, printing response in the form
of HTML.
WE'LL COVER THE FOLLOWING
• A web application
A web application #
In the following program from line 7 to line 10, we see how the HTML,
needed for our web form (which is a simple input form with text box and
submit button) is stored in a multi-line string constant form.
The program starts a webserver on port 3000 at line 39. It uses a combined if
statement so that, whenever an error occurs, the web server stops with a
panic statement (see line 40). Before starting a webserver, we see two so-
called routing statements:
http.HandleFunc("/test1", SimpleServer) at line 37
http.HandleFunc("/test2", FormServer) at line 38
This means that whenever the URL ends with /test1 , the webserver will
execute the function SimpleServer , and the same for /test2 with FormServer .
Now, look at the header of SimpleServer() at line 13. It outputs a hello world
string in the browser by writing the corresponding HTML string to io with
the WriteString method at line 14.
Now, look at the header of FormServer() at line 19. The browser can request
_two different HTML methods: GET and POST . The code for FormServer uses a
switch (starting at line 22) to distinguish between the 2 possibilities. If this
URL is requested by the browser initially, then the request has a GET method,
and the response is the constant form, as stated at line 25. When entering
something in the text box and clicking the button, a POST request is issued. In
the POST case, the content of the text box with a name in it is retrieved with
the request.FormValue("in") , as you can see at line 32, and written back to the
browser page.
Start the program in a console and open a browser with the URL
https://1dkne4jl5mmmm.educative.run/test2 ( in your case the URL will be
different) to test this program:
Remark: Change line 39 to if err := http.ListenAndServe(":8088",
nil); err != nil { if you’re running the server locally.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"net/http"
"io"
)
const form = `<html><body><form action="#" method="post" name="bar">
<input type="text" name="in"/>
<input type="submit" value="Submit"/>
</form></html></body>`
/* handle a simple get request */
func SimpleServer(w http.ResponseWriter, request *http.Request) {
io.WriteString(w, "<h1>hello, world</h1>")
}
/* handle a form, both the GET which displays the form
and the POST which processes it.*/
func FormServer(w http.ResponseWriter, request *http.Request) {
w.Header().Set("Content-Type", "text/html")
switch request.Method {
case "GET":
/* display the form to the user */
io.WriteString(w, form );
case "POST":
/* handle the form data, note that ParseForm must
be called before we can extract form data with Form */
// request.ParseForm();
//io.WriteString(w, request.Form["in"][0])
// easier method:
io.WriteString(w, request.FormValue("in"))
}
}
func main() {
http.HandleFunc("/test1", SimpleServer)
http.HandleFunc("/test2", FormServer)
if err := http.ListenAndServe("0.0.0.0:3000", nil); err != nil {
panic(err)
}
}
Click the Run button, and wait for the terminal to start. Once it starts, perform
the following steps:
Click on this link
Step 1
1 of 6
Browser opens a page
Step 1
2 of 6
Add "/test1" at the end of URL and reload the browser
Step 2
3 of 6
Add "/test2" at the end of URL and reload the browser
Step 3
4 of 6
Type anything in the text box and press Submit button
Step 4
5 of 6
Server prints the text on the screen
Step 4
6 of 6
Remark: If you’re running the program locally, try URLs
http://localhost:8088/test1 and http://localhost:8088/test2.
When using constant strings, which represent html-text it is important to
include the
<html><body>... </html></body>
to let the browser know that it receives html. Even safer is to set the header
with the content-type text/html before writing the response in the handler:
w.Header().Set("Content-Type", "text/html")
The content-type the browser thinks it can be retrieved with the function:
http.DetectContentType([]byte(form))
Now that you know how to write a simple web application, the next lesson
brings you a related challenge for you to solve.
Challenge: Web Application for Statistics
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Develop a web application that lets the user put in a series of numbers, and
prints out the numbers, their count, their mean, and their median, like in the
following screenshot:
1 of 2
2 of 2
Remark: Use 0.0.0.0:3000 or localhost:3000 or simply :3000 for the
connection. If port 3000 is already occupied, use localhost:9001 for
example.
Try to attempt the challenge below. Feel free to view the solution, after giving
some shots. Good Luck!
Hint: Do not forget to import log , sort , strconv , and strings packages.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"net/http"
)
type statistics
numbers
mean
median
}
struct {
[]float64
float64
float64
const form = `<html><body><form action="/" method="POST">
<h1>Statistics</h1>
<h5>Compute base statistics for a given list of numbers</h5>
<label for="numbers">Numbers (comma or space-separated):</label><br>
<input type="text" name="numbers" size="30"><br />
<input type="submit" value="Calculate">
</form></html></body>`
const error = `<p class="error">%s</p>`
var pageTop = ""
var pageBottom = ""
// Define a root handler for requests to function homePage, and start the webserver combined
func main() {
}
// Write an HTML header, parse the form, write form to writer and make request for numbers
func homePage(writer http.ResponseWriter, request *http.Request) {
// write your code here
}
// Capture the numbers from the request, and format the data and check for errors
func processRequest(request *http.Request) ([]float64, string, bool) {
// write your code here
return nil, "", false
}
// sort the values to get mean and median
func getStats(numbers []float64) (stats statistics) {
// write your code here
return stats
}
// seperate function to calculate the sum for mean
func sum(numbers []float64) (total float64) {
// write your code here
return 0
}
// seperate function to calculate the median
func median(numbers []float64) float64 {
// write your code here
return 0
}
func formatStats(stats statistics) string {
return fmt.Sprintf(`<table border="1">
<tr><th colspan="2">Results</th></tr>
<tr><td>Numbers</td><td>%v</td></tr>
<tr><td>Count</td><td>%d</td></tr>
<tr><td>Mean</td><td>%f</td></tr>
<tr><td>Median</td><td>%f</td></tr>
</table>`, stats.numbers, len(stats.numbers), stats.mean, stats.median)
}
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Web Application for Statistics
This lesson discusses the solution to the challenge given in the previous lesson.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"log"
"net/http"
"sort"
"strconv"
"strings"
)
type statistics
numbers
mean
median
}
struct {
[]float64
float64
float64
const form = `<html><body><form action="/" method="POST">
<h1>Statistics</h1>
<h5>Compute base statistics for a given list of numbers</h5>
<label for="numbers">Numbers (comma or space-separated):</label><br>
<input type="text" name="numbers" size="30"><br />
<input type="submit" value="Calculate">
</form></html></body>`
const error = `<p class="error">%s</p>`
var pageTop = ""
var pageBottom = ""
func main() { // Define a root handler for requests to function homePage, and start the webse
http.HandleFunc("/", homePage)
if err := http.ListenAndServe(":3000", nil); err != nil {
log.Fatal("failed to start server", err)
}
}
func homePage(writer http.ResponseWriter, request *http.Request) { // Write an HTML header, p
writer.Header().Set( Content-Type , text/html )
err := request.ParseForm() // Must be called before writing response
fmt.Fprint(writer, pageTop, form)
if err != nil {
fmt.Fprintf(writer, error, err)
} else {
if numbers, message, ok := processRequest(request); ok {
stats := getStats(numbers)
fmt.Fprint(writer, formatStats(stats))
} else if message != "" {
fmt.Fprintf(writer, error, message)
}
}
fmt.Fprint(writer, pageBottom)
}
func processRequest(request *http.Request) ([]float64, string, bool) { // Capture the numbers
var numbers []float64
if slice, found := request.Form["numbers"]; found && len(slice) > 0 {
text := strings.Replace(slice[0], ",", " ", -1)
for _, field := range strings.Fields(text) {
if x, err := strconv.ParseFloat(field, 64); err != nil {
return numbers, "'" + field + "' is invalid", false
} else {
numbers = append(numbers, x)
}
}
}
if len(numbers) == 0 {
return numbers, "", false // no data first time form is shown
}
return numbers, "", true
}
func getStats(numbers []float64) (stats statistics) { // sort the values to get mean and medi
stats.numbers = numbers
sort.Float64s(stats.numbers)
stats.mean = sum(numbers) / float64(len(numbers))
stats.median = median(numbers)
return
}
func sum(numbers []float64) (total float64) { // seperate function to calculate the sum for m
for _, x := range numbers {
total += x
}
return
}
func median(numbers []float64) float64 { // seperate function to calculate the median
middle := len(numbers) / 2
result := numbers[middle]
if len(numbers)%2 == 0 {
result = (result + numbers[middle-1]) / 2
}
return result
}
func formatStats(stats statistics) string {
return fmt.Sprintf(`<table border="1">
<tr><th colspan="2">Results</th></tr>
<tr><td>Numbers</td><td>%v</td></tr>
<tr><td>Count</td><td>%d</td></tr>
<tr><td>Mean</td><td>%f</td></tr>
<tr><td>Median</td><td>%f</td></tr>
</table>`, stats.numbers, len(stats.numbers), stats.mean, stats.median)
}
In the code above, we define a struct statistics at line 11 to contain all the
input data: numbers of type []float64 and the calculation results on them:
mean and median .
As we have seen before, the HTML string for the web page is contained in a
constant form, defined from line 17 to line 23. We also define a constant for
error reporting at line 25. The main() routine is very succinct, defining a root
handler for requests to a function homePage , and starting the web server
combined with error-handling from line 32 to line 34.
Now, look at the header of the homePage() function at line 37. It starts by
writing an HTML header at line 38 and then calls the ParseForm method at line
39. Then, we use Fprint at line 40 to write the form HTML to the
ResponseWriter writer. We check for an error at line 41. If there is one, we
print it out at line 42. If not, we call the processRequest function at line 44.
This takes the request value as a parameter, and returns the input numbers.
Now, look at the header of the processRequest() function at line 54. At line 56,
we see that the result of request.Form["numbers"] is captured in a slice. Line
57 replaces all commas in the input by spaces. Then, we iterate over all the
fields at line 58. We convert each field to a float at line 59. If there is an error,
we return an error string as the 2nd return value, and false as the 3rd at line
60. If no error, we append the float to numbers at line 62. Line 66 checks
whether there are input numbers; if not, this is the first time the form is
shown. If everything goes ok, we return the numbers slice together with an
empty error string and true at line 69.
Coming back to line 44, we call the function getStats on the numbers at line
45, if everything is ok. The results from getStats are stored in struct value
stats , which are sent to the web server output with Fprint at line 46, after
being transformed by formatStats . Line 47 handles the case of a possible
error and line 51 prints a pageBottom output.
Now, look at the header of the getStats() function at line 72. It first sorts the
numbers at line 74 using the sort package. Then, it calculates the mean and
median values at line 75 and line 76, respectively, and stores them in the
stats struct, which is returned at line 77.
We have separate functions for calculating the sum and median of values. To
calculate the sum a function sum is implemented from line 80 to line 85. To
calculate the median a function median is implemented from line 87 to line
94. They use simple mathematics to calculate these results.
Now, look at the header of formatStats function at line 96. It returns an
HTML
markup and uses Sprintf to embed the input values and the calculated
values. More specifically, it substitutes in the values of stats.numbers ,
len(stats.numbers) , stats.mean , stats.median in respectively %v, %d, %f and
%f (from line 98 to line 102).
That is it for the solution. In the next lesson, we’ll discuss how to make an
application robust over the web.
Making a Web Application Robust
This lesson shows how to make an application robust by making it able to withstand a minor panic.
WE'LL COVER THE FOLLOWING
• Example
When a handler function in a web application panics our web server simply
terminates. This is not good: a web server must be a robust application, able
to withstand what perhaps is a temporary problem. A first idea could be to
use defer/recover in every handler-function, but this would lead to much
duplication of code. Applying the error-handling scheme with closures is a
much more elegant solution. Here, we show this mechanism applied to the
simple web-server made previously, but it can just as easily be applied in any
web-server program.
To make the code more readable, we create a function type for a page
handler-function:
type HandleFnc func(http.ResponseWriter,*http.Request)
Our errorHandler function applied here becomes the function logPanics :
func logPanics(function HandleFnc) HandleFnc {
return func(writer http.ResponseWriter, request *http.Request) {
defer func() {
if x := recover(); x != nil {
log.Printf("[%v] caught panic: %v", request.RemoteAddr, x)
}
}()
function(writer, request)
}
}
And we wrap our calls to the handler functions within logPanics :
http.HandleFunc("/test1", logPanics(SimpleServer))
http.HandleFunc("/test2", logPanics(FormServer))
The handler-functions should contain panic calls or a kind of check(error)
function.
Example #
The complete code is listed here:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"net/http"
"log"
"io"
)
type HandleFnc func(http.ResponseWriter,*http.Request)
const form = `<html><body><form action="#" method="post" name="bar">
<input type="text" name="in"/>
<input type="submit" value="Submit"/>
</form></html></body>`
/* handle a simple get request */
func SimpleServer(w http.ResponseWriter, request *http.Request) {
io.WriteString(w, "<h1>hello, world</h1>")
}
/* handle a form, both the GET which displays the form
and the POST which processes it.*/
func FormServer(w http.ResponseWriter, request *http.Request) {
w.Header().Set("Content-Type", "text/html")
switch request.Method {
case "GET":
/* display the form to the user */
io.WriteString(w, form );
case "POST":
/* handle the form data, note that ParseForm must
be called before we can extract form data with Form */
// request.ParseForm();
//io.WriteString(w, request.Form["in"][0])
// easier method:
io.WriteString(w, request.FormValue("in"))
}
}
func main() {
http.HandleFunc("/test1", logPanics(SimpleServer))
http.HandleFunc("/test2", logPanics(FormServer))
if err := http.ListenAndServe("0.0.0.0:3000", nil); err != nil {
panic(err)
}
}
func logPanics(function HandleFnc) HandleFnc {
return func(writer http.ResponseWriter, request *http.Request) {
defer func() {
if x := recover(); x != nil {
log.Printf("[%v] caught panic: %v", request.RemoteAddr, x)
}
}()
function(writer, request)
}
}
Follow the same procedure to run the program discussed previously when
making a web application.
Remark: Change line 42 to if err := http.ListenAndServe(":8088",
nil); err != nil { if you’re running it locally. And try URLs
http://localhost:8088/test1 and http://localhost:8088/test2.
A better alternative to writing an application is using templates. In the next
lesson, there is a detailed discussion on how to write an application using
HTML templates.
Writing Web Application with Templates
This lesson shows how to build a wiki application which is more detailed than the previous one. To make this
application quickly and effectively, a template package is used from the Go standard library.
WE'LL COVER THE FOLLOWING
• Building a wiki application
Building a wiki application #
The following program is a working web-application for a wiki, which is a
collection of pages that can be viewed, edited, and saved in under 100 lines of
code. It is the code lab wiki tutorial from the Go site, one of the best Gotutorials in existence. It is certainly worthwhile to work through the complete
code lab to see and better understand how the program builds up. Here, we
will give a complimentary description of the program in its totality, from a
top-down view.
The program is a web server, so it must be started on the command-line; let’s
say on port 3000. The browser can ask to view the content of a wiki-page with
a URL, like https://1dkne4jl5mmmm.educative.run/view/page1.
The text of this page is then read from a file and shown on the web page; this
includes a hyperlink to edit the wiki page (
https://1dkne4jl5mmmm.educative.run/edit/page1). The edit page shows the
contents in a text frame. The user can change the text and save it to its file
with a submit button Save; then, the same page with the modified content is
viewed. If the page that is asked for viewing does not exist (e.g.,
https://1dkne4jl5mmmm.educative.run/edit/page999), the program detects
this and redirects immediately to the edit-page, so that the new wiki-page can
be made and saved.
The wiki-page needs a title and text content. It is modeled in the program by
the following struct, where the content is a slice of bytes named Body :
type Page struct {
Title string
Body []byte
}
In order to maintain our wiki-pages outside of the running program, we will
use simple text-files as persistent storage.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
<h1>Editing {{.Title |html}}</h1>
<form action="/save/{{.Title |html}}" method="POST">
<div><textarea name="body" rows="20" cols="80">{{printf "%s" .Body|html}}</textarea></div>
<div><input type="submit" value="Save"></div>
</form>
Remark: Change line 92 to log.Fatal(http.ListenAndServe(":8080",
nil)) if you’re running this program locally.
Let us go through the code:
First we import the necessary packages, http of course, because we are
going to construct a web server but also io/ioutil for easy reading and
writing of the files, regexp for validating URL-input, and template for
dynamically creating our Html-files. The package template
(html/template) implements data-driven templates for generating HTML
output, which are safe against code injection. It provides the same
interface as the package text/template and should be used instead of
text/template whenever the output is HTML.
We want to stop hackers input which could harm our server, so we will
check the user input in the browser URL (which is the title of the wiki
page) with the following regular expression:
var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)
$")
This will be controlled in the function makeHandler .
We must have a mechanism to insert our Page structs into the title and
content of a web page; this is done as follows with the use of the template
package:
First, make the html-templatefile(s) in an editor, e.g., view.html:
<h1>{{.Title |html}}</h1>
<p>[<a href="/edit/{{.Title |html}}">edit</a>]</p>
<div>{{printf "%s" .Body |html}}</div>
The fields, which are to be inserted from a data-structure, are put
between {{ }}, here {{.Title|html}} and {{printf "%s" .Body
|html}} , from the Page struct (of course this can be very complex
html. However, it is simplified as much as possible to show the
principle.
The template.Must(template.ParseFiles("edit.html", "view.html"))
function transforms this into a *template.Template . For efficiency
reasons, we only do this parsing once in our program.
In order to construct the page out of the template and the struct, we
must use the function:
templates.ExecuteTemplate(w, tmpl+".html", p)
It is called upon a template, gets the Page struct p to be substituted
in the template as a parameter, and writes to the ResponseWriter w .
This function must be checked on its error-output. In case there is an
error, we call http.Error to signal this. This code will be called many
times in our application, so we extract it into a separate function
renderTemplate .
In main() , our web server is started with ListenAndServe on port 3000,
but we first define some handler functions for URLs, which start with
view, edit, and save after https://1dkne4jl5mmmm.educative.run/. In
most web server applications, this forms a series of URL-paths with
handler-functions analogous to a routing table in MVC frameworks like
Ruby and Rails, Django or ASP.NET MVC. The requested URL is matched
with these paths. The longer paths match first; if not matched with
anything else, the handler for / is called.
Here, we need to define 3 handlers, and because this setup contains
repetitive code, we isolate it in a makeHandler function. This is a rather
special higher-order function, which is well worth studying. It has a
function as its first parameter, and it returns a function which is a
closure:
func makeHandler(fn func(http.ResponseWriter, *http.Request, string))
http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !validPath.MatchString(r.URL.Path) {
fmt.Fprintf(w, "Wrong URL!", r.URL.Path)
return
}
title := r.URL.Path[lenPath:]
if title == "" {
http.NotFound(w, r)
return
}
fn(w, r, title)
}
}
This closure takes the enclosing function variable fn in order to
construct its return value, but before doing that, it validates the URL
input with validPath.MatchString(r.URL.Path) . If the URL does not start
with edit, save or view, and does not consist out of letters and digits, a
NotFound error is signaled (test this with, e.g.,
https://1dkne4jl5mmmm.educative.run/view/page++). The viewhandler ,
edithandler and savehandler , which are the parameters for makeHandler
in main() , must all be of the same type as fn .
The viewhandler tries to read a text file with the given title; this is done
through the load() function which constructs the filename and reads the
file with ioutil.ReadFile . If the file is found its contents go into a local
string body. A pointer to a Page struct is made literally with it:
&Page{Title: title, Body: body}
This is returned to the caller together with nil for the error.
The struct is then merged with the template with renderTemplate . In case
of an error, which means the wiki-page does not yet exist on disk, the
error is returned to viewHandler() , where an automatic redirect is done
to request an edit-page with that title.
The edithandler is almost the same: try to load the file. If found, render
the edit-template with it; in case of an error, make a new Page object
with that title and render it also.
Saving a page content is done through the Save button on the edit-page.
This button resides in the Html form, which starts with:
<form action="/save/{{.Title}}" method="POST">
This means that when posting, a request with a URL of the form
https://1dkne4jl5mmmm.educative.run/save/{Title} (with the title
substituted through the template) is sent to the web server. For such a
URL, we have defined a handler function saveHandler() . With the
FormValue() method of the request, it extracts the contents of the text
area-field named body, constructs a Page object with this info and tries to
store this page with the save() function. In case this fails, an http.Error
is returned to be shown in the browser. When it succeeds, the browser is
redirected to view the same page. The save() function is very simple.
Write the Body field of the Page struct in a file called filename with the
function ioutil.WriteFile() . It uses {{ printf "%s" .Body|html}} .
Remark: Try the following URLs; they should work if you’re running the
code locally: http://localhost:8080/view/page1,
http://localhost:8080/edit/page1, http://localhost:8080/view/page999,
http://localhost:8080/edit/page999, http://localhost/save/{Title}
That’s how the template package helps in devising such an application. Let’s
explore further functionalities provided by this package in the next lesson.
Exploring the template Package
This lesson provides in-depth knowledge on functionalities and what support the template package offers.
WE'LL COVER THE FOLLOWING
• Introduction
• Field substitution
• Validation of the templates
• If-else construct
• Dot and with-end
• Template variables $
• Range-end
• Prede ned template functions
Introduction #
The documentation for the template package is available here. In the last
lesson, we used templates to merge data from a (data) struct(ure)s with HTMLtemplates. This is very useful, indeed, for building web applications, but
template techniques are more general than this. Data-driven templates can be
made for generating textual output, and HTML is only a special case of this.
A template is executed by merging it with a data structure, in many cases a
struct or a slice of structs. It rewrites a piece of text on the fly by substituting
elements derived from data items passed to templ.Execute() . Only the
exported data items are available for merging with the template. Actions can
be data evaluations or control structures and are delimited by “{{” and “}}”.
Data items may be values or pointers. The interface hides the indirection.
Field substitution #
To include the content of a field within a template, enclose it within double
curly braces and add a dot at the beginning, e.g. if Name is a field within a
struct and its value needs to be substituted while merging, then include the
text {{.Name}} in the template. This also works when Name is a key to a map.
A new template is created with template.New , which takes the template name
as a string parameter. As we already encountered previously, the Parse
methods generate a template as an internal representation by parsing some
template definition string. Use ParseFile when the parameter is the path to a
file with the template definition. When there was a problem with the parsing,
their second return parameter is an Error != nil . In the last step, the content
of a data structure p is merged with the template through the Execute
method, and written to its first argument, which is an io.Writer . Again, an
error can be returned.
This is illustrated in the following program, where the output is written to the
console through os.Stdout :
package main
import (
"os"
"fmt"
"text/template"
)
type Person struct {
Name string
nonExportedAgeField string
}
func main() {
t := template.New("hello")
t, _ = t.Parse("hello {{.Name}}!")
p := Person{Name:"Mary", nonExportedAgeField: "31"} // data
if err := t.Execute(os.Stdout, p); err != nil {
fmt.Println("There was an error:", err.Error())
}
}
Parsing the Field
To use templates, we need to import the package text/template (line 5). We
also define a struct Person at line 8 with two fields. The first field is Name ,
which is exported (it begins with a capital letter) and the second is
nonExportedAgeField which is not exported ( at line 10).
Line 14 creates a new template t with the name hello. The template
definition is parsed at line 15. Line 16 makes an instance of Person : data to be
shown through the template.
Calling the Execute method on t at line 17 with the data as the second
parameter shows the output. It is combined with error-handling in the same
line. If there was an error during parsing, it is shown through line 18.
Our data structure contains a non-exported field, and when we try to merge
this through a definition string like:
t, _ = t.Parse("your age is {{.nonExportedAgeField}}!")
the following error occurs: There was an error: template: hello:1:8:
executing "hello" at <.nonExportedAgeField>: nonExportedAgeField is an
unexported field of struct type main.Person .
If you simply want to substitute the second argument of Execute() , use {{.}}.
When this is done in a browser context, filter the content with the HTML
filter, like this: {{html .}} or with a field FieldName {{ .FieldName |html }}
The |html part asks the template engine to pass the value of FieldName
through the HTML-formatter before outputting it, which escapes special
HTML characters (such as replacing > with &gt ;). This will prevent user data
from corrupting the form HTML.
Validation of the templates #
To check whether the template definition syntax is correct, use the Must
function executed on the result of the Parse .
package main
import (
"text/template"
"fmt"
)
func main() {
tOk := template.New("ok")
//a valid template, so no panic with Must:
template.Must(tOk.Parse("/* and a comment */ some static text: {{ .Name }}"))
fmt.Println("The first one parsed OK.")
fmt.Println("The next one ought to fail.")
tErr := template.New("error_template")
template.Must(tErr.Parse(" some static text {{ .Name }}"))
}
Template Validation
In the code above, line 8 creates a new template tOk with the name ok. The
template definition is parsed at line 10. To check for errors while parsing the
template, you can use the template.Must method, as is done at line 10. This
panics if the error is non-nil.
Line 13 creates a new template tErr with the name error_template. The
template definition is parsed at line 14. The Must function finds an error and
panics because {{ .Name } lacks a closing brace.
If-else construct #
The output from a template resulting from Execute contains static text, and
text contained within {{ }}, which is called a pipeline. For example, running
this code:
t := template.New("template test")
t = template.Must(t.Parse("This is just static text. \n{{\"This is pipelin
e data - because it is
evaluated within the double braces.\"}} {{`So is this, but within revers
e quotes.`}}\n"))
t.Execute(os.Stdout, nil)
gives this output:
This is just static text.
This is pipeline data - because it is evaluated within the double brace
s. So is this, but within reverse quotes.
Now, we can condition the output of pipeline data with if-else-end, if the
pipeline is empty, like in:
{{if ``}} Will not print. {{end}}
then, the if condition evaluates to false and nothing will be output, but with
this:
{{if `anything`}} Print IF part. {{else}} Print ELSE part.{{end}}
Print IF part will be output.
This is illustrated in the following program:
package main
import (
"os"
"text/template"
)
func main() {
tEmpty := template.New("template test")
//empty pipeline following if
tEmpty.Execute(os.Stdout, nil)
tEmpty = template.Must(tEmpty.Parse("Empty pipeline if demo: {{if ``}} Will not print
tWithValue := template.New("template test")
//non empty pipeline following if condition
tWithValue = template.Must(tWithValue.Parse("Non empty pipeline if demo: {{if `anythi
tWithValue.Execute(os.Stdout, nil)
tIfElse := template.New("template test")
//non empty pipeline following if condition
tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} Print IF part.
tIfElse.Execute(os.Stdout, nil)
}
Template with if-else
In the code above, at line 9, a new template tEmpty is made. When it is parsed
at line 12, the {{if ``}} doesn’t execute anything ( `` is false). So, this
template doesn’t produce any output when executed.
At line 14, a new template tWithValue is made. When it is parsed at line 16,
the {{if `anything`}} executes ( `anything` is true). So, this template does
produce its output when executed.
At line 19, a new template Else is made. When it is parsed at line 21, the {{if
`anything`}} executes ( `anything` is true). So, this template produces the
output of the if branch, not that of the else branch. This produces the
following output:
Non-empty pipeline if demo:
if-else demo:
Will print.
Print IF part.
Dot and with-end #
The dot (.) is used in Go templates. Its value {{.}} is set to the current pipeline
value. The with statement sets the value of dot to the value of the pipeline. If
the pipeline is empty, then whatever is between the with-end block is skipped;
when nested, the dot takes the value according to the closest scope.
This is illustrated in the following program:
package main
import (
"os"
"text/template"
)
func main() {
t := template.New("test")
t, _ = t.Parse("{{with `hello`}}{{.}}{{end}}!\n")
t.Execute(os.Stdout, nil)
t, _ = t.Parse("{{with `hello`}}{{.}} {{with `Mary`}}{{.}}{{end}}{{end}}!\n")
t.Execute(os.Stdout, nil)
}
Template with with-end Construct
In the code above, the template t defined at line 8, is parsed at line 9, and
executed at line 10. Because of the with hello , the current pipeline has a
value, namely hello, which is printed together with the !.
The template t is parsed at line 9, and executed at line 11, resulting in hello
Mary! printed out.
Template variables $ #
You can create local variables for the pipelines within the template by
prefixing the variable name with a $ sign. Variable names can be composed
of alphanumeric characters and the underscore. In the example below, we
have used a few variations that work for variable names. It is necessary to use
:= in the assignment.
package main
import (
"os"
"text/template"
)
func main() {
t := template.New("test")
t = template.Must(t.Parse("{{with $3 := `hello`}}{{$3}}{{end}}!\n"))
t.Execute(os.Stdout, nil)
t = template.Must(t.Parse("{{with $x3 := `hola`}}{{$x3}}{{end}}!\n"))
t.Execute(os.Stdout, nil)
t = template.Must(t.Parse("{{with $x_1 := `hey`}}{{$x_1}} {{.}} {{$x_1}}{{end}}!\n"))
t.Execute(os.Stdout, nil)
}
Local Variables
In the code above, line 8 constructs a template t . At line 9 and at line 10, a
variable $3 is displayed with its value given in the with clause. At line 11 and
line 12, the same is done with a variable $x3 . At line 13 and line 14, the same
is done with a variable $x_1 . The . at line 13 also takes its value from this
variable.
This produces the following output:
hello!
hola!
hey hey hey!
Range-end #
This construct has the format:
{{range pipeline}} T1 {{else}} T0 {{end}}
The range is used for looping over collections, the value of the pipeline must
be an array, slice, or map. If the value of the pipeline has a length of zero, dot
is unaffected and T0 is executed; otherwise, dot is set to the successive
elements of the array, slice, or map and T1 is executed.
If t is the template:
{{range .}}
{{.}}
{{end}}
then, this code:
s := []int{1,2,3,4}
t.Execute(os.Stdout, s)
will output:
1
2
3
4
Here is a more concrete example, where data from fields Author , Content and
Date are shown:
{{range .}}
{{with .Author}}
<p><b>{{html .}}</b> wrote:</p>
{{else}}
<p>An anonymous person wrote:</p>
{{end}}
<pre>{{html .Content}}</pre>
<pre>{{html .Date}}</pre>
{{end}}
The range . here loops over a slice of structs, each containing an Author ,
Content and Date field.
Prede ned template functions #
There are also a few predefined template functions that you can use within
your code, e.g., the printf function, which works similar to the function
fmt.Sprintf :
package main
import (
"os"
"text/template"
)
func main() {
t := template.New("test")
t = template.Must(t.Parse("{{with $x := `hello`}}{{printf `%s %s` $x `Mary`}}{{end}}!
t.Execute(os.Stdout, nil)
}
Predefined Functions
In the code above, the template I , created at line 8, is parsed at line 9, and
displayed at line 10. It prints out hello Mary! because, in the format string %s
%s of the printf function. The first %s is substituted by $x , given in the with
clause, and the second %s is substituted by the constant string Mary.
That is about the details on the template package; the next lesson brings you a
challenge to solve.
Challenge: Web Application for Serving Guests
This lesson brings you a challenge to solve.
WE'LL COVER THE FOLLOWING
• Problem statement
Problem statement #
Implement a simple web application for making a list of guests with the
following interface:
An input text field to give the guest-name
A summed up list of the guest-names already entered
Also, make it possible to give a name through the url: /add?name=Vera
Lists of Guests
Remark: Use 0.0.0.0:3000 or simply :3000 for the connection.
However, if you run it locally, use :8765
Try to attempt the challenge below. Feel free to view the solution, after giving
some shots. Good Luck!
Hint: Do not forget to import fmt and html/template packages.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"net/http"
)
func main() {
}
// indexHandler serves the main page
func indexHandler(w http.ResponseWriter, req *http.Request) {
}
// addHandler add a name to the names list
func addHandler(w http.ResponseWriter, req *http.Request) {
}
var indexHTML = `
<!DOCTYPE html>
<html>
<head>
<title>Guest Book ::Web GUI</title>
</head>
<body>
<h1>Guest Book :: Web GUI</h1>
<form action="/add" method="post">
Name: <input name="name" /><submit value="Sign Guest Book">
</form>
<hr />
<h4>Previous Guests</h4>
<ul>
{{range .}}
<li>{{.}}</li>
{{end}}
</ul>
</body>
</html>
`
We hope that you were able to solve the challenge. The next lesson brings you
the solution to this challenge.
Solution Review: Web Application for Serving Guests
This lesson discusses the solution to the challenge given in the previous lesson.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"html/template"
"net/http"
)
const port = 3000
var guestList []string
func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/add", addHandler)
http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}
// indexHandler serves the main page
func indexHandler(w http.ResponseWriter, req *http.Request) {
t := template.New("index.html")
t, err := t.Parse(indexHTML)
if err != nil {
message := fmt.Sprintf("bad template: %s", err)
http.Error(w, message, http.StatusInternalServerError)
}
// the HTML output is now safe against code injection
t.Execute(w, guestList)
}
// addHandler add a name to the names list
func addHandler(w http.ResponseWriter, req *http.Request) {
guest := req.FormValue("name")
if len(guest) > 0 {
guestList = append(guestList, guest)
}
http.Redirect(w, req, "/", http.StatusFound)
}
var indexHTML =
<!DOCTYPE html>
<html>
<head>
<title>Guest Book ::Web GUI</title>
</head>
<body>
<h1>Guest Book :: Web GUI</h1>
<form action="/add" method="post">
Name: <input name="name" /><submit value="Sign Guest Book">
</form>
<hr />
<h4>Previous Guests</h4>
<ul>
{{range .}}
<li>{{.}}</li>
{{end}}
</ul>
</body>
</html>
`
In the code above, The HTML for the web form is contained in the variable
indexHTML , defined from line 38 to line 58. It contains an input field name at
line 47. The list starting at line 51 ranges over the current values, showing
them as list items.
The web server is started at line 14 on port with the ListenAndServe method.
There are two routing handlers:
indexHandler for root requests ( / )
addHandler for requests of the form /add
Now, look at the header of the indexHandler() function at line 18. It defines a
new template t at line 19, and parses it at line 20. If there is a parsing error,
this is displayed in the browser from line 21 to line 24. Otherwise, the
template is executed, writing its output to the browser, and using guestList
(defined at line 9) as the current variable to iterate over.
Now, look at the header of addHandler() function at line 30. At line 31, it
reads the name input value with the FormValue method into guest . If guest is
not empty, it is appended to guestList at line 33. Then, we simulate a /
request by calling Redirect at line 35. This will invoke the indexHandler ,
which will show the new guestList in the browser.
That is it for the solution. In the next lesson, we’ll discuss how to make an
elaborated webserver.
An Elaborate Web Server
This lesson covers all the primary services provided by a server by implementing a detailed web server, and
showing how each service is delivered independently.
WE'LL COVER THE FOLLOWING
• Response in browser
• Logger
• HelloServer
• Counter
• FileServer
• FlagServer
• ArgServer
• Channel
• DateServer
• Timing your Http requests
To further deepen your understanding of the Http package and how to build
web server functionality, study and experiment with the following example.
First, the code is listed, then, the usage of the different functionalities of the
program and its output are shown.
The example below demonstrates various ways in which you can write web
server handlers. The web server itself is started at line 43 in main() with
error-handling in the following lines.
Package expvar provides a standardized interface to public variables, such as
operation counters in servers. A variable containing the number of hello
requests is defined at line 15, and a struct Counter at line 22 as well as a
channel of integers at line 27. Also, some command-line flags are defined
(from line 17 to line 19, which are then parsed at line 30.
Then, from line 31 to line 42, we define all of the handlers, which are used by
this webserver. At line 34, we define a new Counter variable named ctr .
Then, at line 36, we publish ctr , which is global to the webserver.
More detailed explanations of each of the web server handlers can be found
below, which also shows you which request URL corresponds to which
handler function.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"bytes"
"expvar"
"flag"
"fmt"
"net/http"
"io"
"log"
"os"
"strconv"
)
// hello world, the web server
var helloRequests = expvar.NewInt("hello-requests")
// flags:
var webroot = flag.String("root", "/home/user", "web root directory")
// simple flag server
var booleanflag = flag.Bool("boolean", true, "another flag for testing")
// Simple counter server. POSTing to it will set the value.
type Counter struct {
n int
}
// a channel
type Chan chan int
func main() {
flag.Parse()
http.Handle("/", http.HandlerFunc(Logger))
http.Handle("/go/hello", http.HandlerFunc(HelloServer))
// The counter is published as a variable directly.
ctr := new(Counter)
expvar.Publish("counter", ctr)
http.Handle( /counter , ctr)
// http.Handle("/go/", http.FileServer(http.Dir("/tmp"))) // uses the OS filesystem
http.Handle("/go/", http.StripPrefix("/go/", http.FileServer(http.Dir(*webroot))))
http.Handle("/flags", http.HandlerFunc(FlagServer))
http.Handle("/args", http.HandlerFunc(ArgServer))
http.Handle("/chan", ChanCreate())
http.Handle("/date", http.HandlerFunc(DateServer))
err := http.ListenAndServe("0.0.0.0:3000", nil)
if err != nil {
log.Panicln("ListenAndServe:", err)
}
}
func Logger(w http.ResponseWriter, req *http.Request) {
log.Print(req.URL.String())
w.WriteHeader(404)
w.Write([]byte("oops"))
}
func HelloServer(w http.ResponseWriter, req *http.Request) {
helloRequests.Add(1)
io.WriteString(w, "hello, world!\n")
}
// This makes Counter satisfy the expvar.Var interface, so we can export
// it directly.
func (ctr *Counter) String() string { return fmt.Sprintf("%d", ctr.n) }
func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch req.Method {
case "GET": // increment n
ctr.n++
case "POST": // set n to posted value
buf := new(bytes.Buffer)
io.Copy(buf, req.Body)
body := buf.String()
if n, err := strconv.Atoi(body); err != nil {
fmt.Fprintf(w, "bad POST: %v\nbody: [%v]\n", err, body)
} else {
ctr.n = n
fmt.Fprint(w, "counter reset\n")
}
}
fmt.Fprintf(w, "counter = %d\n", ctr.n)
}
func FlagServer(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprint(w, "Flags:\n")
flag.VisitAll(func(f *flag.Flag) {
if f.Value.String() != f.DefValue {
fmt.Fprintf(w, "%s = %s [default = %s]\n", f.Name, f.Value.String(),
} else {
fmt.Fprintf(w, "%s = %s\n", f.Name, f.Value.String())
}
})
}
// simple argument server
func ArgServer(w http.ResponseWriter, req *http.Request) {
for _, s := range os.Args {
fmt.Fprint(w, s, " ")
}
}
func ChanCreate() Chan {
c := make(Chan)
go func(c Chan) {
for x := 0; ; x++ {
c <- x
}
}(c)
return c
}
func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, fmt.Sprintf("channel send #%d\n", <-ch))
}
// exec a program, redirecting output
func DateServer(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
r, w, err := os.Pipe()
if err != nil {
fmt.Fprintf(rw, "pipe: %s\n", err)
return
}
p, err := os.StartProcess("/bin/date", []string{"date"}, &os.ProcAttr{Files: []*os.Fi
defer r.Close()
w.Close()
if err != nil {
fmt.Fprintf(rw, "fork/exec: %s\n", err)
return
}
defer p.Release()
io.Copy(rw, r)
wait, err := p.Wait()
if err != nil {
fmt.Fprintf(rw, "wait: %s\n", err)
return
}
if !wait.Exited() {
fmt.Fprintf(rw, "date: %v\n", wait)
return
}
}
Remark: Change line 43 to err := http.ListenAndServe(":12345", nil) ,
if you’re running it locally.
Response in browser #
Copy your URL written at the bottom of code widget next to Your app can be
found at. For example, https://1dkne4jl5mmmm.educative.run
Logger #
https://1dkne4jl5mmmm.educative.run/ (root)
oops
On Windows: A dialog window pops up saying: Unable to open .../. The
internet site reports that the item you requested could not be found (HTTP
1.0 / 404) .
The Logger prints a 404 Not Found header with w.WriteHeader(404) . This
technique is generally useful whenever an error is encountered in the web
processing code; it can be applied like this:
if err != nil {
w.WriteHeader(400)
return
}
It also prints date + time through the logger function and the URL for each
request on the command window of the web server.
Remark: To test it locally, try http://localhost:12345/.
HelloServer #
https://1dkne4jl5mmmm.educative.run/go/hello
hello, world!
The package expvar makes it possible to create variables (of types Int, Float,
String) and make them public by publishing them. It exposes these variables
via HTTP at /debug/vars in JSON format. It is typically used for, e.g., operation
counters in servers. The helloRequests is such an int64 variable; this handler
adds 1 to it and then writes hello, world! to the browser.
Remark: To test it locally, try http://localhost:12345/go/hello.
Counter #
https://1dkne4jl5mmmm.educative.run/counter
counter = 1
refresh (= GET)
counter = 2
Each refresh increments the counter. The counter object ctr has a String()
method and implements the Var interface. This makes it publishable even
though, it is a struct. The function ServeHTTP is a handler for ctr because it
has the right signature.
Remark: To test it locally, try http://localhost:12345/counter.
FileServer #
https://1dkne4jl5mmmm.educative.run/go/ggg.html
404 error
On Windows: A dialog window pops up saying: Unable to open the page
.../ggg.html. The internet site reports that the item you requested could
not be found (HTTP 1.0 / 404)
FileServer returns a handler that serves HTTP requests with the contents of
the file system rooted at root. To use the operating system’s file system, use
http.Dir , as in:
http.Handle("/go/", http.FileServer(http.Dir("/tmp")))
Remark: To test it locally, try http://localhost:12345/go/ggg.html.
FlagServer #
https://1dkne4jl5mmmm.educative.run/flags
Flags:
boolean = true
root = /home/rsc
This handler uses the flag.VisitAll function to loop through all the flags, and
prints their name, value and default value (if different from value).
Remark: To test it locally try http://localhost:12345/flags.
ArgServer #
https://1dkne4jl5mmmm.educative.run/args
./elaborated_webserver On Windows: elaborated_webserver
This handler loops through os.Args to print out all the command-line
arguments. If there are none, only the program name (the path to the
executable) is printed.
Remark: To test it locally, try http://localhost:12345/args.
Channel #
https://1dkne4jl5mmmm.educative.run/chan
channel send #1
refresh
channel send #2
Each refresh increments the channel #. The channel’s ServeHTTP method
shows the next integer from the channel at each new request. So a web server
can take its response from a channel, populated by another function (or even
a client). The following snippet shows a handler function which does exactly
that but also times out after 30 s:
func ChanResponse(w http.ResponseWriter, req *http.Request) {
timeout := make (chan bool)
go func () {
time.Sleep(30e9)
timeout <- true
}()
select {
case msg := <-messages:
io.WriteString(w, msg)
case stop := <-timeout:
return
}
}
Remark: To test it locally try http://localhost:12345/chan.
DateServer #
https://1dkne4jl5mmmm.educative.run/date
shows the current datetime (works only on Unix because it calls /bin/date)
Possible output: Thu Sep 8 12:41:09 CEST 2011
The os.Pipe() returns a connected pair of Files, reads from r , and returns
bytes written to w . It returns the files and an error, if any, using:
func Pipe() (r *File, w *File, err error)
Timing your Http requests #
The following function makeLoggingHandler enables you to do this:
func makeLoggingHandler(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
h(w, r)
log.Printf("%s\t%s\t%s", r.RemoteAddr, r.URL, time.Sub(start))
}
}
Call it as: makeLoggingHandler(yourHandler) .
Remark: To test it locally, try http://localhost:12345/date.
That’s pretty much it about making an elaborate web server and its
applications. In the next lesson, you’ll learn how to send remote calls within
different programs using Go.
Remote Procedure Calls with RPC
This lesson covers how Go lets two different machines (a server machine and a client machine) communicate with
another using remote calls.
Go programs can communicate with each other through the net/rpc-package ;
so this is another application of the client-server paradigm. It provides a
convenient means of making function calls over a network connection. Of
course, this is only useful when the programs run on different machines. The
rpc package builds on the package gob to turn its encode/decode automation
into transport for method calls across the network.
A server registers an object, making it visible as a service with the type-name
of the object. This provides access to the exported methods of that object
across a network or other I/O connection for remote clients. It is all about
exposing methods on types over a network. The package uses the Http and
TCP protocol, and the gob package for data transport. A server may register
multiple objects (services) of different types, but it is an error to register
multiple objects of the same type.
Here we discuss a simple example:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"net/http"
"log"
"net"
"net/rpc"
"time"
"rpcobjects"
)
func main() {
calc := new(rpcobjects.Args)
rpc.Register(calc)
rpc.HandleHTTP()
listener, e := net.Listen("tcp", "0.0.0.0:3001")
if e != nil {
log.Fatal("Starting RPC-server -listen error:", e)
}
go http.Serve(listener, nil)
time.Sleep(1000e9)
}
Remark: If you’re running locally, then replace line 15 with listener, e
:= net.Listen("tcp", "localhost:1234")
This is the server-side code. In main.go, we need the package net/rpc , which
is imported at line 6. We also import the package rpcobjects (see line 8). At
line 12, we construct a new instance of type rpcobject.Args , called calc . At
line 13, this is registered as an rpc method.
Line 14 tells rpc to handle the HTTP protocol. Then, at line 15, we make an
RPC server listener with net.Listen . From line 16 to line 18, we perform the
usual error-handling. Line 19 starts the RPC server in a goroutine, and then
the main() routine waits for a second.
rpcobjects.go is a package we made and is imported at line 8. This server-
side code defines the type Args as consisting of two exported integers N and
M (from line 3 to line 5). It also defines a method Multiply on Args (from line
7 to line 10) that takes pointers to Args and the reply, which is the product of
the integers.
Here is the client code:
package main
import (
"fmt"
"log"
"net/rpc"
"./rpc_objects"
)
const serverAddress = "localhost"
func main() {
client, err := rpc.DialHTTP("tcp", serverAddress + ":3001")
if err != nil {
log.Fatal( Error dialing: , err)
}
// Synchronous call
args := &rpc_objects.Args{7, 8}
var reply int
err = client.Call("Args.Multiply", args, &reply)
if err != nil {
log.Fatal("Args error:", err)
}
fmt.Printf("Args: %d * %d = %d", args.N, args.M, reply)
}
RPC Client
Remark: If you’re running locally, then change line 11 as client, err :=
rpc.DialHTTP("tcp", serverAddress + ":1234")
This is the client-side code, that goes with the preceding server code. The
address of the server (can be its name or IP address) is stored in the constant
serverAddress at line 9 (for easy testing, we use localhost here).
The client machine has to know the definition of the object type ( Args in our
case) and its methods ( Multiply in this example). It calls rpc.DialHTTP() at
line 11, with error-handling from line 11 to line 14.
At line 16, args := &rpc_objects.Args{7, 8} , the client initializes an Args
struct with the values 7 and 8. When the client connection is made, remote
methods can be invoked upon it with client.Call("Type.Method", args,
&reply) , as done at line 18, where Type.Method here becomes Args.Multiply .
Now, let’s run the client and server.
Again, after error-handling (from line 19 to line 21), the reply comes in from
the remote server and is displayed at line 22.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"log"
"net/rpc"
"./rpcobjects"
)
const serverAddress = "localhost"
func main() {
client, err := rpc.DialHTTP("tcp", serverAddress + ":3001")
if err != nil {
log.Fatal("Error dialing:", err)
}
// Synchronous call
args := &rpcobjects.Args{7, 8}
var reply int
err = client.Call("Args.Multiply", args, &reply)
if err != nil {
log.Fatal("Args error:", err)
}
fmt.Printf("Args: %d * %d = %d", args.N, args.M, reply)
}
First, start the server, and then a client process. Click the RUN button and wait
for the terminal to start. Then, open a separate console-window for a client
process, which is started by performing the following steps on the terminal:
Type cd usr/local/go/src and press ENTER.
Type go run client.go and press ENTER.
Remark: If you’re running it locally then only perform the second step:
go run client.go
This gets the following result:
Args: 7 * 8 = 56
This call is synchronous, so it waits for the result to come back. An
asynchronous call can be made as follows:
call1 := client.Go("Args.Multiply", args, &reply, nil)
replyCall := <- call1.Done
If the last argument has a value of nil, a new channel will be allocated when
the call is complete.
If you have a Go server running as root and want to run some of your code as
a different user, the package go-runas by Brad Fitz uses the rpc package just
to accomplish this.
Now, you have gained a lot of knowledge on web servers. In the next lesson,
you’ll learn how to send an email with Go.
Sending Mails with SMTP
This lesson explains how Go code can be used to send an email using an SMTP server.
WE'LL COVER THE FOLLOWING
• Overview of SMTP in Go
• Explanation
Overview of SMTP in Go #
The package net/smtp implements the Simple Mail Transfer
Protocol for sending mail. It contains a Client type that
represents a client connection to an SMTP server:
Dial returns a new Client connected to an SMTP
server.
Set Mail (=from) and Rcpt (= to)
Data returns a writer that can be used to write the data,
here with buf.WriteTo(wc) .
Explanation #
package main
import (
"bytes"
"log"
"net/smtp"
)
func main() {
// Connect to the remote SMTP server.
client, err := smtp.Dial("smtp.gmail.com:587")
if err != nil {
log.Fatal(err)
}
// Set the sender and recipient.
client.Mail("sender email address")
client.Rcpt("receiver email address")
// Send the email body.
wc, err := client.Data()
if err != nil {
log.Fatal(err)
}
defer wc.Close()
buf := bytes.NewBufferString("This is the email body.")
if _, err = buf.WriteTo(wc); err != nil {
log.Fatal(err)
}
}
SMTP Server
In the code above, we need the package net/smtp , which is imported at line 5.
First, we need to connect to an active remote SMTP server, which is done with
the Dial method at line 10, creating a client instance. Error-handling (from
line 11 to line 13) exits the program on error.
We set up the sender and receiver email address at line 15 and line 16,
respectively. Line 18 constructs the Data method on client , which makes a
client writer wc with similar error-handling from line 19 to line 21. At line
22, we make sure that the writer wc will be closed. Then, at line 23, we make
a buffered string and write it to wc at line 24. This if-statement is combined
with error-handling, logging an eventual error and exiting the program at line
25.
The function SendMail can be used if authentication is needed and when you
have a number of recipients. It connects to the server at addr , switches to TLS
(Transport Layer Security encryption and authentication protocol),
authenticates with the mechanism if possible, and then sends an email from
address from to addresses to , with the message msg:
func SendMail(addr string, a Auth, from string, to []string, msg []byte) e
rror
Go through the following illustrations that explain how to set a Gmail account
for sending an email before running a program.
Click on Security
Step 1: Open your Account Settings
1 of 7
Turn 2-Step Verification on.
If already on move
to the next step
Step 2: Enable 2-Step Verification
2 of 7
Click App passwords to generate a password
If already generated then use the generated password.
If you don't remember it make a new one.
Step 3: Generate an App Password
3 of 7
After the verification, the following page will open
Step3: Generate App Password
4 of 7
Choose Other option and give
any name by your own choice
Step3: Generate App Password
5 of 7
After selecting Device
and App click Generate
Choose your device from the pop-up
If your device isn't listed then select Other
and name you device
Step3: Generate App Password
6 of 7
Don't forget to copy this 16 digit password.
You can use this password in the program
to send email rather than using original password.
Step 4: Copy the Password
7 of 7
Look at the following program to see how it works:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"net/smtp"
)
// smtpServer data to smtp server
type smtpServer struct {
host string
port string
}
// serverName URI to smtp server
func (s *smtpServer) serverName() string {
return s.host + ":" + s.port
}
func main() {
// Sender data.
from := "sender email address"
password := "password" // you can enter original password or password generated with App
// Receiver email address.
to := []string{
"first receiver email address",
//"second receiver email address",
}
// smtp server configuration.
smtpServer := smtpServer{host: "smtp.gmail.com", port: "587"}
// Message.
message := []byte("Enter the message you want to send.")
// Authentication.
auth := smtp.PlainAuth("", from, password, smtpServer.host)
// Sending email.
err := smtp.SendMail("smtp.gmail.com:587", auth, from, to, message)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Email Sent!")
}
Click the RUN button and wait for the terminal to start. Type go run main.go
in the terminal and press ENTER.
Hurrah! You just sent your first email with Go! Cloud computing is so popular
nowadays, and Go provides support for it. See the next lesson to see how
Golang has made this possible.
Go in the Cloud
This lesson explains how Go and Google App Engine collaborate to create a exible environment for cloud
computing.
WE'LL COVER THE FOLLOWING
• Google cloud computing
• Environment provided by GAE
• Standard environment
• Flexible environment
Google cloud computing #
Google App Engine (from now on shortened to GAE) is the
Google way of cloud computing, which includes executing
your web applications and storing your data on the vast
Google infrastructure without having to worry about the
servers, the network, the operating system, the data store,
and so on. This collection of resources is commonly referred
to as the cloud, and its maintenance is the sole responsibility
of Google itself. For you, as a developer, only your
application counts, and the services it can deliver to its
users, who can work with and run your application on any
device which can connect to the internet.
You only pay for the resources (CPU processing time, network bandwidth, disk
storage, memory, and so on) your software really needs. When there are peak
moments, the cloud platform automatically increases resources for your
application and decreases them when they are no longer needed. This is also
known as scalability. Scalability is one of the biggest advantages of cloud
computing.
Collaborative applications (where groups of people work together on, share
data, communicate, and so on), applications that deliver services and
applications that perform large computations, are excellent candidates for
cloud computing. GAE has had Go support since 2011. You can visit its start
page here.
Go applications running in the cloud are a kind of web applications, so the
typical user interface for a cloud application is a browser environment. To
run Go in GAE, you have to choose its runtime environment. This can either
be a standard and flexible environment:
Environment provided by GAE #
Standard environment #
Here, your application runs within its own secure and reliable environment
that is independent of the hardware, operating system, or physical location of
the server. The focus is on executing reliably under a heavy load and with
large amounts of data.
Flexible environment #
Based on Google Compute Engine, this environment focuses on balancing the
load, and optimizing for adaptive scaling.
For both environments, you first need to install the Cloud SDK. This installs
the gcloud command-line tool; for more info, click here. Your cloud app is
configured through .yaml files (see yaml), of which the app.yaml is the most
important.
Here is a simple example of a cloud Go app:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
http.HandleFunc("/", indexHandler)
port := os.Getenv("PORT")
if port == "" {
port = "3000"
log.Printf("Defaulting to port %s", port)
}
log.Printf("Listening on port %s", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
}
// indexHandler responds to requests with our greeting.
func indexHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
fmt.Fprint(w, "Hello, World!")
}
Click RUN and wait for the terminal to start. Once it’s started, click the URL
next to the Your app can be found at and see the result.
Remark: To run it locally, change line 13 to port = 8080 . To test this
program open browser at http://localhost:8080.
You see that looking at the code, it is just a normal web application. You can
monitor your app and its resource usage through a sophisticated web
dashboard.
That’s it about networking and applications that communicate over the
Internet. We have covered almost all advanced programming concepts in Go.
In the next chapter, the common programming mistakes are mentioned so
you can avoid making them. Furthermore, some common patterns are revised
with a purpose to gain proficiency in Go programming.
Regular Mistakes and Suggestions
In this chapter, we assemble some of the most common mistakes or don’t(s) in Go-programming. We often refer to
the full explanation and examples from the previous chapters.
WE'LL COVER THE FOLLOWING
• Common pitfalls
• Best practices
In the previous chapters, we were sometimes warned with
Remarks and Notes for Go misuses. Be sure to look for the
specific section in this course on that subject when you
encounter a difficulty in a coding situation like that.
Common pitfalls #
Here is an overview of pitfalls for your convenience, referring to where you
can find more explanations and examples:
Never use var p*a to not confuse pointer declaration and multiplication.
Never change the counter-variable within the for-loop itself.
Never use the value in a for-range loop to change the value itself.
Never use goto with a preceding label.
Never forget the braces () after a function name to call the function
specifically when calling a method on a receiver or invoking a lambda
function as a Goroutine.
Never use new() with maps, always use make() .
When coding a String() method for a type, don’t use fmt.Print or alike
in the code, use the fmt.Sprint family of methods.
Never forget to use Flush() when terminating buffered writing.
Never ignore errors because ignoring them can lead to program crashes.
Do not use global variables or shared memory; they make your code
unsafe for running concurrently.
When using JSON, make sure that the data fields you require are
exported in the data structure.
Use println only for debugging purposes.
Best practices #
In contrast, use the following:
Initialize a slice of maps the right way.
Always use the “comma, ok” (or checked) form for type assertions.
Make and initialize your types with a factory.
Use a pointer as receiver for a method on a struct only when the method
modifies the structure, otherwise use a value.
Always use the package html/template to implement data-driven
templates for generating HTML output safe against code injection.
These were some of the most common mistakes and overlooked errors. Best
practices should be followed to enhance performance. Study the next lesson to
code with proficiency keeping in view the best practices.
Highlights from the Previous Chapters
We often refer to the full explanation and examples in previous chapters. Here, each section summarizes all the
concepts learned in this course and warns what not to do!
WE'LL COVER THE FOLLOWING
• Hiding a variable by misusing short declaration
• Misusing strings
• Using defer for closing a le in the wrong scope
• Confusing new() and make()
• No need to pass a pointer to a slice to a function
• Using pointers to interface types
• Misusing pointers with value types
• Misusing goroutines and channels
• Using closures with goroutines
• Bad error handling
• Don’t use booleans
• Don’t clutter code with error-checking
• for range in arrays
Hiding a variable by misusing short declaration #
In the following code snippet:
var remember bool = false
if something {
remember := true // Wrong.
}
// use remember
The variable remember will never become true outside of the if-body. Inside
the if-body, a new remember variable that hides the outer remember is
declared because of := , and there it will be true. However, after the closing }
of if , the variable remember regains its outer value false. So, write it as:
if something {
remember = true
}
This can also occur with a for-loop, and can be particularly subtle in functions
with named return variables, as the following snippet shows:
func shadow() (err error) {
x, err := check1() // x is created; err is assigned to
if err != nil {
return // err correctly returned
}
if y, err := check2(x); err != nil { // y and inner err are created
return // inner err shadows outer err so err is wrongly returned!
} else {
fmt.Println(y)
}
return
}
Misusing strings #
When you need to do a lot of manipulations on a string, think about the fact
that strings in Go (like in Java and C#) are immutable. String concatenations of
the kind a += b are inefficient, mainly when performed inside a loop. They
cause many reallocations and the copying of memory. Instead, one should use
a bytes.Buffer to accumulate string content, like in the following snippet:
var b bytes.Buffer
...
for condition {
b.WriteString(str) // appends string str to the buffer
}
return b.String()
Remark: Due to compiler-optimizations and the size of the strings, using
a buffer only starts to become more efficient when the number of
concatenations is > 15.
Using defer for closing a le in the wrong scope
#
Suppose you are processing a range of files in a for-loop, and you want to
make sure the files are closed after processing by using defer , like this:
for _, file := range files {
if f, err = os.Open(file); err != nil {
return
}
// This is wrong. The file is not closed when this loop iteration ends.
defer f.Close()
// perform operations on f:
f.Process(data)
}
But, at the end of the for-loop, defer is not executed. Therefore, the files are
not closed! Garbage collection will probably close them for you, but it can
yield errors. Better do it like this:
for _, file := range files {
if f, err = os.Open(file); err != nil {
return
}
// perform operations on f:
f.Process(data)
// close f:
f.Close()
}
The keyword defer is only executed at the return of a function, not at the end of
a loop or some other limited scope.
Confusing new() and make() #
We have talked about this and illustrated it with code already at great length
in Chapter 5 and Chapter 8. The point is:
For slices, maps and channels: use make
For arrays, structs and all value types: use new
No need to pass a pointer to a slice to a function
#
A slice is a pointer to an underlying array. Passing a slice as a parameter to a
function is probably what you always want, which means namely passing a
pointer to a variable to be able to change it, and not passing a copy of the data.
So, you want to do this:
func findBiggest( listOfNumbers []int ) int {}
not this:
func findBiggest( listOfNumbers *[]int ) int {}
Do not dereference a slice when used as a parameter!
Using pointers to interface types #
Look at the following program which can’t be compiled:
package main
import (
"fmt"
)
type nexter interface {
next() byte
}
func nextFew1(n nexter, num int) []byte {
var b []byte
for i:=0; i < num; i++ {
b[i] = n.next()
}
return b
}
func nextFew2(n *nexter, num int) []byte {
var b []byte
for i:=0; i < num; i++ {
b[i] = n.next()
// compile error:
// n.next undefined (type *nexter is pointer to interface, not interface)
}
return b
}
func main() {
fmt.Println("Hello World!")
}
Pointers to Interface Type
In the code above, nexter is an interface with the method next() , which
reads the next byte. nextFew1() has this interface type as a parameter and
reads the next num bytes, returning them as a slice. This is ok. However,
nextFew2() uses a pointer to the interface type as a parameter. When using
the next() function, we get a clear compiler error: n.next undefined (type
*nexter is pointer to interface, not interface) .
So never use a pointer to an interface type; this is already a pointer!
Misusing pointers with value types #
Passing a value as a parameter in a function or as a receiver to a method may
seem a misuse of memory because a value is always copied. But, on the other
hand, values are allocated on the stack, which is quick and relatively cheap. If
you pass a pointer to the value instead of the Go compiler, in most cases, we
will see this as the making of an object, and we will move this object to the
heap, causing an additional memory allocation. Therefore, nothing was
gained in using a pointer instead of the value.
Misusing goroutines and channels #
For didactic reasons and for gaining insight into their working, a lot of the
examples in Chapter 12 applied goroutines and channels in very simple
algorithms, like as a generator or iterator. In practice, often, you don’t need
the concurrency, or you don’t need the overhead of the goroutines with
channels; passing parameters using the stack is, in many cases, far more
efficient. Moreover, it is likely to leak memory if you break, return or panic
your way out of the loop because the goroutine blocks in the middle of doing
something. In real code, it is often better to just write a simple procedural
loop. Use goroutines and channels only where concurrency is important!
Using closures with goroutines #
Look at the following code:
package main
import (
"fmt"
"time"
)
var values = [5]int{10, 11, 12, 13, 14}
func main() {
// version A:
for ix := range values { // ix is the index
func() {
fmt.Print(ix, " ")
}() // call closure, prints each index
}
fmt.Println()
// version B: same as A, but call closure as a goroutine
for ix := range values {
go func() {
fmt.Print(ix, " ")
}()
}
fmt.Println()
time.Sleep(5e9)
// version C: the right way
for ix := range values {
go func(ix int) {
fmt.Print(ix, " ")
}(ix)
}
fmt.Println()
time.Sleep(5e9)
// version D: print out the values:
for ix := range values {
val := values[ix]
go func() {
fmt.Print(val, " ")
}()
}
time.Sleep(1e9)
}
Closures with Goroutines
Version A calls a closure five times, which prints the value of the index.
Version B does the same but invokes each closure as a goroutine, with an
argument that this would be faster because the closures execute in parallel. If
we leave enough time for all goroutines to run, the output of version B is 4 4 4
4 4. Why is this? The ix variable in the above loop B is a single variable that
takes on the index of each array element. Because the closures are all only
bound to that one variable, there is a very good chance that, when you run
this code, you will see the last index (4) printed for every iteration, instead of
each index in the sequence. This is because the goroutines will probably not
begin executing until after the loop, when ix has the value 4.
The right way to code that loop is version C: invoke each closure with ix as a
parameter. Then, ix is evaluated at each iteration and placed on the stack for
the goroutine, so each index is available to the goroutine when it is eventually
executed. Note that the output depends on when each of the goroutines starts.
In version D, we print out the values of the array. Why does this work and
version B do not? Because variables declared within the body of a loop (as
val here) are not shared between iterations, and thus can be used separately
in a closure.
Bad error handling #
Stick to the patterns described in Chapter 11 and summarized in the chapter
after it.
Don’t use booleans #
Making a boolean variable whose value is a test on the error-condition, like in
the following, is superfluous:
var good bool
// test for an error, good becomes true or false
if !good {
return errors.New("things aren't good")
}
Instead, test on the error immediately:
_, err1 := api.Func1()
if err1 != nil { ... }
Don’t clutter code with error-checking #
Avoid writing code like this:
... err1 := api.Func1()
if err1 != nil {
fmt.Println("err: " + err.Error())
return
}
err2 := api.Func2()
if err2 != nil {
...
return
}
First, include the call to the functions in an initialization statement of the if’s.
Even then, the errors are reported (by printing them) with if-statements
scattered throughout the code. With this pattern, it is hard to tell what is
normal program logic and what is error checking/reporting. Also, notice that
most of the code is dedicated to error conditions at any point in the code. A
good solution is to wrap your error conditions in a closure wherever possible,
like in the following example:
func httpRequestHandler(w http.ResponseWriter, req *http.Request) {
err := func () error {
if req.Method != "GET" {
return errors.New("expected GET")
}
if input := parseInput(req); input != "command" {
return errors.New("malformed command")
}
// other error conditions can be tested here
} ()
if err != nil {
w.WriteHeader(400)
io.WriteString(w, err)
return
}
doSomething() ...
...
This approach clearly separates the error checking, error reporting, and
normal program logic.
for range in arrays #
If you make a for range over an array with 1 value after the for, you get the
indices back, not the values of the array. In:
for n := range array { ... }
The variable n gets the values 0, 1, … , len(array)-1. Moreover, the compiler
won’t warn you if the operations in the loop are compatible with ints.
These are some of the important concepts that are mostly overlooked when
programming. In the next lesson, we’ll be studying the comma, ok pattern
used several times before in this course.
The comma, ok Pattern
This lesson brie y discusses the uses of the comma, ok pattern in Go language.
WE'LL COVER THE FOLLOWING
• Testing for errors on function return
• Testing if a key-value item exists in a map
• Testing if an interface variable is of certain type
• Testing if a channel is closed
While studying the Go-language, we encountered several times the so-called
comma, ok idiom where an expression returns two values: the first of which is
a value or nil, and the second is true/false or an error. An if-condition with
initialization and then testing on the second-value leads to succinct and
elegant code. This is a significant pattern in idiomatic Go-code. Here are all
cases summarized:
Testing for errors on function return #
var value Type_value
var err error
if value, err = pack1.Func1(param1); err != nil {
fmt.Printf("Error %s in pack1.Func1 with parameter %v", err.Error(), par
am1)
return err
}
// no error in Func1:
Process(value)
Other places where it is used:
os.Open(file), strconv.Atoi(str)
The following code will not compile because the scope of the file variable is
the if-block only:
if file, err := os.Open("input.dat"); err != nil {
fmt.Printf("An error occurred on opening the inputfile\n" +
"Does the file exist?\n" +
"Have you got access to it?\n")
return // exit the function on error
}
defer file.Close()
reader := bufio.NewReader(file)
In this case, declare the variables upfront:
var file *os.File
var err error
and then file, err := os.Open("input.dat") can be replaced by: file, err =
os.Open("input.dat") .
The function in which this code occurs returns the error to the caller, giving it
the value nil when the normal processing was successful and so has the
signature:
func SomeFunc() error {
...
if value, err = pack1.Func1(param1); err != nil {
...
return err
}
...
return nil
}
The same pattern is used when recovering from a panic with defer . A good
pattern for clean error checking is using closures.
Testing if a key-value item exists in a map #
Does this means that map1 has a value for key1 ? Look at the following
snippet:
if value, isPresent = map1[key1]; isPresent {
Process(value)
}
// key1 is not present
...
Testing if an interface variable is of certain type #
if value, ok := varI.(T); ok {
Process(value)
}
// varI is not of type T
Testing if a channel is closed #
for input := range ch {
Process(input)
}
or:
var input Type_of_input
var err error
for {
if input, open = <-ch; !open {
break // channel is closed
}
Process(input)
}
This is how this pattern resolves many issues in a single line. Similarly, in the
next lesson, we’ll be studying the defer pattern, used several times before in
this course.
The defer Pattern
This lesson brie y discusses the uses of the defer pattern in Go language.
WE'LL COVER THE FOLLOWING
• Closing a le stream
• Unlocking a locked resource (a mutex)
• Closing a channel (if necessary)
• Recovering from a panic
• Stopping a ticker
• Release of a process
• Stopping CPUpro ling and ushing the info
• A goroutine signaling a WaitGroup
Using defer ensures that all resources are properly closed or given back to
the pool when the resources are not needed anymore. Secondly, it is
paramount in Go to recover from panicking.
Closing a le stream #
// open a file f
defer f.Close()
Unlocking a locked resource (a mutex) #
mu.Lock()
defer mu.Unlock()
Closing a channel (if necessary) #
ch := make(chan float64)
defer close(ch)
or with 2 channels:
answerα, answerβ := make(chan int), make(chan int)
defer func() { close(answerα); close(answerβ) }()
Recovering from a panic #
defer func() {
if err := recover(); err != nil {
log.Printf("run time panic: %v", err)
}()
Stopping a ticker #
tick1 := time.NewTicker(updateInterval)
defer tick1.Stop()
Release of a process #
p, err := os.StartProcess(..., ..., ...)
defer p.Release()
Stopping CPUpro ling and ushing the info #
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
A goroutine signaling a WaitGroup #
func HeavyFunction1(wg *sync.WaitGroup) {
defer wg.Done()
// Do a lot of stuff
}
It can also be used when not forgetting to print a footer in a report.
That’s it about the defer pattern, and its uses. The next lesson brings you the
highlights of playing around operators without violating the visibility rule.
Visibility and Operator Patterns
This lesson explains the possible uses of operators to gain maximum bene ts without violating the visibility
pattern.
WE'LL COVER THE FOLLOWING
• The visibility pattern and interfaces
• The operator pattern and interfaces
• Implement the operators as functions
• Implement the operators as methods
• Using an interface
The visibility pattern and interfaces #
In Chapter 2, we saw how the simple Visibility rule dictates the access-mode to
types, variables, and functions in Go. Chapter 8 showed how you could make
the use of the Factory function mandatory when defining types in separate
packages.
Define your interfaces in a high-level package that the other packages in your
project depend on. Then they can just pass interface values around.
The operator pattern and interfaces #
An operator is a unary or binary function which returns a new object and does
not modify its parameters, like + and *. In C++, special infix operators (+, -, *,
and so on) can be overloaded to support math-like syntax, but apart from a
few special cases Go does not support operator overloading. To overcome this
limitation, operators must be simulated with functions. Since Go supports a
procedural as well as an object-oriented paradigm, there are two options:
Implement the operators as functions #
The operator is implemented as a package level function to operate on one or
two parameters and return a new object, implemented in the package
dedicated to the objects on which they operate. For example, if we implement
a matrix manipulation in a package matrix , this would contain the addition of
matrices Add() and multiplication Mult() , which result in a matrix. These
would be called on the package name itself so that we could make expressions
of the form:
m := matrix.Add(m1, matrix.Mult(m2, m3))
If we would like to differentiate between the different kinds of matrices
(sparse and dense) in these operations, because there is no function
overloading, we would have to give them different names, as in:
func addSparseToDense (a *sparseMatrix, b *denseMatrix) *denseMatrix
func addDenseToDense (a *denseMatrix, b *denseMatrix) *denseMatrix
func addSparseToSparse (a *sparseMatrix, b *sparseMatrix) *sparseMatrix
This is not very elegant, and the best we can do is hide these as private
functions and expose a single public function Add() . This can operate on any
combination of supported parameters by type-testing them in a nested type
switch:
func Add(a Matrix, b Matrix) Matrix {
switch a.(type) {
case sparseMatrix:
switch b.(type) {
case sparseMatrix:
return addSparseToSparse(a.(sparseMatrix), b.(sparseMatrix))
case denseMatrix:
return addSparseToDense(a.(sparseMatrix), b.(denseMatrix))
...
default:
// unsupported arguments
...
}
}
However, the more elegant and preferred way is to implement the operators
as methods, as it is done everywhere in the standard library.
Implement the operators as methods #
Methods can be differentiated according to their receiver type. Therefore,
instead of having to use different function names, we can simply define an
Add() method for each type:
func (a *sparseMatrix) Add(b Matrix) Matrix
func (a *denseMatrix) Add(b Matrix) Matrix
Each method returns a new object, which becomes the receiver of the next
method call, so we can make chained
expressions: m := m1.Mult(m2).Add(m3)
This is shorter and clearer. The correct implementation can again be selected
at runtime based on a type-switch:
func (a *sparseMatrix) Add(b Matrix) Matrix {
switch b.(type) {
case sparseMatrix:
return addSparseToSparse(a.(sparseMatrix), b.(sparseMatrix))
case denseMatrix:
return addSparseToDense(a.(sparseMatrix), b.(denseMatrix))
...
default:
// unsupported arguments
...
}
}
Again, this is easier than the nested type switch.
Using an interface #
When operating with the same methods on different types, the concept of
creating a generalizing interface to implement this polymorphism should
come to mind. We could, for example, define the interface Algebraic :
type Algebraic interface {
Add(b Algebraic) Algebraic
Min(b Algebraic) Algebraic
Mult(b Algebraic) Algebraic
...
Elements()
}
and define the methods Add() , Min() , Mult() , … for our matrix types.
Each type that implements the Algebraic interface above will allow for
method chaining. Each method implementation should use a type-switch to
provide optimized implementations based on the parameter type.
Additionally, a default case should be specified, which relies only on the
methods in the interface:
func (a *denseMatrix) Add(b Algebraic) Algebraic {
switch b.(type) {
case sparseMatrix:
return addDenseToSparse(a, b.(sparseMatrix))
default:
for x in range b.Elements() ...
...
}
If a generic implementation cannot be implemented using only the methods
in the interface, you are probably dealing with classes that are not similar
enough, and this operator pattern should be abandoned. For example, it does
not make sense to write a.Add(b) if a is a set, and b is a matrix; therefore, it
will be difficult to implement a generic a.Add(b) in terms of set and matrix
operators. In this case, split your package in two and provide separate
AlgebraicSet and AlgebraicMatrix interfaces.
This chapter was like a revision of the previous sections, to help you avoid the
common pitfalls and follow the best practices. Meanwhile, the next chapter
will bring you an overview of all the chapters you studied before with the
purpose of revising the essential concepts, lesson by lesson.
Strings, Arrays and Slices
This lesson is a ashback to the standard operations and their syntaxes de ned on strings, arrays, and slices.
WE'LL COVER THE FOLLOWING
• πŸ“ Useful code snippets for strings
• Changing a character in a string
• Taking a part (substring) of a string
• Looping over a string with for or for-range
• Finding number of bytes and characters in string
• Concatenating strings
• πŸ“ Useful code snippets for arrays and slices
• Creation
• Initialization
• Cutting the last element of an array or slice line
• Looping over an array (or slice) arr with for or for-range
πŸ“ Useful code snippets for strings #
Changing a character in a string #
Strings are immutable, so in fact a new string is created here.
str := "hello"
c := []rune(s)
c[0] = 'c'
s2 := string(c) // s2 == "cello"
Taking a part (substring) of a string #
substr := str[n:m]
Looping over a string with for or for-range #
// gives only the bytes:
for i:=0; i < len(str); i++ {
... = str[i]
}
// gives the Unicode characters:
for ix, ch := range str {
...
}
Finding number of bytes and characters in string #
Number of bytes in a string str :
len(str)
Number of characters in a string str :
The fastest way is:
utf8.RuneCountInString(str)
An equivalent way is:
len([]int(str))
Concatenating strings #
The fastest way is:
// with a bytes.Buffer
var buffer bytes.Buffer
var s string
buffer.WriteString(s)
fmt.Print(buffer.String(), "\n")
Other ways are:
Strings.Join() // using Join function
str1 += str2 // using += operator
πŸ“ Useful code snippets for arrays and slices #
Creation #
To create an array:
arr1 := new([len]type)
To create a slice:
slice1 := make([]type, len)
Initialization #
To initialize an array:
arr1 := [...]type{i1, i2, i3, i4, i5}
arrKeyValue := [len]type{i1: val1, i2: val2}
To initialize a slice:
var slice1 []type = arr1[start:end]
Cutting the last element of an array or slice line #
line = line[:len(line)-1]
Looping over an array (or slice) arr with for or for-range #
for i:=0; i < len(arr); i++ {
... = arr[i]
}
for ix, value := range arr {
...
}
This pretty much summarizes arrays, strings, and slices. The next lesson deals
with structs, interfaces, and maps.
Structs, Interfaces and Maps
This lesson is a ashback to the standard operations and their syntaxes de ned on structs, interfaces, and maps.
WE'LL COVER THE FOLLOWING
• πŸ“ Useful code snippets for structs
• Creation
• Initialization
• πŸ“ Useful code snippets for interfaces
• Testing if a value implements an interface
• A type classi er
• πŸ“ Useful code snippets for maps
• Creation
• Initialization
• Looping over a map with for or for-range
• Testing if a key value exists in a map
• Deleting a key in a map
πŸ“ Useful code snippets for structs #
Creation #
type struct1 struct {
field1 type1
field2 type2
...
}
ms := new(struct1)
Initialization #
ms := &struct1{10, 15.5,
Chris }
Capitalize the first letter of the struct name to make it visible outside its
package. Often, it is better to define a factory function for the struct and force
using that.
ms := Newstruct1{10, 15.5, "Chris"}
func Newstruct1(n int, f float32, name string) *struct1 {
return &struct1{n, f, name}
}
πŸ“ Useful code snippets for interfaces #
Testing if a value implements an interface #
if v, ok := v.(Stringer); ok { // test if v implements Stringer
fmt.Printf("implements String(): %s\n", v.String());
}
A type classi er #
func classifier(items ...interface{}) {
for i, x := range items {
switch x.(type) {
case bool: fmt.Printf("param #%d is a bool\n", i)
case float64: fmt.Printf("param #%d is a float64\n", i)
case int, int64: fmt.Printf("param #%d is an int\n", i)
case nil: fmt.Printf("param #%d is nil\n", i)
case string: fmt.Printf("param #%d is a string\n", i)
default: fmt.Printf("param #%d's type is unknown\n", i)
}
}
}
πŸ“ Useful code snippets for maps #
Creation #
map1 := make(map[keytype]valuetype)
Initialization #
map1 := map[string]int{"one": 1, "two": 2}
Looping over a map with for or for-range #
for key, value := range map1 {
...
}
Testing if a key value exists in a map #
val1, isPresent = map1[key1]
This gives a value or zero-value for val1 , true or false for isPresent .
Deleting a key in a map #
delete(map1, key1)
This pretty much summarizes structs, interfaces, and maps. The next lesson
deals with functions and files.
Functions and Files
This lesson ashes back to the standard operations and their syntaxes de ned on functions and les.
WE'LL COVER THE FOLLOWING
• πŸ“ Useful code snippets for functions
• Recovering to stop a panic terminating sequence:
• πŸ“ Useful code snippets for le
• Opening and reading a File
• Copying a le with a buffer
πŸ“ Useful code snippets for functions #
Recovering to stop a panic terminating sequence: #
func protect(g func()) {
defer func() {
log.Println("done") // Println executes normally even if there is a pa
nic
if x := recover(); x != nil {
log.Printf("run time panic: %v", x)
}
}()
log.Println("start")
g()
}
πŸ“ Useful code snippets for
le #
Opening and reading a File #
file, err := os.Open("input.dat")
if err!= nil {
fmt.Printf("An error occurred on opening the inputfile\n" +
"Does the file exist?\n" +
"Have you got acces to it?\n")
return
}
defer file.Close()
iReader := bufio.NewReader(file)
for {
str, err := iReader.ReadString('\n')
if err!= nil {
return // error or EOF
}
fmt.Printf("The input was: %s", str)
}
Copying a le with a buffer #
func cat(f *file.File) {
const NBUF = 512
var buf [NBUF]byte
for {
switch nr, er := f.Read(buf[:]); true {
case nr < 0:
fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", f.String
(),
er.String())
os.Exit(1)
case nr == 0: // EOF
return
case nr > 0:
if nw, ew := file.Stdout.Write(buf[0:nr]); nw != nr {
fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", f.Strin
g(),
ew.String())
}
}
}
}
This pretty much summarizes functions and file handling. The next lesson
deals with parallelism and networking.
Goroutines and Networking
This lesson ashes back to the standard operations and their syntaxes de ned on goroutines and networking.
WE'LL COVER THE FOLLOWING
• πŸ“ Useful code snippets for channels
• Creation
• Looping over a channel with a for–range
• Testing if a channel is closed
• Using a channel to let the main program wait
• Channel factory pattern
• Stopping a goroutine
• Simple timeout pattern
• Using an in- and out-channel instead of locking
• πŸ“ Useful code snippets for networking applications
• Templating
A rule of thumb if you use parallelism to gain efficiency over serial
computation:
"The amount of work done inside goroutines has to be much larger than
the costs associated with creating goroutines and sending data back and
forth between them."
Using buffered channels for performance: A buffered channel can
easily double its throughput depending on the context, and the
performance gain can be 10x or more. You can further try to optimize by
adjusting the capacity of the channel.
Limiting the number of items in a channel and packing them in
arrays: Channels become a bottleneck if you pass a lot of individual items
through them. You can work around this by packing chunks of data into
arrays and then unpacking on the other end. This can give a speed gain of
a factor 10x.
πŸ“ Useful code snippets for channels #
Creation #
ch := make(chan type, buf)
Looping over a channel with a for–range #
for v := range ch {
// do something with v
}
Testing if a channel is closed #
//read channel until it closes or error-condition
for {
if input, open := <-ch; !open {
break
}
fmt.Printf("%s ", input)
}
Or, use the looping over a channel method where the detection is automatic.
Using a channel to let the main program wait #
Using the semaphore pattern, you can let the main program wait until the
goroutine completes:
ch := make(chan int) // Allocate a channel
// Start something in a goroutine; when it completes, signal on the channe
l
go func() {
// doSomething
ch <- 1 // Send a signal; value does not matter
}()
doSomethingElseForAWhile()
<-ch // Wait for goroutine to finish; discard sent value
Channel factory pattern #
The function is a channel factory, and it starts a lambda function as goroutine,
populating the channel:
func pump() chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
}()
return ch
}
Stopping a goroutine #
runtime.Goexit()
Simple timeout pattern #
timeout := make(chan bool, 1)
go func() {
time.Sleep(1e9) // one second
timeout <- true
}()
select {
case <-ch:
// a read from ch has occurred
case <-timeout:
// the read from ch has timed out
}
Using an in- and out-channel instead of locking #
func Worker(in, out chan *Task) {
for {
t := <-in
process(t)
out <- t
}
}
πŸ“ Useful code snippets for networking
applications #
Templating #
Make, parse and validate a template:
var strTempl = template.Must(template.New("TName").Parse(strTemplateH
TML))
Use the html filter to escape HTML special characters, when used in a
web context:
{{html .}} or with a field FieldName {{ .FieldName |html }}
Use template-caching.
This pretty much summarizes goroutines and networking. The suggestions
you learned in the previous chapters are now summarized in the next lesson
for you.
Suggestions and Considerations
This lesson lists some useful pieces of advice, tips and things a programmer should consider to program ef ciently
in Go.
WE'LL COVER THE FOLLOWING
• General
• Stopping a program in case of error
• Performance best practices and advice
• Memory considerations
General #
Stopping a program in case of error #
if err != nil {
fmt.Printf("Program stopping with error %v", err)
os.Exit(1)
}
or:
if err != nil {
panic("ERROR occurred: " + err.Error())
}
Performance best practices and advice #
Use []rune , if possible, instead of strings.
Use slices instead of arrays.
Use arrays or slices instead of a map where possible.
Use a for range over a slice if you only need the value and not the index;
this is slightly faster than having to do a slice lookup for every element.
When the array is sparse (containing many 0 or nil-values), using a map
can result in lower memory consumption.
Specify an initial capacity for maps.
When defining methods, use a pointer to a type (struct) as a receiver.
Use constants or flags to extract constant values from the code.
Use caching whenever possible when large amounts of memory are being
allocated.
Use template caching.
Memory considerations #
For long-lived processes, deploy your Go applications on a 64-bit OS. Due to
the current nature of the GC, big chunks of memory are pre-allocated, which
could bring a long-lived Go process on a 32-bit machine to crash due to
memory depletion. Other causes of memory leaks could be:
You keep creating goroutines that don’t ever exit.
You keep appending to the same persistent slice (or writing to the same
bytes.Buffer ) and never resetting it.
You keep adding items to a persistent map without ever removing them.
Here is a code snippet to monitor memory usage:
memstats = new(runtime.MemStats)
runtime.ReadMemStats(memstats)
log.Printf("memstats before GC: bytes = %d footprint = %d",
memstats.HeapAlloc, memstats.Sys)
After learning all basic and advanced concepts of programming in Golang,
how about making a web application of our own, keeping in view all the
concepts and suggestions? The next chapter is about building a complete
application.
Introduction
This lesson brie y introduces the application we will be designing in this chapter. Every lesson covers a part of the
application. After every lesson, you'll achieve a milestone.
WE'LL COVER THE FOLLOWING
• Introducing Project UrlShortener
In this chapter, we will develop a complete program: goto, a URLShortener
web application, because the web is all-ubiquitous, and we don’t want to type
long URLs. The example is taken from the excellent lecture from Andrew
Gerrand at FOSSDEM 2011. We will do this in 3 stages; each stage has more
functionalities and shows progressively more features of the Go language. We
will draw heavily on what we have learned about web applications in Chapter
13.
Version 1: a map and a struct are used, together with a
Mutex from the sync package and a struct factory.
Version 2: the data is made persistent because it is
written to a file in gob-format.
Version 3: the application is rewritten with goroutines
and channels.
Version 4: what has to change if we want a JSONversion.
Version 5: a distributed version is made with the rpc
protocol.
Introducing Project UrlShortener #
You know that some addresses in the browser (called URLs) are (very) long
and/or complex and that there are services on the web which turn these into a
nice short URL, to be used instead. Our project is like that. It is a web service
with two functionalities:
Add: given a long URL, it returns a short version, e.g.,
http://maps.google.com/maps?
f=q&source=s_q&hl=en&geocode=&q=tokyo&sll=37.0625,-95.677068&sspn
=68.684234,65.566406&ie=UTF8&hq=&hnear=Tokyo,+Japan&t=h&z=9
(link A) becomes http://goto/UrcGq (link B) and stores this pair of data (all
our short URL’s start with http://goto/).
Redirect: whenever a shortened URL is requested, it redirects the user to
the original, long URL. So, if you type (B) in a browser, it redirects you to
the page of (A). For example, http://goto/a redirects to http://google.com/ if
it was shortened to http://goto/a.
Now that we know what the application is supposed to do, let’s look at the
data structures we will use for it in the next lesson.
Setting up the Structure of Application
This lesson provides detailed code for designing the application and its explanation.
WE'LL COVER THE FOLLOWING
• Making it thread-safe
• Using defer to simplify the code
• A factory for URLStore
• Using our URLStore
When our application is running in production, it will receive many requests
with short URLs, and several requests to turn a long URL into a short one. But,
which data structure will our program stock this data in?
Both URL-types (A) and (B) from the last lesson are strings. Moreover, they
relate to each other: given (B) as a key, we need to fetch (A) as its value, i.e.,
they map to each other. To store our data in memory, we need such a map
structure, a map[string]string . The key type is written between [ ], and the
value type follows, as we learned all about maps in Chapter 6. In any nontrivial program, it is useful to give the special types that you use a name. In
Go, we do this with the keyword type, so we define a: type URLStore
map[string]string . It maps short URLs to long URLs; both are strings.
To make a variable of that type named urls , just use: urls := make(URLStore) .
Suppose we want to store the mapping of http://goto/a to http://google.com/ in
urls ; we can do this with the statement:
urls ["a"] = "http://google.com/"
We just store the suffix of http://goto/ as a key, and what comes before the key
is a fixed URL prefix.
To retrieve its corresponding long URL given a , we write:
url := urls ["a"]
Then, the value of url is equal to “http://google.com/”. Note that with := we
don’t need to say that url is of type string; the compiler deduces the type
from the value on the right side.
Making it thread-safe #
Our URLStore variable is the central memory data store here. Once we get
some traffic, there will be many requests of type Redirect . These are read
operations, read with the given short URL as key, and they return the long URL
as value. However, requests of type Add are different; they change our
URLStore, adding new key-value pairs. When our service gets many update
type-requests at once, the following problem could arise: the add operation
could be interrupted by another request of the same type, and the long URL
would perhaps not be written as value. Also, there could be modifications
together with reads, possibly resulting in having the wrong values read. Our
map does not guarantee that once an update starts, it will terminate
completely before a new update begins. In other words: a map is not threadsafe, and goto will serve many requests concurrently. Therefore, we must
make our URLStore type-safe to access from separate threads.
The simplest and classical way to do this is to add a lock to updating
operations. In Go, this is a Mutex type from the sync package in the standard
library, which we now must import into our code. We change the type
definition of our URLStore to a struct type with two fields: the map and an
RWMutex from the sync package:
import "sync"
type URLStore struct {
urls map[string]string // map from short to long URLs
mu sync.RWMutex
}
An RWMutex has two locks: one for readers and one for writers. Many clients
can take the read lock simultaneously, but only one client can take the write
lock (to the exclusion of all readers), effectively serializing the updates and
making them take place consecutively. We will implement our read request
type Redirect in a Get function, and our write request type Add as a Set
function. The Get function looks like this:
func (s *URLStore) Get(key string) string {
s.mu.RLock()
url := s.urls[key]
s.mu.RUnlock()
return url
}
It takes a key (short URL) and returns the corresponding map value as a URL.
The function works on a variable s , which is a pointer to our URLStore . But,
before reading the value, we set a read-lock with s.mu.RLock() so that no
update can interrupt the read. After the read, we unlock so that pending
updates can start. What if the key is not present in the map? Then the zero
value for the string type (an empty string) will be returned. Notice the .
notation familiar from OO languages: the method RLock() is called on the
field mu of s .
The Set function needs both a key and a URL and has to use the write lock
Lock() to exclude any other updates at the same time. It returns a boolean
true or false value to indicate whether the Set was successful or not:
func (s *URLStore) Set(key, url string) bool {
s.mu.Lock()
_, present := s.urls[key]
if present {
s.mu.Unlock()
return false
}
s.urls[key] = url
s.mu.Unlock()
return true
}
With the form _, present := s.urls[key] , we can test to see whether our map
already contains the key. If so, present becomes true. Otherwise, it is false.
This is the so-called comma, ok form, which we frequently encounter in Go
code. If the key is already present, Set returns a boolean false value, and the
map is not updated because we return from the function (so we don’t allow
our short URL’s to be reused). If the key is not present , we add it to the map
and return true. The _ on the left side is a placeholder for the value, and it
indicates that we are not going to use it because we assign it to _ . Note that as
soon as possible (after the update), we Unlock() our URLStore .
Using defer to simplify the code #
In this case, the code is still simple, and it is easy to remember to do the
Unlock() . However, in more complex code, this might be forgotten or put in
the wrong place, leading to problems that are often difficult to track down.
For these kinds of situations, Go has a special keyword defer , which allows, in
this case, the Unlock to be signaled immediately after the Lock . However, its
effect is that the Unlock() will only be done just before returning from the
function.
Get can be simplified to (we have eliminated the local variable URL):
func (s *URLStore) Get(key string) string {
s.mu.RLock()
defer s.mu.RUnlock()
return s.urls[key]
}
The logic for Set also becomes somewhat clearer (we don’t need to think
about unlocking anymore):
func (s *URLStore) Set(key, url string) bool {
s.mu.Lock()
defer s.mu.Unlock()
_, present := s.urls[key]
if present {
return false
}
s.urls[key] = url
return true
}
A factory for URLStore #
The URLStore struct contains a map field, which must be initialized with make
before it can be used. Making the value of a struct is done in Go by defining a
function with the prefix New, that returns an initialized value of the type
(here, and in most cases, a pointer to it):
func NewURLStore() *URLStore {
return &URLStore{ urls: make(map[string]string) }
}
In return, we make a URLStore literal with our map initialized. The lock
doesn’t need to be specifically initialized; this is the standard way in Go of
making struct objects. & is the address-of operator, to return a pointer
because NewURLStore returns the pointer *URLStore . We call this function to
make a URLStore variable store:
var store = NewURLStore()
Using our URLStore #
To add a new short/long URL pair to our map, all we have to do is call the Set
method on s , and since this is a boolean, we can immediately wrap it in an ifstatement:
if s.Set("a", "http://google.com") {
// success
}
To retrieve the long URL given a short URL, we call the Get method on s and
put the result in a variable url :
if url := s.Get("a"); url != "" {
// redirect to url
} else {
// key not found
}
Here, we make use of the fact that, in Go, an if can start with an initializing
statement before the condition.
We may also need a method Count to give us the number of key-value pairs in
the map; this is given by the built-in len function:
func (s *URLStore) Count() int {
s.mu.RLock()
defer s.mu.RUnlock()
return len(s.urls)
}
How will we compute the short URL given the long URL? For this, we use a
function genKey(n int) string {...} ; as an argument, we will pass it the
current value of s.Count() .
The exact algorithm is of little importance. We can now make a Put method
that takes a long URL, generates its short key with genKey , stores the URL
under this (short URL) key with the Set method, and returns that key:
func (s *URLStore) Put(url string) string {
for {
key := genKey(s.Count())
if s.Set(key, url) {
return key
}
}
// shouldn't get here
return ""
}
The for loop retries the Set until it is successful (meaning that we have
generated a not yet existing short URL).
Until now, we have defined our data structures and functions. In the next
lesson, we will define a web server to deliver the Add and the Redirect
services.
Our User Interface
This lesson shows the implementation of the application by designing a server and making a front end to see how
things work.
WE'LL COVER THE FOLLOWING
• Designing a server
• The Add function
• The Redirect function
• Testing the program
Designing a server #
We haven’t yet coded the function with which our program must be started.
This is (always) the function main() as in C, C++ or Java. In it, we will start our
web server, e.g., we can start a local web server on port 8080 with the
command:
http.ListenAndServe(":8080", nil)
The web server listens for incoming requests in an infinite loop, but we must
also define how this server responds to these requests. We do this by making
so-called HTTP handlers with the function HandleFunc . For example, by coding
http.HandleFunc("/add", Add) we say that every request which ends in /add
will call a function Add (still to be made).
Our program will have two HTTP handlers:
Redirect , which redirects short URL requests
Add , which handles the submission of new URLs
Schematically:
/
http Package
Redirect Handler
Get
Add Handler
Put
/add
URL Store
Handler Functions in goto
Our minimal main() could look like:
func main() {
http.HandleFunc("/", Redirect)
http.HandleFunc("/add", Add)
http.ListenAndServe(":8080", nil)
}
Requests to /add will be served by the Add handler, where all the other
requests will be served by the Redirect handler. Handler functions get
information from an incoming request (a variable r of type *http.Request ),
and they make and write their response to a variable w of type
http.ResponseWriter .
The Add function #
What must our Add function do?
Read in the long URL, which means reading the URL from an HTML-form
contained in an HTTP request with r.FormValue("url") .
Put it in the store using our Put method on the store.
Send the corresponding short URL to the user.
Each requirement translates in one code line:
func Add(w http.ResponseWriter, r *http.Request) {
url := r.FormValue("url")
key := store.Put(url)
fmt.Fprintf(w, "%s", key)
}
The function Fprintf of the fmt package is used here to substitute a key in
the string containing %s and then sends that string as a response back to the
client. Notice that Fprintf writes to a ResponseWriter . In fact, Fprintf can
write to any data structure that implements io.Writer() , which means that it
implements a Write() method. io.Writer() is what is called, in Go, an
interface. We see that through the use of interfaces, Fprintf is very general;
it can write to a lot of different things. The use of interfaces is pervasive in Go
and makes code more generally applicable. However, we still need a form; we
can display a form by using Fprintf again. This time writing a constant to w .
Let’s modify Add to display an HTML form when no URL is supplied:
func Add(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
url := r.FormValue("url")
if url == "" {
fmt.Fprint(w, AddForm)
return
}
key := store.Put(url)
fmt.Fprintf(w, "%s", key)
}
const AddForm = `
<html><body>
<form method="POST" action="/add">
URL: <input type="text" name="url">
<input type="submit" value="Add">
</form>
<\html><\body>`
In that case, we send the constant string AddForm to the client, which is the
html, necessary for creating a form with an input field URL , and a submit
button, which when pushed will post a request ending in /add . So, the Add
handler function is again invoked, now with a value for URL from the text
field. (The `` are needed to make a raw string, otherwise, strings are enclosed
in “” as usual).
The Redirect function #
The Redirect function finds the key in the HTTP request path (the short URL
key is the request path minus the first character, this can be written in Go as
[1:]; for the request “/abc” the key would be “abc”), retrieves the
corresponding long URL from the store with the Get function, and sends an
HTTP redirect to the user. If the URL is not found, a 404 “Not Found” error is
sent instead.
func Redirect(w http.ResponseWriter, r *http.Request) {
key := r.URL.Path[1:]
url := store.Get(key)
if url == "" {
http.NotFound(w, r)
return
}
http.Redirect(w, r, url, http.StatusFound)
}
Here, http.NotFound and http.Redirect are helpers for sending common
HTTP responses.
Now, we have discussed all the code of Version-1. Compile and run the
program, which starts the web-server:
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import "sync"
type URLStore struct {
urls map[string]string
mu
sync.RWMutex
}
func NewURLStore() *URLStore {
return &URLStore{urls: make(map[string]string)}
}
func (s *URLStore) Get(key string) string {
s.mu.RLock()
defer s.mu.RUnlock()
return s.urls[key]
}
func (s *URLStore) Set(key, url string) bool {
s.mu.Lock()
defer s.mu.Unlock()
if _, present := s.urls[key]; present {
return false
}
s.urls[key] = url
return true
}
func (s *URLStore) Count() int {
s.mu.RLock()
defer s.mu.RUnlock()
return len(s.urls)
}
func (s *URLStore) Put(url string) string {
for {
key := genKey(s.Count()) // generate the short URL
if ok := s.Set(key, url); ok {
return key
}
}
// shouldn't get here
return ""
}
Note: If you’re running this code on your local machine, use port number
8080 instead of 3000 at line 21.
Testing the program #
Click the Run button, and wait for the terminal to start. Once it starts, type go
run *.go and perform the following steps:
Click on this link
Step 1
1 of 3
Browser opens a page
Step 1
2 of 3
Add "/add" at the end of the URL
Step 2
3 of 3
This starts our Add handler function. There isn’t yet any url variable in the
form, so the response is the HTML-form which asks for input:
Add Handler
Add a (long) URL for which you want a short equivalent, like
http://golang.org/pkg/bufio/#Writer, and press the button. The application
makes a short URL for you and prints a key, e.g., 0.
Response of Add Handler
Copy and paste the same URL you did before in your browser address box and
append the key at the end to it, received as a response by Add handler.
Redirect Handler
Response of Redirect Handler
The result is the Redirect handler in action, and the page of the long URL is
shown. You can stop this process with CTRL/C in the terminal window.
Note: If you are running this application on a local machine, to add a
URL, open a browser and request the URL: http://localhost:8080/add.
Then, add the long URL. Let’s suppose the server responds with 0 as a
key. Lastly, request the URL: http://localhost:8080/0.
Our application is now successful in redirecting to the same page with the
shorter URL. Our next concern is having proper storage for URLs that have
already been made shorter. Let’s cover this in detail in the next lesson.
Persistent Storage with gob
This lesson explains how to build persistent storage for the application.
When the goto process (the web-server on a port) ends, and
this has to happen sooner or later, the map with the
shortened URLs in memory will be lost. To preserve our map
data, we need to save them to a disk file. We will modify
URLStore so that it writes its data to a file, and restores that
data on goto start-up. For this, we will use Go’s
encoding/gob package, which is a serialization and
deserialization package that turns data structures into
arrays (or more accurately slices) of bytes and vice versa
(see Chapter 10).
With the gob package’s NewEncoder and NewDecoder functions, you can decide
where you write the data to or read it from. The resulting Encoder and
Decoder objects provide Encode and Decode methods for writing and reading
Go data structures to and from files. Encoder also implements the Writer
interface, and so does Decoder for the Reader interface.
We will add a new file field (of type *os.File ) to URLStore that will be a
handle to an open file that can be used for writing and reading.
type URLStore struct {
urls map[string]string
mu sync.RWMutex
file *os.File
}
We will call this file store.gob and give that name as a parameter when we
instantiate the URLStore :
var store = NewURLStore("store.gob")
Now, we have to adapt our NewURLStore function:
func NewURLStore(filename string) *URLStore {
s := &URLStore{urls: make(map[string]string)}
f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatal("URLStore:", err)
}
s.file = f
return s
}
The NewURLStore function now takes a filename argument, opens the file, and
stores the *os.File value in the file field of our URLStore variable store, here,
locally called s . The call to OpenFile can fail (our disk file could be removed
or renamed for example). It can return an error err ; notice how Go handles
this:
f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatal("URLStore:", err)
}
When err is not nil, meaning there was an error, we stop the program with a
message. This is one way of doing it: mostly the error is returned to the calling
function. However, this pattern of testing errors is ubiquitous in Go code.
After the }, we are certain the file is opened. We open this file with writing
enabled, more exactly in append-mode. Each time a new (short, long) URL
pair is made in our program, we will store it through gob in the file store.gob.
For that purpose, we define a new struct type record :
type record struct {
Key, URL string
}
and a new save method that writes a given key and URL to disk as a gobencoded record:
func (s *URLStore) save(key, url string) error {
e := gob.NewEncoder(s.file)
return e.Encode(record{key, url})
}
At the start of goto , our datastore on disk must be read into the URLStore
map; for this, we have a load method:
func (s *URLStore) load() error {
if _, err := s.file.Seek(0, 0); err != nil {
return err
}
d := gob.NewDecoder(s.file)
var err error
for err == nil {
var r record
if err = d.Decode(&r); err == nil {
s.Set(r.Key, r.URL)
}
}
if err == io.EOF {
return nil
}
return err
}
The new load method will seek to the beginning of the file, read and decode
each record, and store the data in the map using the Set method. Again,
notice the all-pervasive error-handling. The decoding of the file is an infinite
loop which continues as long as there is no error:
for err == nil {
...
}
If we get an error, it could be because we have just decoded the last record
and the error io.EOF (EndOfFile) occurs. If this is not the case, we have an
error while decoding, and this is returned with the return err . This method
must be added to NewURLStore:
func NewURLStore(filename string) *URLStore {
s := &URLStore{urls: make(map[string]string)}
f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatal("Error opening URLStore:", err)
}
s.file = f
if err := s.load(); err != nil {
log.Println("Error loading data in URLStore:", err)
}
return s
}
Also in the Put function, when we add a new url-pair to our map, this should
also be saved immediately to the datafile:
func (s *URLStore) Put(url string) string {
for {
key := genKey(s.Count())
if s.Set(key, url) {
if err := s.save(key, url); err != nil {
log.Println("Error saving to URLStore:", err)
}
return key
}
}
panic("shouldn't get here")
}
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"fmt"
"net/http"
)
var store = NewURLStore("store.gob")
func main() {
http.HandleFunc("/", Redirect)
http.HandleFunc("/add", Add)
http.ListenAndServe(":3000", nil)
}
func Redirect(w http.ResponseWriter, r *http.Request) {
key := r.URL.Path[1:]
url := store.Get(key)
if url == "" {
http.NotFound(w, r)
return
}
http.Redirect(w, r, url, http.StatusFound)
}
func Add(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
url := r.FormValue("url")
if url == "" {
fmt.Fprint(w, addForm)
return
}
key := store.Put(url)
fmt.Fprintf(w, "%s", key)
}
const addForm = `
<html><body>
<form method="POST" action="/add">
URL: <input type="text" name="url">
<input type="submit" value="Add">
</form>
</html></body>
`
Click RUN and type go run *.go . Wait for the terminal to start. Once it’s
started, click the URL next to the Your app can be found at and follow the
steps taught in the previous lesson.
Remark: To run it locally, change line 13 to
http.ListenAndServe(":8080", nil) . To test this program, open a browser
at http://localhost:8080.
Goroutines can be used to increase performance. The next lesson explains
how to do so.
Using goroutines for Performance
This lesson focuses on handling multiple clients at the same time through goroutines.
WE'LL COVER THE FOLLOWING
• Flags for user’s interaction
There is still a performance problem with the 2nd version if too many clients
attempt to add URLs simultaneously. Our map is safely updated for
concurrent access thanks to the locking mechanism, but the immediate
writing of each new record to disk is a bottleneck. The disk writes may
happen simultaneously, and depending on the characteristics of your OS, this
may cause corruption. Even if the writes do not collide, each client must wait
for their data to be written to disk before their Put function will return.
Therefore, on a heavily I/O-loaded system, clients will wait longer than
necessary for their Add requests to go through. To remedy these issues, we
must decouple the Put and save processes; we do this by using Go’s
concurrency mechanism. Instead of saving records directly to disk, we send
them to a channel, which is a kind of buffer, so the sending function doesn’t
have to wait for it.
The save process, writes to disk reads from that channel and is started on a
separate thread by launching it as a goroutine called saveloop. The main
program and saveloop are now executed concurrently, so there is no more
blocking. We replace the file field in URLStore by a channel of records: save
chan record .
type URLStore struct {
urls map[string]string
mu sync.RWMutex
save chan record
}
A channel, just like a map, must be made with make ; we will do this in our
changed factory NewURLStore and give it a buffer length of 1000, like:
save := make(chan record, saveQueueLength)
To remedy our performance situation instead of making a function call to
save each record to disk, Put can send a record to our buffered channel save:
func (s *URLStore) Put(url string) string {
for {
key := genKey(s.Count())
if s.Set(key, url) {
s.save <- record{key, url}
return key
}
}
panic("shouldn't get here")
}
At the other end of the save channel, we must have a receive. Our new
method saveLoop will run in a separate goroutine; it receives record values
and writes them to a file. The saveLoop is also started in the NewURLStore()
function with the keyword go , and we can remove the now-unnecessary file
opening code. Here is the modified NewURLStore() :
const saveQueueLength = 1000
func NewURLStore(filename string) *URLStore {
s := &URLStore{
urls: make(map[string]string),
save: make(chan record, saveQueueLength),
}
if err := s.load(filename); err != nil {
log.Println("Error loading URLStore:", err)
}
go s.saveLoop(filename)
return s
}
Here is the code for the method saveloop :
func (s *URLStore) saveLoop(filename string) {
f, err := os.Open(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatal( URLStore: , err)
}
defer f.Close()
e := gob.NewEncoder(f)
for {
r := <-s.save // taking a record from the channel and encoding it
if err := e.Encode(r); err != nil {
log.Println("URLStore:", err)
}
}
}
Records are read from the save channel in an infinite loop and encoded to the
file. In chapter 12, we studied goroutines and channels in-depth, but here we
have seen a useful example for better managing the different parts of a
program. Notice also that now we only make our Encoder object once.
Flags for user’s interaction #
Another improvement can be introduced to make goto more flexible. Instead
of coding the filename, the listener address and the hostname hard-coded, or
as constants in the program, we can define them as flags. That way, they can
be given new values if they are typed in on the command line when starting
the program. If this is not done, the default value from the flag will be taken.
This functionality comes from a different package, so we have to: import
"flag" .
We first create some global variables to hold the flag values:
var (
listenAddr = flag.String("http", ":3000", "http listen address")
dataFile = flag.String("file", "store.gob", "data store file name")
hostname = flag.String("host", "1dkne4jl5mmmm.educative.run", "host nam
e and port")
)
In your case, the url will be different which is next to the Your app can be
found at.
For processing command-line parameters, we must add flag.Parse() to the
main function, and instantiate the URLStore after the flags have been parsed,
once we know the value of dataFile (in the code *dataFile is used. This is
because a flag is a pointer and must be dereferenced to get the value):
var store *URLStore
func main() {
flag.Parse()
store = NewURLStore(*dataFile)
http.HandleFunc("/", Redirect)
http.HandleFunc("/add", Add)
http.ListenAndServe(*listenAddr, nil)
}
Compile and test this Version-3 like in the previous versions.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"flag"
"fmt"
"net/http"
)
var (
listenAddr = flag.String("http", ":3000", "http listen address")
dataFile
= flag.String("file", "store.gob", "data store file name")
hostname
= flag.String("host","1dkne4jl5mmmm.educative.run", "http host name")
)
var store *URLStore
func main() {
flag.Parse()
store = NewURLStore(*dataFile)
http.HandleFunc("/", Redirect)
http.HandleFunc("/add", Add)
http.ListenAndServe(*listenAddr, nil)
}
func Redirect(w http.ResponseWriter, r *http.Request) {
key := r.URL.Path[1:]
url := store.Get(key)
if url == "" {
http.NotFound(w, r)
return
}
http.Redirect(w, r, url, http.StatusFound)
}
func Add(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
url := r.FormValue("url")
if url == "" {
fmt.Fprint(w, addForm)
return
}
key := store.Put(url)
fmt.Fprintf(w, "%s", key)
}
const addForm = `
<html><body>
<form method="POST" action="/add">
URL: <input type="text" name="url">
<input type="submit" value="Add">
</form>
</html></body>
`
Click the RUN button and type go run *.go .
In case you want to run multiple clients, open console-windows. In each
terminal, a client process is started performing the following steps for each
separate terminal:
Type usr/local/go/src and press ENTER.
Type go run *.go and press ENTER.
Click the URL next to Your app can be found at: .
Remark: To run it locally, change the port in line 10 to 8080 . Change
line 12 as hostname = flag.String("host","localhost:8080", "http host
name") . To test this program open browser at http://localhost:8080.
That’s how performance can be made efficient using gob. The next lesson
focuses on JSON to achieve the same.
Persistent Storage with JSON
This lesson explains how to build persistent storage for the application using JSON.
WE'LL COVER THE FOLLOWING
• Using JSON for storage
• Multiprocessing on many machines
• Using a ProxyStore
Using JSON for storage #
If you are a keen tester you will perhaps have noticed that when goto is
started two times, the 2nd time it has the short URLs and works perfectly.
However, from the 3rd start onwards, we get the error: Error loading
URLStore: extra data in the buffer . This is because gob is a stream-based
protocol that doesn’t support restarting. We will remedy this situation, here,
by using JSON as a storage protocol, which stores the data as plain text, so it
can also be read by processes written in other languages than Go. This also
shows how easy it is to change to a different persistent protocol because the
code dealing with the storage is cleanly isolated in 2 methods, namely load
and saveLoop .
Start by creating a new empty file store.json, and change the line in main.go
where the variable for the file is declared:
var dataFile = flag.String("file", "store.json", "data store file name")
In store.go, import json instead of gob . Then, in saveLoop , the only line that
has to be changed is: e := gob.NewEncoder(f) . We change it in: e :=
json.NewEncoder(f) . Similarly, in the load method, the line d :=
gob.NewDecoder(f) is changed to d := json.NewDecoder(f) . This is everything
we need to change! Compile, start and test; you will see that the previous error
does not occur anymore.
As an exercise, write a distributed version of the program, as described in the
following section.
Multiprocessing on many machines #
Until now, goto runs as a single process, but even using goroutines, a single
process that runs on one machine can only serve so many concurrent
requests. A URL shortener typically serves many more Redirects (reads, using
Get() ) than it does Adds (writes, using Put()). So, it is better to create an
arbitrary number of read-only slaves that serve and cache Get requests, and
pass Puts through to the master, like in the following schema:
goto manager
rpc
goto slave
HTTP
rpc
goto slave
HTTP
rpc
goto slave
HTTP
HTTP Load Balancer or
DNS Round Robin
Use HTTP Request
Internet
Distributing Workload over Master-and Slave Nodes
To run a master instance of the application goto on another computer in a
network other than the slave process(es), they have to be able to communicate
with each other. Go’s rpc package provides a convenient means of making
function calls over a network connection, making URLStore an RPC service.
The slave process(es) will handle Get requests to deliver the long URLs. When
a new long URL has to be transformed into a short one (using a Put()
method), they delegate that task to the master process through an RPCconnection; so, only the master will have to write to the data file.
The basic Get() and Put() methods of URLStore , until now, have the
signatures:
func (s *URLStore) Get(key string) string
func (s *URLStore) Put(url string) string
RPC can only work through methods with the signature (t is a value of type
T ):
func (t T) Name(args *ArgType, reply *ReplyType) error
To make URLStore an RPC service, we need to alter the Put and Get methods
so that they match this function signature. This is the result:
func (s *URLStore) Get(key, url *string) error
func (s *URLStore) Put(url, key *string) error
With this changed signature in mind, write the code for the new Get() , Put()
and Set() functions. Don’t forget to adapt the call to Set() from load() to
s.Set(&r.Key, &r.URL) , because the arguments are now pointers. You also
have to modify the HTTP Redirect and Add handlers to accommodate the
changes to URLStore .
Add a command-line flag to enable the RPC server in main(), as follows:
var rpcEnabled = flag.Bool("rpc", false, "enable RPC server")
To make RPC work, we have to register the URLStore with the rpc package
and set up the RPC-over-HTTP handler with HandleHTTP , like this:
func main() {
flag.Parse()
store = NewURLStore(*dataFile)
if *rpcEnabled { // flag has been set
rpc.RegisterName("Store", store)
rpc.HandleHTTP()
}
... (set up http like before)
}
Using a ProxyStore #
Now that you have the URLStore available as an RPC service , build
another type (call it ProxyStore ) that represents the rpc-client and that
will forward requests to the RPC server:
type ProxyStore struct {
client *rpc.Client
}
The goal is that the slave(s) use the ProxyStore , only the master uses the
URLStore .
Make a function NewProxyStore that creates our ProxyStore object. An
RPC-client is created in that function by using the DialHTTP() method to
connect to an RPC-server. This ProxyStore also needs Get and Put
methods that pass the requests directly to the RPC server using the Call
method of the RPC client:
func (s *ProxyStore) Get(key, url *string) error {
return s.client.Call("Store.Get", key, url)
}
func (s *ProxyStore) Put(url, key *string) error {
return s.client.Call("Store.Put", url, key)
}
In order for the slaves to handle the Get requests, they must have a copy
(a cache) of the URLStore . So, add a pointer to URLStore to the
ProxyStore . Adapt the function NewProxyStore .
Then, modify the NewURLStore function so that it doesn’t try to write to or
read from disk if an empty filename is given.
Expand the Get method, so that it first checks if the key is in the cache. If
present, Get returns the cached result. If not, it should make the RPC call,
and update its local cache with the result.
Similarly, the Put method needs to only update the local cache when it
performs a successful RPC-Put.
You perhaps realized that URLStore and ProxyStore look very much alike:
they both implement Get and Put methods with the same signature. So,
try to specify an interface Store to generalize their behavior. Now, our
global variable store can be of type Store :
var store Store
Finally, adapt the main() function so that either a slave- or a master
process is started up (and we can only do that because store is now of the
interface type Store ). To that end, add a new command line flag:
var masterAddr = flag.String("master", "", "RPC master address")
with no default value. Adapt main() so that if a master address is given,
start a slave process and make a new ProxyStore ; otherwise, start a
master process and make a new URLStore . Now, you have enabled
ProxyStore to use the web front-end, instead of URLStore . The rest of the
front-end code continues to work as before. It does not need to be aware
of the Store interface. Only the master process will write to the data file.
Now, we can launch a master and several slaves and stress-test the slaves.
Compile this version like the previous ones.
Environment Variables
Key:
Value:
GOROOT
/usr/local/go
GOPATH
//root/usr/local/go/src
PATH
//root/usr/local/go/src/bin:/usr/local/go…
package main
import (
"flag"
"fmt"
"net/http"
"net/rpc"
)
var (
listenAddr = flag.String("http", ":3000", "http listen address")
dataFile
= flag.String("file", "store.json", "data store file name")
hostname
= flag.String("host", ":3000", "http host name")
masterAddr = flag.String("master", "", "RPC master address")
rpcEnabled = flag.Bool("rpc", false, "enable RPC server")
)
var store Store
func main() {
flag.Parse()
if *masterAddr != "" {
store = NewProxyStore(*masterAddr)
} else {
store = NewURLStore(*dataFile)
}
if *rpcEnabled { // the master is the rpc server:
rpc.RegisterName("Store", store)
rpc.HandleHTTP()
}
http.HandleFunc("/", Redirect)
http.HandleFunc("/add", Add)
http.ListenAndServe(*listenAddr, nil)
}
func Redirect(w http.ResponseWriter, r *http.Request) {
key := r.URL.Path[1:]
if key == "" {
http.NotFound(w, r)
return
}
var url string
if err := store.Get(&key, &url); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, url, http.StatusFound)
}
func Add(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
url := r.FormValue("url")
if url == "" {
fmt.Fprint(w, addForm)
return
}
var key string
if err := store.Put(&url, &key); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "%s", key)
}
const addForm = `
<html><body>
<form method="POST" action="/add">
URL: <input type="text" name="url">
<input type="submit" value="Add">
</form>
</html></body>
`
Click the RUN button and wait for the terminal to start. Then type go run
*.go .
Remark: To run it locally, change the port in line 11 to 8080 . Change
line 13 as hostname = flag.String("host","localhost:8080", "http host
name") . To test this program open browser at http://localhost:8080
Enhancements
This lesson lists some main points to bring possible improvements in the application.
WE'LL COVER THE FOLLOWING
• Possible improvements
Possible improvements #
By gradually building up our goto application, we used nearly all of Go’s
essential features. While this program does what we set out to do, there are a
few ways it could still be improved:
Aesthetics: the user interface could be (much) prettier. For this, you
would use Go’s template package.
Reliability: the master/slave RPC connections could be more reliable,
which means that if the client-server connection goes down; the client
should attempt to re-dial. A dialer goroutine could manage this.
Resource exhaustion: as the size of the URL database grows, memory
usage might become an issue. It could be resolved by dividing (sharing)
the master servers by key.
Deletion: to support the deletion of shortened URLs, the interactions
between master and slave would need to be made more complicated.
That’s all about The Way to Go. In case you want to run Golang locally on
your machine you can follow the Appendix.
We at Educative hope that this course was a good learning experience for
you. Thank you for following it.
Your feedback, comments, concerns, and questions are always welcome. Drop
us an email or a comment on the community forum.
Looking forward to hearing from you! πŸ™‚
Platforms and Architectures
This lesson discusses the support of Go on multiple platforms and architectures.
WE'LL COVER THE FOLLOWING
• Compatible operating systems
• Flexibility provided by Go
• Go compilers
• File extensions and packages
Compatible operating systems #
Some architectures were considered to design the
compilers for Go according to their supportive OS.
The Go-team and the community developed
compilers for the following operating systems (OS):
Linux 2.6.23 or later
FreeBSD 10.3 or later
Mac OS (also named Darwin) 10.10 or later
Windows 7, Server 2008 R2 or later
ChromeOS
NetBSD
Solaris
Plan9
The following OS-processor combinations
(instruction sets) are released:
GO
OS-Architecture Combinations
The latest Go release at the time of writing was Go 1.11 (released Aug 2018),
and work on Go 2.0 is underway.
Flexibility provided by Go #
The portability of Go-code between OS’s is excellent. Assuming you only use
pure Go-code, no cgo, inotify, or any low-level packages, then you can copy the
source code to the other OS and compile, but you can also cross-compile Go
sources, which you will learn in the next lesson. To present a simpler, more
unified and OS-independent interface to the developer from Go1 onwards, the
compiler, linker, make-scripts, and so on were all brought together in one toolchain: the go command, which is written in Go and works in an OSindependent manner.
Go compilers #
The Go compilers and linkers are written in C and produce native code; there
is no Go bootstrapping or self-hosting involved. It means a different compiler
(instruction set) is required for every combination of processor-architecture
(32 bit and 64 bit) and OS. The compilers produce state of the art native code
and are not linkable with gcc. Furthermore, they work single-pass, non-
compacting, and parallel.
The sources of the compilers and linkers can be found under:
$GOROOT/src/cmd
Modifying the Go language itself is done in C code, not Go. The lexer/parser is
generated with GNU bison. An overview of the build process can be seen by
examining:
$GOROOT/src/make.bash
Most of the directories contain a file doc.go, that provides more information.
File extensions and packages #
The extension for Go source code files is, not surprisingly, .go. C files end in .c
and assembly files in .s, where source code-files are organized in packages.
Compressed files for packages containing executable code have the extension
.a (AR archive). Linker (object) files can have the extension .o. An executable
program has the extension .exe on Windows by default.
Remember, when creating directories for working with Go or Go-tools, never
use spaces in the directory name, you can replace space by ‘_’.
Compilers for Go differ for different platforms to provide full support and
maximum flexibility. In the next lesson, you will learn how to install Go on
your local machine, if the OS of your device has a Go compiler designed for it.
Installing Go and the Go Tools-Chain in Binary Format
This lesson serves as a guide to show how the installation of Go can be processed and veri ed.
WE'LL COVER THE FOLLOWING
• Go binaries
• Installing Go binaries
• For Windows
• For FreeBSD and Linux
• For Mac OS
• Making Go binaries system-wide available
• Installing to a custom folder
• Testing the installation
• Updating the installation
• What is installed on your machine?
Go binaries #
The already compiled code that allows users to download the program on the
machine without needing to compile the source code is called a binary.
You can download GO binaries here, where you choose the file appropriate
for your OS and processor architecture: msi or zip for Windows, pkg for Mac
OS and tar.gz for Linux and FreeBSD.
Installing Go binaries #
The Go binary distributions assume they will be installed in /usr/local/go or
c:\go, but it is possible to install them in a different location. During
installation, the GOROOT environment variable is set by default to this root of
the Go installation tree. For example, on Windows, GOROOT has the default
value c:\go.
For Windows #
Start the msi installer (double click) and follow the install wizard. The Go tree
will install in c:\Go. Alternatively, extract the zip file (with Winzip or another
popular compression tool) to c:\Go or another folder.
For FreeBSD and Linux #
Extract the archive into /usr/local, creating a Go tree in /usr/local/go For
example:
tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz
Choose the name of the archive file suitable for the installation. For instance,
if you are installing Go version 1.13.3 for 64-bit x86 on Linux, the archive you
want is called go1.13.1.linux-amd64.tar.gz. Typically, these commands must
be run as root or through sudo keyword.
For Mac OS #
Open the .pkg file and follow the prompts to install the Go tools. The package
installs the Go distribution to /usr/local/go.
Making Go binaries system-wide available #
To do that, the bin folder under GOROOT should be available in the PATH
system environment variable. In Windows and Mac OS, this is taken care of
by the installation process (any open Terminal sessions must be restarted for
the changes to take effect). In Linux, you must do this manually: add the
following line to your /etc/profile (for a system-wide installation) or
$HOME/.profile.
export PATH=$PATH:/usr/local/go/bin
or using GOROOT:
export PATH=$PATH:$GOROOT/bin
Installing to a custom folder #
If you installed Go to another directory, say d:\golang or $HOME/goinstall,
you must explicitly set GOROOT to that value to use the Go tooling. For
example, with Linux and Mac OS, if you installed Go to your home directory (
$HOME is /home/user1 if you are logged in as user1), you should add the
following commands to $HOME/.profile:
export GOROOT=$HOME/goinstall # only to be set if different from default
export PATH=$PATH:$GOROOT/bin
After the changes, restart terminal or reload .profile with: source .profile and
test the values with env | grep GO meaning or echo $GOROOT in a terminal
window.
In Windows, change the GOROOT variable to your installation folder and add
the path to the bin folder to PATH in the Environment Variables from System
Configuration. To create or change an environment variable, do the following:
Open Windows Explorer
Select Computer
Right Mouse Click: Properties -> Advanced System Settings ->Environment
Variables ->System Variables -> New (or Edit).
Testing the installation #
Using your favorite text editor, make a file with the following code, and save
this as hello_world1.go.
package main
import "fmt"
func main() {
// Printing Hello World
fmt.Println("Hello", "world")
}
Hello World
Compile, link and run this Go-program with the go tool as follows:
go run hello_world1.go
which produces the output as Hello world.
Go code comes in packages: packages of your own, packages of the standard
library like fmt, or packages written by others. It is the equivalent of modules
or libraries in other languages.
Updating the installation #
Delete any previous Go version by removing the installation folder, and then
proceed with the standard installation instructions.
For ARM support visit this page.
What is installed on your machine? #
We saw how to install Go on your machines having different operating
systems. You may wonder what the installation folder looks like. The structure
of Go tree, as the installation folder is called under the go-root folder
($GOROOT), is:
README, AUTHORS, CONTRIBUTORS, LICENSE
\api
these are data files for Go's API checker
\bin
contains the executables go, godoc and gofmt
\doc
tutorial programs, code walks,local documentation, lo
go’s, ...
\lib
\pkg\os_arch
md64,...
templates for the documentation
with os_arch e.g. linux_amd64, windows_386, windows_a
the object files (.a) of all the packages of the stan
dard library
\tool\os_arch
\include
\src
\test
contains the compiler, linker and other tools
C/C++ header files
Go source files of all
packages in the standard
library bash-scripts and make command files
all kinds of test files
Whether it be writing a Hello World program or designing a web application,
once Go binaries and tools are installed and tested, you’re good to go. You can
also install Go directly from the source, which the next lesson discusses!
Installing Go from Source
This lesson serves as a guide to show how the installation of Go from compilable source-code can be processed
and veri ed.
WE'LL COVER THE FOLLOWING
• Step 1: Install git if needed
• Step 2: Install a compiler
• Step 3: Fetch the Go repository
• Step 4: Build the source code
• Step 5: Get additional Go tools
• Step 6: Verifying the release of installation
• Step 7: Update to a newer release
It is instructive to download and compile the full source-code of Go yourself.
Complete and up to date instructions for that process are available here. We’ll
provide an overview of the different steps, providing additional info and
references as needed. Because the Go toolchain is written in Go, you need a
(previous) Go compiler installed to build it, as explained in the last lesson.
Let’s get an overview of the basic steps.
Step 1: Install git if needed #
To download the Go source code or to use the go get command, you need Git.
Check by typing git on a command-line. If it is not installed, download git
from this link.
Step 2: Install a compiler #
It is only needed if your Go packages call C code. Use the standard installation
methods for your system. Also, see this page for more details.
Step 3: Fetch the Go repository #
Go will install its source code in a folder named go. Change to the directory
that will be its parent and make sure go directory does not exist. Then, check
out the repository with the command:
$ git clone https://go.googlesource.com/go
$ cd go
$ git checkout go1.11.5
Change the version number in the last line to the version you want to install.
To play with the latest changes, you want to use the master branch:
git checkout master
Step 4: Build the source code #
To build the source code (in Linux/Mac OS) type the following command:
cd go/src
./all.bash
But, for Windows the last line is:
./all.bat
The building and testing takes some time (in the order of minutes), and when
successful, the following text appears on Linux/Mac OS:
ALL TESTS PASSED
--Installed Go for linux/amd64 in /home/user/go.
Installed commands in /home/user/go/bin.
*** You need to add /home/user/go/bin to your $PATH. ***
On Windows, the following text appears:
ALL TESTS PASSED
--Installed Go for windows/amd64 in c:\Go
Installed commands in c:\go\bin
Step 5: Get additional Go tools #
First, define the GOPATH variable, as explained in the next lesson. Then, issue
the following command:
go get golang.org/x/tools/cmd/...
Step 6: Verifying the release of installation #
Issue the following command:
go version
This will result (in Linux/Mac os) for example in the output:
go version go1.11.5 linux/amd64
Or, on Windows:
go version go1.11.5 windows/amd64
From within Go-code, the current version can be obtained with the function
Version from the package runtime .
package main
import (
"fmt"
"runtime"
)
func main() {
// Printing Version number of Go on your machine
fmt.Printf("%s", runtime.Version())
}
Go Version
Our platform has version 1.6.2. Thus, the output for the above executable is
go1.6.2.
Step 7: Update to a newer release #
For example, if you want to update to version 1.11.5, following code will do the
work:
cd go/src
git fetch
git checkout go1.11.5
Then, you have to repeat Step 4.
To release notes for different versions, view this page. The gofix tool can be
used to update the Go source-code (written in an older version) to the latest
release.
Now that you know how to install Go and check for newer releases let’s see
how to create a Go environment.
Go Work Environment
This lesson addresses how compilation looks in the background, what a workspace is and what the basic folder
structure of a Go environment is.
WE'LL COVER THE FOLLOWING
• Workspace
• GOPATH variable
• Other Go environment variables
• Cross-compiling
• Target machine
• Host machine
• Runtime environment
• Garbage collector
Workspace #
A folder structure with two directories at its root is
typically is called a workspace. A typical Golang
project keeps all its code in a single workspace. The
workspace of Golang is:
src, a folder that contains Go source code files
bin, a folder that contains executable binaries
(also called commands)
However, if you need external packages, there will
also be a folder called pkg to accommodate these.
GOPATH variable #
The workspace root location cannot be the same as the Go install location, and
it is specified by the GOPATH environment variable. It is by default a go
subfolder in the home directory, so $HOME/go on Unix or
C:\Users\username\go on Windows. Add the bin folder of your workspace to
the PATH variable so that your application binaries are automatically found.
For example on Linux add the following to your .profile:
export GOPATH=$HOME/go # this is the default
export PATH=$PATH:$GOPATH/bin
On Windows, GOPATH can also be added to the System Variables. GOPATH can
be any other folder, for example, e:\go_projects, but, then you have to set this
explicitly. The src branch can contain many code repositories, which are
version controlled (managed by Git or Mercurial). If you keep your code at
GitHub under github.com/user, then each repository folder is named with that
base path, like this:
- src
+ github.com/user/repo1
+ github.com/user/repo2
+ ...
+ (can be any remote package installed with go get)
Each repository contains one or more packages. Each package is a single
subfolder containing one or more Go code files.
Other Go environment variables #
We already encountered these two:
GOROOT—Go installation folder
GOPATH—Your application working directory
Cross-compiling #
Compiling on a host machine that has other characteristics than the target
machine, where you are going to run the Go application, is called crosscompiling.
The Go-environment can be further customized by several environment
variables. These are mostly optional and set by the installation procedure.
However, you can override them when needed. The Go compiler architecture
enables cross-compiling. By definition, there are two machines:
Target machine
Host machine
Target machine #
GOARCH indicates the processor-architecture of the target machine and can
have one of the values from the 3rd column of the figure below, like 386,
amd64, arm. GOOS indicates the operating system of the target machine and
can have one of the values from the 4th column of the figure below, like
darwin, Freebsd, Linux, Windows.
Host machine #
This is the local system. GOHOSTOS and GOHOSTARCH are the name of the
host operating system and compilation architecture. By default, target and
host architecture are the same, and GOHOSTOS and GOHOSTARCH take their
values from GOOS and GOARCH.
Cross Compiling to other Platforms/Architecture
The GOMAXPROCS variable can also be of use. This specifies the number of
cores or processors your application uses, as discussed in Chapter 12.
On a Unix system, set these variables (if needed) in your shell profile, which is
the $HOME/.profile or $HOME/.bashrc file or equivalent (use any editor like
gedit or vi) as:
export GOARCH=amd64
export GOOS=linux
The go run command does not produce an executable file output; it stores its
output in a folder specified by GOCACHE. This has the default value
$HOME/.cache/go-build on Unix and C:\Users\user\AppData\Local\go-build on
a Windows machine.
The command go env lists all Go environment variables.
Runtime environment #
Although the compiler generates native executable code, this code executes
within a runtime (the code of this tool is contained in the package runtime).
This runtime is minimal compared to the virtual machines used by Java and
.NET-languages. It is responsible for handling memory allocation and garbage
collection (as explained in Chapter 8), stack handling, goroutines, channels,
slices, maps, reflection, and more. Package runtime is the “top-level” package
that is linked to every Go package, and it is mostly written in C. It can be
found in $GOROOT/src/runtime/. Go executable files are much bigger in size
than the source code files. This is precisely because the Go runtime is
embedded in every executable. This could be a drawback when having to
distribute the executable to a large number of machines. On the other hand,
deployment is much easier than with Java or Python, because with Go,
everything needed sits in 1 static binary, no other files are needed. There are
no dependencies that can be forgotten or incorrectly versioned.
Garbage collector #
Go has a very efficient, low-latency concurrent collector, based on the markand-sweep algorithm. Having a garbage-collected language doesn’t mean you
can ignore memory allocation issues like allocating and deallocating memory
also uses CPU-resources.
Setting variables of the Go environment accurately provides a perfect
workspace to enable successful compilation. The next lesson covers the basic
requirements for a Go environment.
Basic Requirements for a Go Environment
This lesson lists some functionalities successfully covered in Go that are expected by users when programming in
any language.
WE'LL COVER THE FOLLOWING
• Desired functionalities
• Consultation
Desired functionalities #
What can you expect from an environment that you can
accomplish with a simple text editor and the compiler/link
tools on the command-line? Here is an extensive list of
desired functionalities:
Syntax highlighting: this is, of course, crucial, and
every environment cited provides a configuration—or
settings files for this purpose—preferably different
color-schemes (also customizable) should be available.
Automatic saving of code, at least before compiling.
Line numbering of code
Good overview and navigation in the codebase must be possible;
different source-files can be kept open.
Setting bookmarks in code is possible.
Bracket matching
From a function call, or type use, go to the definition of the function or
type
Excellent find and replace possibilities, the latter preferably with
preview
Being able to (un)comment lines and selections of code
Compiler errors: double-clicking the error-message should highlight the
offending code line.
Cross-platform: working on Linux, macOS, and Windows so that only
one environment needs to be learned/installed
Preferably free, although some developers willing to pay for a highquality environment
Preferably open-source
Plugin-architecture: relatively easy to extend or replace a functionality
by a new plugin.
Easy to use: an IDE is a complex environment, but still, it must have a
lightweight feel
Code snippets (templates): quick insertion of much-used pieces of code
for ease and accelerating the coding.
Closely related is the concept of a build system: it must be easy to
compile/link (build), clean (remove binaries) and/or run a program or a
project. Running a program should be possible in the console view or
inside the IDE.
Debugging capabilities (breakpoints, inspection of values, stepping
through executing code, being able to step over the standard library
code)
Easy access to recent files and projects
Code completion (IntelliSense) capabilities: syntax-wise (keywords),
packages, types and methods within packages, program types, variables,
functions, and methods; function signatures
An AST-view (abstract syntax tree) of a project/package code
Built-in access to the go tool-chain, such as go fmt , go fix , go install ,
go test , …
Convenient and integrated browsing of Go documentation
Easy switching between different Go-environments
Exporting code to different formats, such as pdf, Html or even printing
of the code
Project templates for special kinds of projects (such as a web
application, an App Engine project) to get you started quickly
Refactoring possibilities
Integration with version control-systems like hg or git
Integration with Google App Engine
Consultation #
There is very good support for Go in a whole stack of editors. Consult this
official page, and visit Github for the various options.
Go is becoming a demanding language due to the ease of use and flexibility. In
the next lesson, you’ll see some editors and IDE(s) commonly used for
programming in Golang.
Editors and Integrated Development Environments
This lesson lists some IDE(s) that provide support for Go and show how the con guration of environments
provides maximum ease.
WE'LL COVER THE FOLLOWING
• IDE(s) providing Go support
• Visual Studio Code with the vscode-go plugin
IDE(s) providing Go support #
Various development functionalities exist for editors ranging from Vim and
Emacs, text editors like BBEdit, Brackets, Gedit, Kate, Komodo, TextMate,
TextPad, JEdit, SublimeText, Atom and Visual Studio Code. Some more IDE-like
cross-platform environments exist for Go-programming (some are plugins for
existing (Java) environments):
GoClipse customizes the Eclipse IDE for Go development (click here).
GoWorks is an open-source Go IDE based on Netbeans (click here).
GoLand is an IDE made by JetBrains (click here).
LiteIDE is a simple, open-source, cross-platform Go IDE (click here).
In most IDE’s you can configure that building also saves and formats the latest
changes to the source file. As an example, here’s a more detailed discussion of
the free Visual Studio Code plugin.
Visual Studio Code with the vscode-go plugin #
This plugin provides rich development support, including:
auto-completion and IntelliSense
code navigation and definition look-up facilities
code snippets
formatting, renaming symbols, generating methods and structs
diagnostic linter and error reporting at save time and even while you
type code
integrated testing, benchmarking and debugging
installing go tools
uploading code to Playground
Much of this support is implemented as specific Go commands in the
Command Palette. Here is a screenshot using the plugin:
VSCode Go Plugin
Several cloud-based IDEs are also available, such as GitPod and Wide.
Editors and different IDE(s) provide different tools and facilities to make sure
that a programmer feels at home when writing code. Another major
functionality provided by IDE(s) is finding errors in the code. Let’s look at this
in the next lesson!
Debugging
This lesson discusses how errors can be tracked and removed when programming in Go.
WE'LL COVER THE FOLLOWING
• Debuggers in Go
• Debugging strategy
Debuggers in Go #
Application programming needs excellent debugging support. Most bigger
IDEs provide this kind of support. To use the debugger in Visual Studio Code,
you must currently manually install delve (see the Installation Instructions for
full details). On macOS, it requires creating a self-signed cert to sign the dlv
binary. An explanation on how to get running with debugging can be found
here: Debugging Go Code Using VS Code. It is also possible to remotely debug
code using VS Code; read Remote Debugging. Go can be debugged with GDB,
and many editors support that. GoClipse has fully featured GDB debugger
support (reusing Eclipse CDT’s GDB integration) comprising breakpoints and
conditions, watch and view, disassembly view, viewing threads and stack
frame contents. GoLand supports standard debugger features: Watches,
Evaluate Expression, Show Inline Values and others. The debugger works for
applications, as well as for tests.
Debugging strategy #
If you don’t want to use a debugger, the following is useful
as a simple debugging strategy:
Use print-statements (with the fmt.Print functions) at
well-chosen places.
In fmt.Printf functions, use the following specifiers to
obtain complete info about variables:
%v gives us a complete output of the value with its
fields.
%#v gives us a complete output of the value with
its fields and qualified type name.
%T gives us the complete type specification.
Use a panic statement to obtain a stack trace (a list of all the called
functions up until that moment).
Use the defer keyword in tracing code execution.
A Windows-version gocomp.bat to compile all source files in the current
directory is even shorter:
FOR %%X in (*.go) DO go build %%X >> compout
Debugging the errors and making a program error-free is one of the major
concerns. Apart from this concern, designers are paying great attention to
provide the best formatting ideas that make a program easily readable at the
user-end. The next lesson discusses such techniques.
Formatting the Code
This lesson highlights the importance of formatting in Go and a tool that performs formatting automatically.
WE'LL COVER THE FOLLOWING
• Formatting in Go
• Formatting code with gofmt
Formatting in Go #
Go’s authors didn’t want endless discussions about codestyle for the Go-language. Specifically, the discussions that
were indeed, a waste of precious development time for so
many languages in the past. So, they made a tool: go fmt (or
with more possibilities: gofmt ). It is a pretty-printer that
imposes the official, standard code formatting and styling on
the Go source-code. It is also a syntax level rewriting tool, a
simple form of refactoring. It is used rigorously in Go
automatically and should be used by all Go-developers. Use
gofmt on your Go-program before compiling or checking in
to format it correctly to the idiomatic Go style.
Formatting code with gofmt #
In most IDE’s you can configure that saving the latest changes to the sourcefile also automatically formats the code with gofmt . For indentation of
different levels in code, the rule is not strict: tabs or spaces can be used. A tab
can be 4 or 8 spaces. In writing code in an editor or IDE, use tabs, don’t insert
spaces. On the command-line, you use it as follows:
go fmt program.go
This reformats program.go (without –w the changes are shown, but not
saved). go fmt *.go works on all go-files; instead of source files, you can also
specify packages. gofmt folder1 works recursively on all .go files in the
folder1 directory and its subdirectories.
It can also be used for simple changes (refactoring) in a codebase by
specifying a rule with the –r flag and a rule between ' the rule has to be of
the form:
pattern -> replacement
Look at some of the examples below:
gofmt -r '(a) -> a' –l *.go
This will replace all unnecessary doubled (( )) with () in all go-files in the
current directory.
gofmt -r 'a[n:len(a)] -> a[n:]' –w *.go
This will remove all superfluous len(a) in such slice-declarations.
gofmt –r 'A.Func1(a,b) -> A.Func2(b,a)' –w *.go
This will replace Func1 with Func2 and swap the function’s arguments.
For more info, visit this page.
The gofmt tool saves the time of users and makes the code readable. Now, it’s
time to learn how to build a program and run it locally on your machine.
Building and Running Go-Programs
This lesson discusses the steps required to build and run a Go program.
WE'LL COVER THE FOLLOWING
• The go command
• Messages from the compiler
• Compiling all go- les in a directory
The go command #
To compile and link a go program test.go, use:
go build test.go
If you want to compile, link and run the program, use:
go run test.go
where the executable binary is stored in the cache. If you want to compile,
link and install the package pack1 , use:
go install pack1
To remove any object and cache file, use:
go clean
Messages from the compiler #
If the build process (which is also called compile-time) doesn’t give any
errors, no message appears.
If building produces (an) error(s), we get the following output: ---- Build
file exited with code 1 , and the line number where the error occurs in
the source-code is indicated, like this, if we insert var a at line 6:
hello_world.go:6: syntax error: unexpected newline, expecting type
In most IDEs, double-clicking the error line positions the editor on that
code line where the error occurs.
Go does not distinguish between warnings and errors. The philosophy is:
when it is worth warning for misuse of something, it better be signaled as
an error to be always on the safe side.
If you try to execute a program which is not yet compiled, you get the
following error: ---- Error run with code File not found, resource
error .
When executing a program, you are in the run-time environment. If the
program executes correctly, after the execution the following is the
output: ---- Program exited with code 0 .
Compiling all go- les in a directory #
Here is the Linux-version of a useful script for the purpose of quick testing for
compilation errors in a lot of files:
#!/bin/bash
FILES=~/go_projects/src/*.go
for f in $FILES
do
echo "Processing $f file..."
# take action on each file. $f stores current filename
# cat $f
go build $f >> compout
done
You need to replace ~/go_projects/src/ with your folder name. This will
compile all the go-files in the current directory and write the output to
compout .
That’s it about building and running a Go program via the command line. The
next lesson discusses the help feature provided by Go.
Searching for Help and Documenting the Code
Generating documentation for code is a useful habit. This lesson discusses a tool that lets us document programs
and packages.
WE'LL COVER THE FOLLOWING
• Basics of godoc command
• General format
• Other uses of godoc command
Basics of godoc command #
The command godoc extracts and generates documentation
for Go programs and packages. It extracts comments that
appear before top-level declarations in the source code with
no intervening newlines. Together with the declaration, they
serve as explanatory text for the item. It can also function as
a web server delivering documentation; this is how the
golang-website works.
General format #
To get the package comment documentation for package, the following
format is used:
go doc package
For example:
go doc fmt
This comment will appear first on the output produced by godoc .
To get the documentation for subpackage in package:
go doc package/subpackage
For example:
go doc container/list
To get the documentation for function in package:
go doc package function
For example:
go doc fmt Printf
provides explanations for the use of fmt.Printf() .
Other uses of godoc command #
The command godoc itself has some more options. Godoc can also be started
as a local webserver:`
godoc -http=:6060
This starts the server on the command-line, then opens a browser window
with the URL: http://localhost:6060, and you have a local documentation
server for your installed version without the need for an internet connection.
Godoc can also be used to generate documentation for your own Go-programs
(see Chapter 7).
For more info, visit this page.
Apart from formatting and documenting tools, multiple tools make Go a
successful application programming language. The next lesson mentions some
of the important tools provided by Go.
Tools and Go's Performance
This lesson discusses the tools that make Go outshine other languages performance-wise.
WE'LL COVER THE FOLLOWING
• Other tools
• go get tool
• go vet tool
• go tool cover tool
• go fix tool
• go test tool
• Go’s performance
• Summary
Other tools #
The tools in the Go-toolset are partly shell-scripts; some are written in Go
itself. They are implemented as commands for the go tool. Here, we discuss
only a few of them. For more info, consult the docs.
go get tool #
It is the go-package manager tool, much like rubygems for the Ruby language.
It is meant for installing go packages outside of the standard library, and it
works with the source-code format of the package, which then has to be
compiled and linked locally.
go vet tool #
It is a tool to report likely mistakes in the code. This command is followed by
the name (or names) of the package(s) that have to be examined. It checks, for
example, for useless assignments, consistency in the use of the Printf
function, unused function results, and so on. Not all problems reported are
genuine, but it can find errors which the compilers did not catch.
go tool cover tool #
It is a tool that enables coverage analysis. It annotates the source code before
compiling, indicating which parts of your code are executed and called. It can
be used to detect useless code, or on the contrary, to track which parts are
heavily used.
go fix tool #
It is a tool that you can use to update Go source-code (outside of the standard
packages) from an older to the newest release: it tries to automatically fix
changes. It is meant to reduce the amount of effort it takes to update existing
code, automating it as much as possible. The gofix takes care of the easy,
repetitive, and tedious changes. If a change in API isn’t simply a substitution
of a new function and requires manual examination, gofix prints a warning
giving the file name and line number, so that a developer can examine and
rewrite the code. The Go-team regularly updates the tool together with new
API changes, and it is also used internally in Google to update the Go source
code. The gofix works because Go has support in its standard libraries for
parsing Go source files into (abstract) syntax trees (AST’s) and also for printing
those syntax trees back to Go source code. go fix . tries to update all .gofiles in the current directory when necessary. The changed file names are
printed on standard output. For example:
go fix -diff .
shows the differences with the installed Go-release. To update with new
changes, use:
go fix -w .
go test tool #
It is a lightweight test framework for unit-testing.
Go’s performance #
According to the Go-team and measured in simple algorithmic programs,
performance is typically 10-20% slower than C. There are no official
benchmarks, but regular experimental comparisons between languages show
a very good performance track. A more realistic statement is that Go is 20%
slower than C++.
In some cases, that difference is irrelevant, but for a company like Google with
thousands of servers, the potential efficiency improvements are certainly
worth the investment. The current popular languages execute in a virtual
machine: JVM for Java and Scala, .NET CLR for C#, and so on. Even with the
massive improvements in virtual machines, JIT-compilers, and scripting
language interpreters (Ruby, Python, Perl, JavaScript), none can come close to
C and C++ for performance.
If Go is 20% slower than C++, that’s still 2 to 10 times faster than any language
that’s not statically typed and compiled, and it is far more memory efficient.
Comparing benchmarks of a program in 2 or more languages is very tricky:
the programs each should do exactly the same things, utilizing the best
possible concepts and techniques for that task from the language. For
example, when processing text, the language that processes this as raw bytes
will almost certainly outperform the language that works with it as Unicode.
The person performing the benchmarks often writes both programs, but often
he/she is much more experienced in one language than in the other, and this
can very much influence the results. Each program should be written by a
developer who is well versed in the language. Otherwise, it is not difficult to
artificially influence the performance behavior of one language compared to
another; it is not an exact science. The outcome can also depend upon the
problem to solve. In most cases, older languages have optimally tuned
libraries for certain tasks.
Summary #
Go has a performance similar to Java and C++, and about 25x better than
dynamic languages like Nodejs or Python. Furthermore, Go has a small
runtime compared to Java or .NET, and it also has a much lower memory
consumption.
Go has made its place in the programming world. Due to its popularity, its
interaction with C and C++ is possible which we’ll look at in the next lesson.
Interaction with C and C++
Go lets its users write programs in combination with Go and C/C++. This lesson covers how a Go program can
have functionalities of C and C++ imported in it.
WE'LL COVER THE FOLLOWING
• Interacting with C
• Interacting with C++
Interacting with C #
The cgo program provides the mechanism for FFI-support (Foreign Function
Interface) to allow safe calling of C libraries from Go code. Here is the link to
the primary cgo documentation. The cgo replaces the normal Go-compilers,
and it outputs Go and C files that can be combined into a single Go package. It
is good practice to combine the calls to C in a separate package. The following
import is then necessary in your Go program:
import "C"
and usually also:
import "unsafe"
You can include C-libraries (or even valid C-code) by placing these statements
as comments (with // or /* */) immediately above the import "C" line:
// #include <stdio.h>
// #include <stdlib.h>
import "C"
C is not a package from the standard library, and it is simply a special name
interpreted by cgo as a reference to C’s namespace. Within this namespace
exist the C types denoted as C.uint , C.long , and so on, and functions like
C.random() from libc can be called. Variables in the Go program have to be
converted to the C type when used as parameters in C functions, and viceversa. For example:
var i int
C.uint(i) // from Go int to C unsigned int
int(C.random()) // from C long (random() gives a long type number) to Go i
nt
In the above snippet, variable i is a Go type variable, which is converted to C
type by C.unit() function. Then, in the next line, we create a random number
of type C using C.random() function, which is then converted to Go type using
int() function.
Strings do not exist as explicit type in C, which means to convert a Go string s
to its C equivalent use:
C.CString(s)
The reverse is done with the function:
C.GoString(cs)
where cs is a C string. Memory allocations made by C code are not known to
Go’s memory manager, so they are not garbage collected. It is up to the
developer to free the memory of C variables with C.free , as follows:
defer C.free(unsafe.Pointer(Cvariable))
This line best follows the line where Cvariable is created to make sure the
memory release happens at the end of the scope.
Interacting with C++ #
SWIG (Simplified Wrapper and Interface Generator) (see documentation)
support exists for calling C++ and C code from Go on Linux. Using SWIG is a
bit more involved:
Write the SWIG interface file for the library to be wrapped.
SWIG will generate the C stub functions.
These can then be called using cgo, doing so the Go files are automatically
generated as well.
This interface handles overloading, multiple inheritance, and allows us to
provide a Go implementation for a C++ abstract class.
That is all about interacting with C and C++. The next lesson includes some
helpful resources to catch up with Golang.
Helpful Resources
This lesson lists some helpful links and resources.
WE'LL COVER THE FOLLOWING
• Important links
Important links #
The principal website runs in Google App Engine with godoc (a Go-tool)
serving (as a web server) the content and a Python front-end.
You can visit the Help page to find links to the Go community (forums,
reddit, mailing-list, wiki, user-groups, and so on).
To see the current issues that are being debated or worked on click here.
The source code can be found here.
To view Wikipedia page on Go, click here.
Go can be interactively tried out in your browser via the Go Playground.
To get a first impression of the Go language, refer to this tour guide.
t a u y ope sou ce S
sty e ce se e eased o t e
u a d
ac OS
platforms. The first Windows-port was announced on November 22 of the
same year. Go 1.0 (the first production-ready version) was released in March
2012. Since 2012, Go has grown from version 1.1 to 1.12 (March 2019), and
work for Go 2.0 is underway!
The below figure shows the Go development timeline starting from its genesis
to the year 2019.
2007
Year of
Genesis
2009
Year of
Launch
2012
Year of
Publish
of Go
1
2019
Year of
Publish
of Go
1.12
Go Development Timeline
Programming language hall of fame #
Go initiated a lot of stir after its public release in January 2010. In 2009 and
again in 2016, Go was pronounced language of the year by Tiobe, which is
well-known for its popularity ranking of programming languages. In this
ranking, it secured the 10th place in November 2018, with a popularity of
nearly 2.5 %. The Programming Language of the Year award is given to the
programming language rated the highest in that year. Hall of fame is given
below:
Image from https://www.tiobe.com/tiobe-index/
That’s all for the introduction. Now let’s see why Go was developed.
Download