|
|
|
|
|
|
|
|
|
Go Working Group R. Gieben |
|
Internet-Draft 25 August 2018 |
|
Intended status: Informational |
|
Expires: 26 February 2019 |
|
|
|
|
|
Learning Go |
|
|
|
Status of This Memo |
|
|
|
This Internet-Draft is submitted in full conformance with the |
|
provisions of BCP 78 and BCP 79. |
|
|
|
Internet-Drafts are working documents of the Internet Engineering |
|
Task Force (IETF). Note that other groups may also distribute |
|
working documents as Internet-Drafts. The list of current Internet- |
|
Drafts is at https://datatracker.ietf.org/drafts/current/. |
|
|
|
Internet-Drafts are draft documents valid for a maximum of six months |
|
and may be updated, replaced, or obsoleted by other documents at any |
|
time. It is inappropriate to use Internet-Drafts as reference |
|
material or to cite them other than as "work in progress." |
|
|
|
This Internet-Draft will expire on 26 February 2019. |
|
|
|
Copyright Notice |
|
|
|
Copyright (c) 2018 IETF Trust and the persons identified as the |
|
document authors. All rights reserved. |
|
|
|
This document is subject to BCP 78 and the IETF Trust's Legal |
|
Provisions Relating to IETF Documents (http://trustee.ietf.org/ |
|
license-info) in effect on the date of publication of this document. |
|
Please review these documents carefully, as they describe your rights |
|
and restrictions with respect to this document. Code Components |
|
extracted from this document must include Simplified BSD License text |
|
as described in Section 4.e of the Trust Legal Provisions and are |
|
provided without warranty as described in the Simplified BSD License. |
|
|
|
Table of Contents |
|
|
|
1. Preface |
|
2. Learning Go |
|
3. Introduction |
|
3.1. How to Read this Book |
|
3.2. Official Documentation |
|
4. Basics |
|
4.1. Hello World |
|
4.2. Compiling and Running Code |
|
4.3. Variables, Types and Keywords |
|
4.3.1. Boolean Types |
|
4.3.2. Numerical Types |
|
4.3.3. Constants |
|
4.3.4. Strings |
|
4.3.5. Runes |
|
4.3.6. Complex Numbers |
|
4.3.7. Errors |
|
4.4. Operators and Built-in Functions |
|
4.5. Go Keywords |
|
4.6. Control Structures |
|
4.6.1. If-Else |
|
4.6.2. Goto |
|
4.6.3. For |
|
4.6.4. Break and Continue |
|
4.6.5. Range |
|
4.6.6. Switch |
|
4.7. Built-in Functions |
|
4.8. Arrays, Slices, and Maps |
|
4.8.1. Arrays |
|
4.8.2. Slices |
|
4.8.3. Maps |
|
4.9. Exercises |
|
4.9.1. For-loop |
|
4.9.2. Answer |
|
4.9.3. Average |
|
4.9.4. Answer |
|
4.9.5. FizzBuzz |
|
4.9.6. Answer |
|
5. Functions |
|
5.1. Scope |
|
5.2. Functions as values |
|
5.3. Callbacks |
|
5.4. Deferred Code |
|
5.5. Variadic Parameter |
|
5.6. Panic and recovering |
|
5.7. Exercises |
|
5.7.1. Average |
|
5.7.2. Answer |
|
5.7.3. Bubble sort |
|
5.7.4. Answer |
|
5.7.5. For-loop II |
|
5.7.6. Answer |
|
5.7.7. Fibonacci |
|
5.7.8. Answer |
|
5.7.9. Var args |
|
5.7.10. Answer |
|
5.7.11. Functions that return functions |
|
5.7.12. Answer |
|
5.7.13. Maximum |
|
5.7.14. Answer |
|
5.7.15. Map function |
|
5.7.16. Answer |
|
5.7.17. Stack |
|
5.7.18. Answer |
|
6. Packages |
|
6.1. Identifiers |
|
6.2. Documenting packages |
|
6.3. Testing packages |
|
6.4. Useful packages |
|
6.5. Exercises |
|
6.5.1. Stack as package |
|
6.5.2. Answer |
|
6.5.3. Calculator |
|
6.5.4. Answer |
|
7. Beyond the basics |
|
7.1. Allocation |
|
7.1.1. Allocation with new |
|
7.1.2. Allocation with make |
|
7.1.3. Constructors and composite literals |
|
7.2. Defining your own types |
|
7.2.1. More on structure fields |
|
7.2.2. Methods |
|
7.3. Conversions |
|
7.3.1. User defined types and conversions |
|
7.4. Exercises |
|
7.4.1. Map function with interfaces |
|
7.4.2. Answer |
|
7.4.3. Pointers |
|
7.4.4. Answer |
|
7.4.5. Linked List |
|
7.4.6. Answer |
|
7.4.7. Cat |
|
7.4.8. Answer |
|
7.4.9. Method calls |
|
7.4.10. Answer |
|
8. Interfaces |
|
8.1. Which is what? |
|
8.2. Empty interface |
|
8.3. Methods |
|
8.4. Methods on interface types |
|
8.5. Interface names |
|
8.6. A sorting example |
|
8.7. Listing interfaces in interfaces |
|
8.8. Introspection and reflection |
|
8.9. Exercises |
|
8.9.1. Answer |
|
8.9.2. Pointers and reflection |
|
8.9.3. Answer |
|
9. Concurrency |
|
9.1. Make it run in parallel |
|
9.2. More on channels |
|
9.3. Exercises |
|
9.3.1. Channels |
|
9.3.2. Answer |
|
9.3.3. Fibonacci II |
|
9.3.4. Answer |
|
10. Communication |
|
10.1. io.Reader |
|
10.2. Some examples |
|
10.3. Command line arguments |
|
10.4. Executing commands |
|
10.5. Networking |
|
10.6. Exercises |
|
10.6.1. Finger daemon |
|
10.6.2. Answer |
|
10.6.3. Echo server |
|
10.6.4. Answer |
|
10.6.5. Word and Letter Count |
|
10.6.6. Answer |
|
10.6.7. Uniq |
|
10.6.8. Answer |
|
10.6.9. Quine |
|
10.6.10. Answer |
|
10.6.11. Processes |
|
10.6.12. Answer |
|
10.6.13. Number cruncher |
|
10.6.14. Answer |
|
11. Informative References |
|
Author's Address |
|
|
|
1. Preface |
|
|
|
The source of this book (https://github.com/miekg/learninggo) is |
|
written in mmark (https://github.com/mmarkdown/mmark) and is |
|
converted from the original LaTeX source (https://github.com/miekg/ |
|
gobook). |
|
|
|
_All example code used in this book is hereby licensed under the |
|
Apache License version 2.0._ |
|
|
|
<blockquote>: |
|
This work is licensed under the Attribution-NonCommercial-ShareAlike |
|
3.0 Unported License. To view a copy of this license, visit |
|
http://creativecommons.org/licenses/by-nc-sa/3.0/ |
|
(http://creativecommons.org/licenses/by-nc-sa/3.0/) or send a letter |
|
to Creative Commons, 171 Second Street, Suite 300, San Francisco, |
|
California, 94105, USA. |
|
|
|
The following people made large or small contributions to earlier |
|
versions of this book: |
|
|
|
Adam J. Gray, Alexander Katasonov, Alexey Chernenkov, Alex Sychev, |
|
Andrea Spadaccini, Andrey Mirtchovski, Anthony Magro, Babu Sreekanth, |
|
Ben Bullock, Bob Cunningham, Brian Fallik, Cecil New, Cobold, Damian |
|
Gryski, Daniele Pala, Dan Kortschak, David Otton, Fabian Becker, |
|
Filip Zaludek, Hadi Amiri, Haiping Fan, Iaroslav Tymchenko, Jaap |
|
Akkerhuis, JC van Winkel, Jeroen Bulten, Jinpu Hu, John Shahid, |
|
Jonathan Kans, Joshua Stein, Makoto Inoue, Marco Ynema, Mayuresh |
|
Kathe, Mem, Michael Stapelberg, Nicolas Kaiser, Olexandr Shalakhin, |
|
Paulo Pinto, Peter Kleiweg, Philipp Schmidt, Robert Johnson, Russel |
|
Winder, Simoc, Sonia Keys, Stefan Schroeder, Thomas Kapplet, T.J. |
|
Yang, Uriel"\dagger", Vrai Stacey, Xing Xing. |
|
|
|
"Learning Go" has been translated into (note that this used the |
|
original LaTeX source). |
|
|
|
* Chinese, by Xing Xing, 这里是中文译本: http://www.mikespook.com/learning- |
|
go/ (http://www.mikespook.com/learning-go/) |
|
|
|
I hope this book is useful. |
|
|
|
Miek Gieben, London, 2015. |
|
|
|
This book still sees development, small incremental improvements |
|
trickle in from Github. |
|
|
|
Miek Gieben, London, 2017. |
|
|
|
Learning Go's source has been rewritten in mmark2 |
|
(https://github.com/mmarkdown/mmark), but did not see any other |
|
changes. |
|
|
|
Miek Gieben, London, 2018. |
|
|
|
2. Learning Go |
|
|
|
|
|
3. Introduction |
|
|
|
<blockquote>: |
|
Is Go an object-oriented language? Yes and no. |
|
|
|
The Go programming language is an open source project language to |
|
make programmers more productive. |
|
|
|
According to the website [go_web] "Go is expressive, concise, clean, |
|
and efficient". And indeed it is. My initial interest was piqued |
|
when I read early announcements about this new language that had |
|
built-in concurreny and a C-like syntax (Erlang also has built-in |
|
concurrency, but I could never get used to its syntax). Go is a |
|
compiled statically typed language that feels like a dynamically |
|
typed, interpreted language. My go to (scripting!) language Perl has |
|
taken a back seat now that Go is around. |
|
|
|
The unique Go language is defined by these principles: |
|
|
|
Clean and Simple |
|
Go strives to keep things small and beautiful. You should be able |
|
to do a lot in only a few lines of code. |
|
|
|
Concurrent |
|
Go makes it easy to "fire off" functions to be run as _very_ |
|
lightweight threads. These threads are called goroutines |
|
|
|
Channels |
|
Communication with these goroutines is done, either via shared |
|
state or via [csp]. |
|
|
|
Fast |
|
Compilation is fast and execution is fast. The aim is to be as |
|
fast as C. Compilation time is measured in seconds. |
|
|
|
Safe |
|
Explicit casting and strict rules when converting one type to |
|
another. Go has garbage collection. No more "free()" in Go: the |
|
language takes care of this. |
|
|
|
Standard format |
|
A Go program can be formatted in (almost) any way the programmers |
|
want, but an official format exists. The rule is very simple: The |
|
output of the filter "gofmt" _is the officially endorsed format_. |
|
|
|
Postfix types |
|
Types are given _after_ the variable name, thus "var a int", |
|
instead of "int a". |
|
|
|
UTF-8 |
|
UTF-8 is everywhere, in strings _and_ in the program code. |
|
Finally you can use "\Phi = \Phi + 1" in your source code. |
|
|
|
Open Source |
|
The Go license is completely open source. |
|
|
|
Fun |
|
Programming with Go should be fun! |
|
|
|
As I mentioned Erlang also shares some features of Go. A notable |
|
difference between Erlang and Go is that Erlang borders on being a |
|
functional language, while Go is imperative. And Erlang runs in a |
|
virtual machine, while Go is compiled. |
|
|
|
3.1. How to Read this Book |
|
|
|
I've written this book for people who already know some programming |
|
languages and how to program. In order to use this book, you (of |
|
course) need Go installed on your system, but you can easily try |
|
examples online in the Go playground. All exercises in this book |
|
work with Go 1, the first stable release of Go -- if not, it's a bug. |
|
|
|
The best way to learn Go is to create your own programs. Each |
|
chapter therefore includes exercises (and answers to exercises) to |
|
acquaint you with the language. Each exercise is either _easy_, |
|
_intermediate_, or _difficult_. The answers are included after the |
|
exercises on a new page. Some exercises don't have an answer; these |
|
are marked with an asterisk. |
|
|
|
Here's what you can expect from each chapter: |
|
|
|
Section 4 |
|
We'll look at the basic types, variables, and control |
|
structures available in the language. |
|
|
|
Section 5 |
|
Here we look at functions, the basic building blocks of |
|
Go programs. |
|
|
|
Section 6 |
|
We'll see that functions and data can be grouped together |
|
in packages. We'll also see how to document and test our |
|
packages. |
|
|
|
Section 7 |
|
We'll create our own types. We'll also look at memory |
|
allocations in Go. |
|
|
|
Section 8 |
|
We'll learn how to use interfaces. Interfaces are the |
|
central concept in Go, as Go does not support object |
|
orientation in the traditional sense. |
|
|
|
Section 9 |
|
We'll learn the "go" keyword, which can be used to start |
|
function in separate routines (called goroutines). |
|
Communication with those goroutines is done via channels. |
|
|
|
Section 10 |
|
Finally we'll see how to interface with the rest of the |
|
world from within a Go program. We'll see how to create |
|
files and read and write to and from them. We'll also |
|
briefly look into networking. |
|
|
|
3.2. Official Documentation |
|
|
|
There is a substantial amount of documentation written about Go. The |
|
Go Tutorial [go_tutorial], the Go Tour (with lots of exercises) and |
|
the Effective Go [effective_go] are helpful resources. The website |
|
http://golang.org/doc/ (http://golang.org/doc/) is a very good |
|
starting point for reading up on Go. Reading these documents is |
|
certainly not required, but it is recommended. |
|
|
|
<blockquote>: |
|
When searching on the internet use the term "golang" instead of plain |
|
"go". |
|
|
|
Go comes with its own documentation in the form of a program called |
|
"godoc". If you are interested in the documentation for the built- |
|
ins, simply do this: |
|
|
|
% godoc builtin |
|
|
|
To get the documentation of the "hash" package, just: |
|
|
|
% godoc hash |
|
|
|
To read the documentation of "fnv" contained in "hash", you'll need |
|
to issue "godoc hash/fnv" as "fnv" is a subdirectory of "hash". |
|
|
|
<CODE BEGINS> |
|
PACKAGE DOCUMENTATION |
|
|
|
package fnv |
|
import "hash/fnv" |
|
|
|
Package fnv implements FNV-1 and FNV-1a, non-cryptographic hash |
|
... |
|
<CODE ENDS> |
|
|
|
4. Basics |
|
|
|
<blockquote>: |
|
I am interested in this and hope to do something. |
|
|
|
In this chapter we will look at the basic building blocks of the Go |
|
programming language. |
|
|
|
4.1. Hello World |
|
|
|
In the Go tutorial, you get started with Go in the typical manner: |
|
printing "Hello World" (Ken Thompson and Dennis Ritchie started this |
|
when they presented the C language in the 1970s). That's a great way |
|
to start, so here it is, "Hello World" in Go. |
|
|
|
<CODE BEGINS> |
|
package main <1> |
|
|
|
import "fmt" <2> // Implements formatted I/O. |
|
|
|
/* Print something */ <3> |
|
func main() { <4> |
|
fmt.Printf("Hello, world.") <5> |
|
} |
|
<CODE ENDS> |
|
|
|
Lets look at the program line by line. This first line is just |
|
required _1_. All Go files start with "package <something>", and |
|
"package main" is required for a standalone executable. |
|
|
|
"import "fmt"" says we need "fmt" in addition to "main" _2_. A |
|
package other than "main" is commonly called a library, a familiar |
|
concept in many programming languages (see Section 6). The line ends |
|
with a comment that begins with "//". |
|
|
|
Next we another comment, but this one is enclosed in "/*" "*/" _3_. |
|
When your Go program is executed, the first function called will be |
|
"main.main()", which mimics the behavior from C. Here we declare |
|
that function _4_. |
|
|
|
Finally we call a function from the package "fmt" to print a string |
|
to the screen. The string is enclosed with """ and may contain non- |
|
ASCII characters _5_. |
|
|
|
4.2. Compiling and Running Code |
|
|
|
To build a Go program, use the "go" tool."helloworld" we just enter: |
|
|
|
% go build helloworld.go |
|
|
|
"helloworld". |
|
|
|
% ./helloworld |
|
Hello, world. |
|
|
|
You can combine the above and just call "go run helloworld.go". |
|
|
|
4.3. Variables, Types and Keywords |
|
|
|
In the next few sections we will look at the variables, basic types, |
|
keywords, and control structures of our new language. |
|
|
|
Go is different from (most) other languages in that the type of a |
|
variable is specified _after_ the variable name. So not: "int a", |
|
but "a int". When you declare a variable it is assigned the |
|
"natural" null value for the type. This means that after "var a |
|
int", "a" has a value of 0. With "var s string", "s" is assigned the |
|
zero string, which is """". Declaring and assigning in Go is a two |
|
step process, but they may be combined. Compare the following pieces |
|
of code which have the same effect. |
|
|
|
<CODE BEGINS> |
|
var a int a := 15 |
|
var b bool b := false |
|
a = 15 |
|
b = false |
|
<CODE ENDS> |
|
|
|
On the left we use the "var" keyword to declare a variable and _then_ |
|
assign a value to it. The code on the right uses ":=" to do this in |
|
one step (this form may only be used _inside_ functions). In that |
|
case the variable type is _deduced_ from the value. A value of 15 |
|
indicates an "int". A value of "false" tells Go that the type should |
|
be "bool". Multiple "var" declarations may also be grouped; "const" |
|
(see Section 4.3.3) and "import" also allow this. Note the use of |
|
parentheses instead of braces: |
|
|
|
<CODE BEGINS> |
|
var ( |
|
x int |
|
b bool |
|
) |
|
<CODE ENDS> |
|
|
|
Multiple variables of the same type can also be declared on a single |
|
line: "var x, y int" makes "x" and "y" both "int" variables. You can |
|
also make use of _parallel assignment_"a, b := 20, 16". This makes |
|
"a" and "b" both integer variables and assigns 20 to "a" and 16 to |
|
"b". |
|
|
|
A special name for a variable is "_". "/dev/null" on Unix). In this |
|
example we only assign the integer value of 35 to "b" and discard the |
|
value 34: "_, b := 34, 35". Declared but otherwise _unused_ |
|
variables are a compiler error in Go. |
|
|
|
4.3.1. Boolean Types |
|
|
|
A boolean type represents the set of boolean truth values denoted by |
|
the predeclared constants _true_ and _false_. The boolean type is |
|
"bool". |
|
|
|
4.3.2. Numerical Types |
|
|
|
Go has most of the well-known types such as "int". The "int" type |
|
has the appropriate length for your machine, meaning that on a 32-bit |
|
machine it is 32 bits and on a 64-bit machine it is 64 bits. Note: |
|
an "int" is either 32 or 64 bits, no other values are defined. Same |
|
goes for "uint", the unsigned int. |
|
|
|
If you want to be explicit about the length, you can have that too, |
|
with "int32", or "uint32". The full list for (signed and unsigned) |
|
integers is "int8", "int16", "int32", "int64" and "byte", "uint8", |
|
"uint16", "uint32", "uint64", with "byte" being an alias for "uint8". |
|
For floating point values there is "float32" and "float64" (there is |
|
no "float" type). A 64 bit integer or floating point value is |
|
_always_ 64 bit, also on 32 bit architectures. |
|
|
|
Note that these types are all distinct and assigning variables which |
|
mix these types is a compiler error, like in the following code: |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
func main() { |
|
var a int |
|
var b int32 |
|
b = a + a |
|
b = b + 5 |
|
} |
|
<CODE ENDS> |
|
|
|
We declare two different integers, a and b where a is an "int" and b |
|
is an "int32". We want to set b to the sum of a and a. This fails |
|
and gives the error: "cannot use a + a (type int) as type int32 in |
|
assignment". Adding the constant 5 to b _does_ succeed, because |
|
constants are not typed. |
|
|
|
4.3.3. Constants |
|
|
|
Constants in Go are just that --- constant. They are created at |
|
compile time, and can only be numbers, strings, or booleans; "const x |
|
= 42" makes "x" a constant. You can use _iota_ |
|
|
|
<CODE BEGINS> |
|
const ( |
|
a = iota |
|
b |
|
) |
|
<CODE ENDS> |
|
|
|
The first use of "iota" will yield 0, so "a" is equal to 0. Whenever |
|
"iota" is used again on a new line its value is incremented with 1, |
|
so "b" has a value of 1. Or, as shown here, you can even let Go |
|
repeat the use of "iota". You may also explicitly type a constant: |
|
"const b string = "0"". Now "b" is a "string" type constant. |
|
|
|
4.3.4. Strings |
|
|
|
Another important built-in type is "string". Assigning a string is |
|
as simple as: |
|
|
|
<CODE BEGINS> |
|
s := "Hello World!" |
|
<CODE ENDS> |
|
|
|
Strings in Go are a sequence of UTF-8 characters enclosed in double |
|
quotes ("). If you use the single quote (') you mean one character |
|
(encoded in UTF-8) --- which is _not_ a "string" in Go. |
|
|
|
Once assigned to a variable, the string cannot be changed: strings in |
|
Go are immutable. If you are coming from C, note that the following |
|
is not legal in Go: |
|
|
|
<CODE BEGINS> |
|
var s string = "hello" |
|
s[0] = 'c' |
|
<CODE ENDS> |
|
|
|
To do this in Go you will need the following: |
|
|
|
<CODE BEGINS> |
|
s := "hello" |
|
c := []rune(s) <1> |
|
c[0] = 'c' <2> |
|
s2 := string(c) <3> |
|
fmt.Printf("%s\n", s2) <4> |
|
<CODE ENDS> |
|
|
|
Here we convert "s" to an array of runes _1_. We change the first |
|
element of this array _2_. Then we create a _new_ string "s2" with |
|
the alteration _3_. Finally, we print the string with "fmt.Printf" |
|
_4_. |
|
|
|
4.3.5. Runes |
|
|
|
"Rune" is an alias for "int32". It is an UTF-8 encoded code point. |
|
When is this type useful? _not_ in Go!). But to get the actual |
|
characters you should use the "rune" type. |
|
|
|
4.3.6. Complex Numbers |
|
|
|
Go has native support for complex numbers. To use them you need a |
|
variable of type "complex128" (64 bit real and imaginary parts) or |
|
"complex64" (32 bit real and imaginary parts). Complex numbers are |
|
written as "re + im""i", where "re" is the real part, "im" is the |
|
imaginary part and "i" is the literal '"i"' ("\sqrt{-1}"). |
|
|
|
4.3.7. Errors |
|
|
|
Any non-trivial program will have the need for error reporting sooner |
|
or later. Because of this Go has a builtin type specially for |
|
errors, called "error". "var e error" creates a variable "e" of type |
|
"error" with the value "nil". This error type is an interface -- |
|
we'll look more at interfaces in Section 8. For now you can just |
|
assume that "error" is a type just like all other types. |
|
|
|
4.4. Operators and Built-in Functions |
|
|
|
Go supports the normal set of numerical operators. See Table 1 for |
|
lists the current ones and their relative precedence. They all |
|
associate from left to right. |
|
|
|
+------------+--------------------+ |
|
| Precedence | Operator(s) | |
|
+============+====================+ |
|
| Highest | "* / % << >> & &^" | |
|
+------------+--------------------+ |
|
| | `+ - | |
|
+------------+--------------------+ |
|
| | "== != < <= > >=" | |
|
+------------+--------------------+ |
|
| | "<-" | |
|
+------------+--------------------+ |
|
| | "&&" | |
|
+------------+--------------------+ |
|
| Lowest | || | |
|
+------------+--------------------+ |
|
|
|
Table 1: Operator precedence. |
|
|
|
"+ - * /" and "%" all do what you would expect, "& | ^" and "&^" are |
|
bit operators for bitwise _and__or__xor_"&&" and "||" operators are |
|
logical _and_ _or_ "!" |
|
|
|
Although Go does not support operator overloading (or method |
|
overloading for that matter), some of the built-in operators _are_ |
|
overloaded. For instance, "+" can be used for integers, floats, |
|
complex numbers and strings (adding strings is concatenating them). |
|
|
|
4.5. Go Keywords |
|
|
|
Let's start looking at keywords, Table 2 lists all the keywords in |
|
Go. |
|
|
|
+------------+---------------+----------+-------------+----------+ |
|
+============+===============+==========+=============+==========+ |
|
| "break" | "default" | "func" | "interface" | "select" | |
|
+------------+---------------+----------+-------------+----------+ |
|
| "case" | "defer" | "go" | "map" | "struct" | |
|
+------------+---------------+----------+-------------+----------+ |
|
| "chan" | "else" | "goto" | "package" | "switch" | |
|
+------------+---------------+----------+-------------+----------+ |
|
| "const" | "fallthrough" | "if" | "range" | "type" | |
|
+------------+---------------+----------+-------------+----------+ |
|
| "continue" | "for" | "import" | "return" | "var" | |
|
+------------+---------------+----------+-------------+----------+ |
|
|
|
Table 2: Keywords in Go. |
|
|
|
We've seen some of these already. We used "var" and "const" in the |
|
Section 4.3 section, and we briefly looked at "package" and "import" |
|
in our "Hello World" program at the start of the chapter. Others |
|
need more attention and have their own chapter or section: |
|
|
|
* "func" is used to declare functions and methods. |
|
|
|
* "return" is used to return from functions. We'll look at both |
|
"func" and "return" in detail in Section 5. |
|
|
|
* "go" is used for concurrency. We'll look at this in |
|
Section 9.3.1. |
|
|
|
* "select" used to choose from different types of communication, |
|
We'll work with "select" in Section 9.3.1. |
|
|
|
* "interface" is covered in Section 8. |
|
|
|
* "struct" is used for abstract data types. We'll work with |
|
"struct" in Section 7. |
|
|
|
* "type" is also covered in Section 7. |
|
|
|
4.6. Control Structures |
|
|
|
There are only a few control structures in Go. To write loops we use |
|
the "for" keyword, and there is a "switch" and of course an "if". |
|
When working with channels "select" will be used (see Section 9.3.1). |
|
Parentheses are are not required around the condition, and the body |
|
must _always_ be brace-delimited. |
|
|
|
4.6.1. If-Else |
|
|
|
In Go an "if" |
|
|
|
<CODE BEGINS> |
|
if x > 0 { |
|
return y |
|
} else { |
|
return x |
|
} |
|
<CODE ENDS> |
|
|
|
"if" and "switch" accept an initialization statement, it's common to |
|
see one used to set up a (local) variable. |
|
|
|
<CODE BEGINS> |
|
if err := SomeFunction(); err == nil { |
|
// do something |
|
} else { |
|
return err |
|
} |
|
<CODE ENDS> |
|
|
|
It is idomatic in Go to omit the "else" when the "if" statement's |
|
body has a "break", "continue", "return" or, "goto", so the above |
|
code would be better written as: |
|
|
|
<CODE BEGINS> |
|
if err := SomeFunction(); err != nil { |
|
return err |
|
} |
|
// do something |
|
<CODE ENDS> |
|
|
|
The opening brace on the first line must be positioned on the same |
|
line as the "if" statement. There is no arguing about this, because |
|
this is what "gofmt" outputs. |
|
|
|
4.6.2. Goto |
|
|
|
Go has a "goto" "goto" you jump to a |
|
|
|
<CODE BEGINS> |
|
func myfunc() { |
|
i := 0 |
|
Here: |
|
fmt.Println(i) |
|
i++ |
|
goto Here |
|
} |
|
<CODE ENDS> |
|
|
|
The string "Here:" indicates a label. A label does not need to start |
|
with a capital letter and is case sensitive. |
|
|
|
4.6.3. For |
|
|
|
The Go "for" |
|
|
|
* "for init; condition; post { }" - a loop using the syntax borrowed |
|
from C; |
|
|
|
* "for condition { }" - a while loop, and; |
|
|
|
* "for { }" - an endless loop. |
|
|
|
Short declarations make it easy to declare the index variable right |
|
in the loop. |
|
|
|
<CODE BEGINS> |
|
sum := 0 |
|
for i := 0; i < 10; i++ { |
|
sum = sum + i |
|
} |
|
<CODE ENDS> |
|
|
|
Note that the variable "i" ceases to exist after the loop. |
|
|
|
4.6.4. Break and Continue |
|
|
|
With "break" "break" breaks the current loop. |
|
|
|
<CODE BEGINS> |
|
for i := 0; i < 10; i++ { |
|
if i > 5 { |
|
break <1> |
|
} |
|
fmt.Println(i) <2> |
|
} |
|
<CODE ENDS> |
|
|
|
Here we "break" the current loop _1_, and don't continue with the |
|
"fmt.Println(i)" statement _2_. So we only print 0 to 5. With loops |
|
within loop you can specify a label after "break" to identify _which_ |
|
loop to stop: |
|
|
|
<CODE BEGINS> |
|
J: for j := 0; j < 5; j++ { <1> |
|
for i := 0; i < 10; i++ { |
|
if i > 5 { |
|
break J <2> |
|
} |
|
fmt.Println(i) |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
Here we define a label "J" _1_, preceding the "for"-loop there. When |
|
we use "break J" _2_, we don't break the inner loop but the "J" loop. |
|
|
|
With "continue" "break", "continue" also accepts a label. |
|
|
|
4.6.5. Range |
|
|
|
The keyword "range" Section 9.3.1). "range" is an iterator that, when |
|
called, returns the next key-value pair from the "thing" it loops |
|
over. Depending on what that is, "range" returns different things. |
|
|
|
When looping over a slice or array, "range" returns the index in the |
|
slice as the key and value belonging to that index. Consider this |
|
code: |
|
|
|
<CODE BEGINS> |
|
list := []string{"a", "b", "c", "d", "e", "f"} |
|
for k, v := range list { |
|
// do something with k and v |
|
} |
|
<CODE ENDS> |
|
|
|
First we create a slice of strings. Then we use "range" to loop over |
|
them. With each iteration, "range" will return the index as an "int" |
|
and the key as a "string". It will start with 0 and "a", so "k" will |
|
be 0 through 5, and v will be "a" through "f". |
|
|
|
You can also use "range" on strings directly. Then it will break out |
|
the individual Unicode characters ^[In the UTF-8 world characters are |
|
sometimes called _runes_ "char" is "rune". and their start position, |
|
by parsing the UTF-8. The loop: |
|
|
|
<CODE BEGINS> |
|
for pos, char := range "Gő!" { |
|
fmt.Printf("character '%c' starts at byte position %d\n", char, pos) |
|
} |
|
<CODE ENDS> |
|
|
|
prints |
|
|
|
character 'G' starts at byte position 0 |
|
character 'ő' starts at byte position 1 |
|
character '!' starts at byte position 3 |
|
|
|
Note that "ő" took 2 bytes, so '!' starts at byte 3. |
|
|
|
4.6.6. Switch |
|
|
|
Go's "switch" "switch" has no expression it switches on "true". It's |
|
therefore possible -- and idiomatic -- to write an "if-else-if-else" |
|
chain as a "switch". |
|
|
|
<CODE BEGINS> |
|
// Convert hexadecimal character to an int value |
|
switch { <1> |
|
case '0' <= c && c <= '9': <2> |
|
return c - '0' <3> |
|
case 'a' <= c && c <= 'f': <4> |
|
return c - 'a' + 10 |
|
case 'A' <= c && c <= 'F': <5> |
|
return c - 'A' + 10 |
|
} |
|
return 0 |
|
<CODE ENDS> |
|
|
|
A "switch" without a condition is the same as "switch true" _1_. We |
|
list the different cases. Each "case" statement has a condition that |
|
is either true of false. Here _2_ we check if "c" is a number. If |
|
"c" is a number we return its value _3_. Check if "c" falls between |
|
"a" and "f" _4_. For an "a" we return 10, for "b" we return 11, etc. |
|
We also do the same _5_ thing for "A" to "F". |
|
|
|
There is no automatic fall through, you can use "fallthrough" |
|
|
|
<CODE BEGINS> |
|
switch i { |
|
case 0: fallthrough |
|
case 1: <1> |
|
f() |
|
default: |
|
g() <2> |
|
<CODE ENDS> |
|
|
|
"f()" can be called when "i == 0" _1_. With "default" "g()" is called |
|
when "i" is not 0 or 1 _2_. We could rewrite the above example as: |
|
|
|
<CODE BEGINS> |
|
switch i { |
|
case 0, 1: <1> |
|
f() |
|
default: |
|
g() |
|
<CODE ENDS> |
|
|
|
You can list cases on one line _1_, separated by commas. |
|
|
|
4.7. Built-in Functions |
|
|
|
A few functions are predefined, meaning you _don't_ have to include |
|
any package to get access to them. Table 3 lists them all. |
|
|
|
+----------+----------+-----------+-----------+ |
|
+==========+==========+===========+===========+ |
|
| "close" | "new" | "panic" | "complex" | |
|
+----------+----------+-----------+-----------+ |
|
| "delete" | "make" | "recover" | "real" | |
|
+----------+----------+-----------+-----------+ |
|
| "len" | "append" | "print" | "imag" | |
|
+----------+----------+-----------+-----------+ |
|
| "cap" | "copy" | "println" | | |
|
+----------+----------+-----------+-----------+ |
|
|
|
Table 3: Pre-defined functions in Go. |
|
|
|
These built-in functions are documented in the "builtin" |
|
|
|
"close" |
|
is used in channel communication. It closes a channel. We'll |
|
learn more about this in Section 9.3.1. |
|
|
|
"delete" |
|
is used for deleting entries in maps. |
|
|
|
"len" and "cap" |
|
are used on a number of different types, "len" is used to return |
|
the lengths of strings, slices, and arrays. In the next section |
|
Section 4.8.1 we'll look at slices, arrays and the function "cap". |
|
|
|
"new" |
|
is used for allocating memory for user defined data types. See |
|
Section 7.1.1. |
|
|
|
"make" |
|
is used for allocating memory for built-in types (maps, slices, |
|
and channels). See Section 7.1.2. |
|
|
|
"copy", "append" |
|
"copy" is for copying slices. "append" is for concatenating |
|
slices. See Section 4.8.2 in this chapter. |
|
|
|
"panic", "recover" |
|
are used for an _exception_ mechanism. See Section 5.6 for more. |
|
|
|
"print", "println" |
|
are low level printing functions that can be used without |
|
reverting to the "fmt" |
|
|
|
"complex", "real", "imag" |
|
all deal with complex numbers. |
|
|
|
4.8. Arrays, Slices, and Maps |
|
|
|
To store multiple values in a list, you can use arrays, or their more |
|
flexible cousin: slices. A dictionary or hash type is also |
|
available. It is called a "map" in Go. |
|
|
|
4.8.1. Arrays |
|
|
|
An array is defined by: "[n]<type>", where "n" is the length of the |
|
array and "<type>" is the stuff you want to store. To assign or |
|
index an element in the array, you use square brackets: |
|
|
|
<CODE BEGINS> |
|
var arr [10]int |
|
arr[0] = 42 |
|
arr[1] = 13 |
|
fmt.Printf("The first element is %d\n", arr[0]) |
|
<CODE ENDS> |
|
|
|
Array types like "var arr [10]int" have a fixed size. The size is |
|
_part_ of the type. They can't grow, because then they would have a |
|
different type. Also arrays are values: Assigning one array to |
|
another _copies_ all the elements. In particular, if you pass an |
|
array to a function it will receive a copy of the array, not a |
|
pointer to it. |
|
|
|
"var a [3]int". To initialize it to something other than zero, use a |
|
_composite literal_ "a := [3]int{1, 2, 3}". This can be shortened to |
|
"a := [...]int{1, 2, 3}", where Go counts the elements automatically. |
|
|
|
<aside>: |
|
A composite literal allows you to assign a value directly to an |
|
array, slice, or map. See Section 7.1.3 for more information. |
|
|
|
When declaring arrays you _always_ have to type something in between |
|
the square brackets, either a number or three dots ("..."), when |
|
using a composite literal. When using multidimensional arrays, you |
|
can use the following syntax: "a := [2][2]int{ {1,2}, {3,4} }". Now |
|
that you know about arrays you will be delighted to learn that you |
|
will almost never use them in Go, because there is something much |
|
more flexible: slices. |
|
|
|
4.8.2. Slices |
|
|
|
A slice is similar to an array, but it can grow when new elements are |
|
added. A slice always refers to an underlying array. What makes |
|
slices different from arrays is that a slice is a pointer _to_ an |
|
array; slices are reference types. |
|
|
|
<aside>: |
|
Reference types are created with "make". We detail this further in |
|
Section 7. |
|
|
|
That means that if you assign one slice to another, both refer to the |
|
_same_ underlying array. For instance, if a function takes a slice |
|
argument, changes it makes to the elements of the slice will be |
|
visible to the caller, analogous to passing a pointer to the |
|
underlying array. With: "slice := make([]int, 10)", you create a |
|
slice which can hold ten elements. Note that the underlying array |
|
isn't specified. A slice is always coupled to an array that has a |
|
fixed size. For slices we define a capacity Section 4.8.2 shows the |
|
creation of an array, then the creation of a slice. First we create |
|
an array of "m" elements of the type "int": "var array[m]int" . |
|
|
|
Next, we create a slice from this array: "slice := array[:n]" . And |
|
now we have: |
|
|
|
* "len(slice) == n" |
|
|
|
* "cap(slice) == m" |
|
|
|
* "len(array) == cap(array) == m" |
|
|
|
Array versus slice |
|
|
|
Given an array, or another slice, a new slice is created via |
|
"a[n:m]". This creates a new slice which refers to the variable "a", |
|
starts at index "n", and ends before index "m". It has length "n - |
|
m". |
|
|
|
<CODE BEGINS> |
|
a := [...]int{1, 2, 3, 4, 5} <1> |
|
s1 := a[2:4] <2> |
|
s2 := a[1:5] <3> |
|
s3 := a[:] <4> |
|
s4 := a[:4] <5> |
|
s5 := s2[:] <6> |
|
s6 := a[2:4:5] <7> |
|
<CODE ENDS> |
|
|
|
First we define _1_ an array with five elements, from index 0 to 4. |
|
From this we create _2_ a slice with the elements from index 2 to 3, |
|
this slices contains: "3, 4". Then we we create another slice _3_ |
|
from "a": with the elements from index 1 to 4, this contains: "2, 3, |
|
4, 5". With "a[:]" _4_ we create a slice with all the elements in |
|
the array. This is a shorthand for: "a[0:len(a)]". And with "a[:4]" |
|
_5_ we create a slice with the elements from index 0 to 3, this is |
|
short for: "a[0:4]", and gives us a slices that contains: "1, 2, 3, |
|
4". With "s2[:]" we create a slice from the slice "s2" _6_, note |
|
that "s5" still refers to the array "a". Finally, we create a slice |
|
with the elements from index 3 to 3 _and_ also set the cap to 4 _7_. |
|
|
|
When working with slices you can overrun the bounds, consider this |
|
code. |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
func main() { |
|
var array [100]int <1> |
|
slice := array[0:99] <2> |
|
|
|
slice[98] = 1 <3> |
|
slice[99] = 2 <4> |
|
} |
|
<CODE ENDS> |
|
|
|
At _1_ we create an array with a 100 elements, indexed from 0 to 99. |
|
Then at _2_ we create a slice that has index 0 to 98. We assign 1 to |
|
the 99th element _3_ of the slice. This works as expected. But at |
|
_4_ we dare to do the impossible, and and try to allocate something |
|
beyond the length of the slice and we are greeted with a _runtime_ |
|
error: "Error: "throw: index out of range"." |
|
|
|
If you want to extend a slice, there are a couple of built-in |
|
functions that make life easier: "append" and "copy". The append |
|
function appends zero or more values to a slice and returns the |
|
result: a slice with the same type as the original. If the original |
|
slice isn't big enough to fit the added values, append will allocate |
|
a new slice that is big enough. So the slice returned by append may |
|
refer to a different underlying array than the original slice does. |
|
Here's an example: |
|
|
|
<CODE BEGINS> |
|
s0 := []int{0, 0} |
|
s1 := append(s0, 2) <1> |
|
s2 := append(s1, 3, 5, 7) <2> |
|
s3 := append(s2, s0...) <3> |
|
<CODE ENDS> |
|
|
|
At _1_ we append a single element, making "s1" equal to "[]int{0, 0, |
|
2}". At _2_ we append multiple elements, making "s2" equal to |
|
"[]int{0, 0, 2, 3, 5, 7}". And at _3_ we append a slice, giving us |
|
"s3" equal to "[]int{0, 0, 2, 3, 5, 7, 0, 0}". Note the three dots |
|
used after "s0..."! This is needed make it clear explicit that |
|
you're appending another slice, instead of a single value. |
|
|
|
The copy function copies slice elements from a source to a |
|
destination, and returns the number of elements it copied. This |
|
number is the minimum of the length of the source and the length of |
|
the destination. For example: |
|
|
|
<CODE BEGINS> |
|
var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7} |
|
var s = make([]int, 6) |
|
n1 := copy(s, a[0:]) <1> |
|
n2 := copy(s, s[2:]) <2> |
|
<CODE ENDS> |
|
|
|
After _1_, "n1" is 6, and "s" is "[]int{0, 1, 2, 3, 4, 5}". And |
|
after _2_, "n2" is 4, and "s" is "[]int{2, 3, 4, 5, 4, 5}". |
|
|
|
4.8.3. Maps |
|
|
|
Many other languages have a type similar to maps built-in. For |
|
instance, Perl has hashes, Python has its dictionaries, and C++ also |
|
has maps (as part of the libraries). In Go we have the "map" "map" |
|
can be thought of as an array indexed by strings (in its most simple |
|
form). |
|
|
|
<CODE BEGINS> |
|
monthdays := map[string]int{ |
|
"Jan": 31, "Feb": 28, "Mar": 31, |
|
"Apr": 30, "May": 31, "Jun": 30, |
|
"Jul": 31, "Aug": 31, "Sep": 30, |
|
"Oct": 31, "Nov": 30, "Dec": 31, <1> |
|
} |
|
<CODE ENDS> |
|
|
|
The general syntax for defining a map is "map[<from type>]<to type>". |
|
Here, we define a map that converts from a "string" (month |
|
abbreviation) to an "int" (number of days in that month). Note that |
|
the trailing comma at _1_ is _required_. |
|
|
|
Use "make" when only declaring a map: "monthdays := |
|
make(map[string]int)". A map is a reference type. |
|
|
|
For indexing ("searching") the map, we use square brackets. For |
|
example, suppose we want to print the number of days in December: |
|
"fmt.Printf("%d\n", monthdays["Dec"])" |
|
|
|
If you are looping over an array, slice, string, or map a, "range" |
|
|
|
<CODE BEGINS> |
|
year := 0 |
|
for _, days := range monthdays <1> |
|
year += days |
|
} |
|
fmt.Printf("Numbers of days in a year: %d\n", year) |
|
<CODE ENDS> |
|
|
|
At _1_ we use the underscore to ignore (assign to nothing) the key |
|
returned by "range". We are only interested in the values from |
|
"monthdays". |
|
|
|
|
|
|
|
To add elements to the map, you would add new month with: |
|
"monthdays["Undecim"] = 30". If you use a key that already exists, |
|
the value will be silently overwritten: "monthdays["Feb"] = 29". To |
|
test for existence "value, present := monthdays["Jan"]". If the key |
|
"Jan" exists, "present" will be true. It's more Go like to name |
|
"present" "ok", and use: "v, ok := monthdays["Jan"]". In Go we call |
|
this the "comma ok" form. |
|
|
|
You can remove elements "map": "delete(monthdays, "Mar")" . In |
|
general the syntax "delete(m, x)" will delete the map entry retrieved |
|
by the expression "m[x]". |
|
|
|
4.9. Exercises |
|
|
|
4.9.1. For-loop |
|
|
|
1. Create a loop with the "for" construct. Make it loop 10 times |
|
and print out the loop counter with the "fmt" package. |
|
|
|
2. Rewrite the loop from 1 to use "goto". The keyword "for" may not |
|
be used. |
|
|
|
3. Rewrite the loop again so that it fills an array and then prints |
|
that array to the screen. |
|
|
|
4.9.2. Answer |
|
|
|
1. There are many possibilities. One solution could be: |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
import "fmt" |
|
|
|
func main() { |
|
for i := 0; i < 10; i++ { |
|
fmt.Println("%d", i) |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
Let's compile this and look at the output. |
|
|
|
% go build for.go |
|
% ./for |
|
0 |
|
1 |
|
. |
|
. |
|
. |
|
9 |
|
|
|
2. Rewriting the loop results in code that should look something |
|
like this (only showing the "main"-function): |
|
|
|
<CODE BEGINS> |
|
func main() { |
|
i := 0 <1> |
|
Loop: <2> |
|
if i < 10 { |
|
fmt.Printf("%d\n", i) |
|
i++ |
|
goto Loop <3> |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
At _1_ we define our loop variable. And at _2_ we define a label |
|
and at _3_ we jump to this label. |
|
|
|
3. The following is one possible solution: |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
import "fmt" |
|
|
|
func main() { |
|
var arr [10]int <1> |
|
for i := 0; i < 10; i++ { |
|
arr[i] = i <2> |
|
} |
|
fmt.Printf("%v", arr) <3> |
|
} |
|
<CODE ENDS> |
|
|
|
Here _1_ we create an array with 10 elements. Which we then fill |
|
_2_ one by one. And finally we print it _3_ with "%v" which lets |
|
Go to print the value for us. You could even do this in one fell |
|
swoop by using a composite literal: |
|
|
|
<CODE BEGINS> |
|
fmt.Printf("%v\n", [...]int{0,1,2,3,4,5,6,7,8,9}) |
|
<CODE ENDS> |
|
|
|
4.9.3. Average |
|
|
|
1. Write code to calculate the average of a "float64" slice. In a |
|
later exercise you will make it into a function. |
|
|
|
4.9.4. Answer |
|
|
|
1. The following code calculates the average. |
|
|
|
<CODE BEGINS> |
|
sum := 0.0 |
|
switch len(xs) { |
|
case 0: <1> |
|
avg = 0 |
|
default: <2> |
|
for _, v := range xs { |
|
sum += v |
|
} |
|
avg = sum / float64(len(xs)) <3> |
|
} |
|
<CODE ENDS> |
|
|
|
Here at _1_ we check if the length is zero and if so, we return 0. |
|
Otherwise we calculate the average at _2_. We have to convert the |
|
value return from "len" to a "float64" to make the division work at |
|
_3_. |
|
|
|
4.9.5. FizzBuzz |
|
|
|
1. Solve this problem, called the Fizz-Buzz [cite_fizzbuzz] problem: |
|
|
|
Write a program that prints the numbers from 1 to 100. But for |
|
multiples of three print, "Fizz" instead of the number, and for |
|
multiples of five, print "Buzz". For numbers which are multiples of |
|
both three and five, print "FizzBuzz". |
|
|
|
4.9.6. Answer |
|
|
|
1. A possible solution to this problem is the following program. |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
import "fmt" |
|
|
|
func main() { |
|
const ( |
|
FIZZ = 3 <1> |
|
BUZZ = 5 |
|
) |
|
var p bool <2> |
|
for i := 1; i < 100; i++ { <3> |
|
p = false |
|
if i%FIZZ == 0 { <4> |
|
fmt.Printf("Fizz") |
|
p = true |
|
} |
|
if i%BUZZ == 0 { <5> |
|
fmt.Printf("Buzz") |
|
p = true |
|
} |
|
if !p { <6> |
|
fmt.Printf("%v", i) |
|
} |
|
fmt.Println() |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
Here _1_ we define two constants to make our code more readable, see |
|
Section 4.3.3. At _2_ we define a boolean that keeps track if we |
|
already printed something. At _3_ we start our for-loop, see |
|
Section 4.6.3. If the value is divisible by FIZZ - that is, 3 - , we |
|
print "Fizz" _4_. And at _5_ we check if the value is divisble by |
|
BUZZ -- that is, 5 -- if so print "Buzz". Note that we have also |
|
taken care of the FizzBuzz case. At _6_, if printed neither Fizz nor |
|
Buzz printed, we print the value. |
|
|
|
5. Functions |
|
|
|
<blockquote>: |
|
I'm always delighted by the light touch and stillness of early |
|
programming languages. Not much text; a lot gets done. Old programs |
|
read like quiet conversations between a well-spoken research worker |
|
and a well- studied mechanical colleague, not as a debate with a |
|
compiler. Who'd have guessed sophistication bought such noise? |
|
|
|
Functions are the basic building blocks of Go programs; all |
|
interesting stuff happens in them. |
|
|
|
Here is an example of how you can declare a function: |
|
|
|
<CODE BEGINS> |
|
type mytype int |
|
func (p mytype) funcname(q int) (r,s int) { return 0,0 } |
|
<1> <2> <3> <4> <5> <6> |
|
<CODE ENDS> |
|
|
|
To declare a function, you use the "func" keyword _1_. You can |
|
optionally bind _2_ to a specific type called receiver Section 8. |
|
Next _3_ you write the name of your function. Here _4_ we define |
|
that the variable "q" of type "int" is the input parameter. |
|
Parameters are passed _pass-by-value_."r" and "s" _5_ are the _named |
|
return parameters_ (((functions, named return parameters))) for this |
|
function. Functions in Go can have multiple return values. This is |
|
very useful to return a value _and_ error. This removes the need for |
|
in-band error returns (such as -1 for "EOF") and modifying an |
|
argument. If you want the return parameters not to be named you only |
|
give the types: "(int, int)". If you have only one value to return |
|
you may omit the parentheses. If your function is a subroutine and |
|
does not have anything to return you may omit this entirely. |
|
Finally, we have the body _6_ of the function. Note that "return" is |
|
a statement so the braces around the parameter(s) are optional. |
|
|
|
As said the return or result parameters of a Go function can be given |
|
names and used as regular variables, just like the incoming |
|
parameters. When named, they are initialized to the zero values for |
|
their types when the function begins. If the function executes a |
|
"return" statement with no arguments, the current values of the |
|
result parameters are returned. Using these features enables you |
|
(again) to do more with less code. |
|
|
|
The names are not mandatory but they can make code shorter and |
|
clearer: _they are documentation_. However don't overuse this |
|
feature, especially in longer functions where it might not be |
|
immediately apparent what is returned. |
|
|
|
Functions can be declared in any order you wish. The compiler scans |
|
the entire file before execution, so function prototyping is a thing |
|
of the past in Go. Go does not allow nested functions, but you can |
|
work around this with anonymous functions. See the |
|
Section Section 5.2 in this chapter. Recursive functions work just |
|
as in other languages: |
|
|
|
<CODE BEGINS> |
|
func rec(i int) { |
|
if i == 10 { <1> |
|
return |
|
} |
|
rec(i+1) <2> |
|
fmt.Printf("%d ", i) |
|
} |
|
<CODE ENDS> |
|
|
|
Here _2_ we call the same function again, "rec" returns when "i" has |
|
the value 10, this is checked on the second line _1_. This function |
|
prints: "9 8 7 6 5 4 3 2 1 0", when called as "rec(0)". |
|
|
|
5.1. Scope |
|
|
|
Variables declared outside any functions are _global_ _local_ |
|
|
|
In the following example we call "g()" from "f()": |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
var a int <1> |
|
|
|
func main() { |
|
a = 5 |
|
print(a) |
|
f() |
|
} |
|
|
|
func f() { |
|
a := 6 <2> |
|
print(a) |
|
g() |
|
} |
|
|
|
func g() { |
|
print(a) |
|
} |
|
<CODE ENDS> |
|
|
|
Here _1_, we declare "a" to be a global variable of type "int". Then |
|
in the "main" function we give the _global_ "a" the value of 5, after |
|
printing it we call the function "f". Then here _2_, "a := 6", we |
|
create a _new, local_ variable also called "a". This new "a" gets |
|
the value of 6, which we then print. Then we call "g", which uses |
|
the _global_ "a" again and prints "a"'s value set in "main". Thus |
|
the output will be: "565". A _local_ variable is _only_ valid when |
|
we are executing the function in which it is defined. Note that the |
|
":=" used in line 12 is sometimes hard to spot so it is generally |
|
advised _not_ to use the same name for global and local variables. |
|
|
|
5.2. Functions as values |
|
|
|
_just_ values. They can be assigned to variables as follows: |
|
|
|
<CODE BEGINS> |
|
import "fmt" |
|
|
|
func main() { |
|
a := func() { <1> |
|
fmt.Println("Hello") |
|
} <2> |
|
a() <3> |
|
} |
|
<CODE ENDS> |
|
|
|
"a" is defined as an anonymous (nameless) function _1_. Note the lack |
|
of parentheses "()" after "a". If there were, that would be to |
|
_call_ some function with the name "a" before we have defined what |
|
"a" is. Once "a" is defined, then we can _call_ it, _3_. |
|
|
|
Functions--as--values may be used in other places, for example maps. |
|
Here we convert from integers to functions: |
|
|
|
<CODE BEGINS> |
|
var xs = map[int]func() int{ |
|
1: func() int { return 10 }, |
|
2: func() int { return 20 }, |
|
3: func() int { return 30 }, |
|
} |
|
<CODE ENDS> |
|
|
|
Note that the final comma on second to last line is _mandatory_. |
|
|
|
Or you can write a function that takes a function as its parameter, |
|
for example a "Map" function that works on "int" slices. This is |
|
left as an exercise for the reader; see the exercise Section 5.7.15. |
|
|
|
5.3. Callbacks |
|
|
|
Because functions are values they are easy to pass to functions, from |
|
where they can be used as callbacks. First define a function that |
|
does "something" with an integer value: |
|
|
|
<CODE BEGINS> |
|
func printit(x int) { |
|
fmt.Printf("%v\n", x) |
|
} |
|
<CODE ENDS> |
|
|
|
This function does not return a value and just prints its argument. |
|
The _signature_ "func printit(int)", or without the function name: |
|
"func(int)". To create a new function that uses this one as a |
|
callback we need to use this signature: |
|
|
|
<CODE BEGINS> |
|
func callback(y int, f func(int)) { |
|
f(y) |
|
} |
|
<CODE ENDS> |
|
|
|
Here we create a new function that takes two parameters: "y int", |
|
i.e. just an "int" and "f func(int)", i.e. a function that takes an |
|
int and returns nothing. The parameter "f" is the variable holding |
|
that function. It can be used as any other function, and we execute |
|
the function on line 2 with the parameter "y": "f(y)" |
|
|
|
5.4. Deferred Code |
|
|
|
Suppose you have a function in which you open a file and perform |
|
various writes and reads on it. In such a function there are often |
|
spots where you want to return early. If you do that, you will need |
|
to close the file descriptor you are working on. This often leads to |
|
the following code: |
|
|
|
<CODE BEGINS> |
|
func ReadWrite() bool { |
|
file.Open("file") |
|
// Do your thing |
|
if failureX { |
|
file.Close() <1> |
|
return false |
|
} |
|
|
|
if failureY { |
|
file.Close() <1> |
|
return false |
|
} |
|
file.Close() <1> |
|
return true <2> |
|
} |
|
<CODE ENDS> |
|
|
|
Note that we repeat a lot of code here; you can see the that |
|
"file.Close()" is called at _1_. To overcome this, Go has the "defer" |
|
"defer" you specify a function which is called just _before_ _2_ the |
|
current function exits. |
|
|
|
With "defer" we can rewrite the above code as follows. It makes the |
|
function more readable and it puts the "Close" _right next_ to the |
|
"Open". |
|
|
|
<CODE BEGINS> |
|
func ReadWrite() bool { |
|
file.Open("filename") |
|
defer file.Close() <1> |
|
// Do your thing |
|
if failureX { |
|
return false <2> |
|
} |
|
if failureY { |
|
return false <2> |
|
} |
|
return true <2> |
|
} |
|
<CODE ENDS> |
|
|
|
At _1_ "file.Close()" is added to the defer list. "Close" is now |
|
done automatically at _2_. This makes the function shorter and more |
|
readable. It puts the "Close" right next to the "Open". |
|
|
|
You can put multiple functions on the "defer list", like this example |
|
from |
|
|
|
<CODE BEGINS> |
|
for i := 0; i < 5; i++ { |
|
defer fmt.Printf("%d ", i) |
|
} |
|
<CODE ENDS> |
|
|
|
Deferred functions are executed in LIFO order, so the above code |
|
prints: "4 3 2 1 0". |
|
|
|
With "defer" you can even change return values, provided that you are |
|
using named result parameters and a function literal |
|
|
|
<CODE BEGINS> |
|
defer func() {/* ... */}() |
|
<CODE ENDS> |
|
|
|
Here we use a function without a name and specify the body of the |
|
function inline, basically we're creating a nameless function on the |
|
spot. The final braces are needed because "defer" needs a function |
|
call, not a function value. If our anonymous function would take an |
|
parameter it would be easier to see why we need the braces: |
|
|
|
<CODE BEGINS> |
|
defer func(x int) {/* ... */}(5) |
|
<CODE ENDS> |
|
|
|
In this (unnamed) function you can access any named return parameter: |
|
|
|
<CODE BEGINS> |
|
func f() (ret int) |
|
defer func() { <1> |
|
ret++ |
|
}() |
|
return 0 |
|
} |
|
<CODE ENDS> |
|
|
|
Here _1_ we specify our function, the named return value "ret" is |
|
initialized with zero. The nameless function in the defer increments |
|
the value of "ret" with 1. The "return 0" on line 5 _will not be the |
|
returned value_, because of "defer". The function "f" will return 1! |
|
|
|
5.5. Variadic Parameter |
|
|
|
Functions that take a variable number of parameters are known as |
|
variadic functions. |
|
|
|
<CODE BEGINS> |
|
func myfunc(arg ...int) {} |
|
<CODE ENDS> |
|
|
|
The "arg ...int" instructs Go to see this as a function that takes a |
|
variable number of arguments. Note that these arguments all have to |
|
have the type "int". In the body of your function the variable "arg" |
|
is a slice of ints: |
|
|
|
<CODE BEGINS> |
|
for _, n := range arg { |
|
fmt.Printf("And the number is: %d\n", n) |
|
} |
|
<CODE ENDS> |
|
|
|
We range over the arguments on the first line. We are not interested |
|
in the index as returned by "range", hence the use of the underscore |
|
there. In the body of the "range" we just print the parameters we |
|
were given. |
|
|
|
If you don't specify the type of the variadic argument it defaults to |
|
the empty interface "interface{}" (see Chapter Section 8). |
|
|
|
Suppose we have another variadic function called "myfunc2", the |
|
following example shows how to pass variadic arguments to it: |
|
|
|
<CODE BEGINS> |
|
func myfunc(arg ...int) { |
|
myfunc2(arg...) |
|
myfunc2(arg[:2]...) |
|
} |
|
<CODE ENDS> |
|
|
|
With "myfunc2(arg...)" we pass all the parameters to "myfunc2", but |
|
because the variadic parameters is just a slice, we can use some |
|
slice tricks as well. |
|
|
|
5.6. Panic and recovering |
|
|
|
Go does not have an exception mechanism: you cannot throw exceptions. |
|
Instead it uses a panic-and-recover mechanism. It is worth |
|
remembering that you should use this as a last resort, your code will |
|
not look, or be, better if it is littered with panics. It's a |
|
powerful tool: use it wisely. So, how do you use it? In the words |
|
of the Go Authors [go_blog_panic]: |
|
|
|
Panic |
|
is a built-in function that stops the ordinary flow of |
|
control and begins panicking. When the function "F" calls |
|
"panic", execution of "F" stops, any deferred functions in |
|
"F" are executed normally, and then "F" returns to its |
|
caller. To the caller, "F" then behaves like a call to |
|
"panic". The process continues up the stack until all |
|
functions in the current goroutine have returned, at which |
|
point the program crashes. Panics can be initiated by |
|
invoking "panic" directly. They can also be caused by |
|
_runtime errors_, such as out-of-bounds array accesses. |
|
|
|
Recover |
|
is a built-in function that regains control of a panicking |
|
goroutine. Recover is _only_ useful inside _deferred_ |
|
functions. During normal execution, a call to "recover" |
|
will return "nil" and have no other effect. If the current |
|
goroutine is panicking, a call to "recover" will capture the |
|
value given to "panic" and resume normal execution. |
|
|
|
This function checks if the function it gets as argument will panic |
|
when it is executed: |
|
|
|
<CODE BEGINS> |
|
func Panic(f func()) (b bool) { <1> |
|
defer func() { <2> |
|
if x := recover(); x != nil { |
|
b = true |
|
} |
|
}() |
|
f() <3> |
|
return <4> |
|
} |
|
<CODE ENDS> |
|
|
|
We define a new function "Panic" _1_ that takes a function as an |
|
argument (see Section 5.2). It returns true if "f" panics when run, |
|
else false. We then _2_ define a "defer" function that utilizes |
|
"recover". If the current goroutine panics, this defer function will |
|
notice that. If "recover()" returns non-"nil" we set "b" to true. |
|
At _3_ Execute the function we received as the argument. And finally |
|
_4_ we return the value of "b". Because "b" is a named return |
|
parameter. |
|
|
|
The following code fragment, shows how we can use this function: |
|
|
|
<CODE BEGINS> |
|
func panicy() { |
|
var a []int |
|
a[3] = 5 |
|
} |
|
|
|
func main() { |
|
fmt.Println(Panic(panicy)) |
|
} |
|
<CODE ENDS> |
|
|
|
On line 3 the "a[3] = 5" triggers a _runtime_ out of bounds error |
|
which results in a panic. Thus this program will print "true". If |
|
we change line 2: "var a []int" to "var a [4]int" the function |
|
"panicy" does not panic anymore. Why? |
|
|
|
5.7. Exercises |
|
|
|
5.7.1. Average |
|
|
|
1. Write a function that calculates the average of a "float64" |
|
slice. |
|
|
|
5.7.2. Answer |
|
|
|
1. The following function calculates the average: |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
func average(xs []float64) (avg float64) { //<1> |
|
sum := 0.0 |
|
switch len(xs) { |
|
case 0: //<2> |
|
avg = 0 |
|
default: //<3> |
|
for _, v := range xs { |
|
sum += v |
|
} |
|
avg = sum / float64(len(xs)) //<4> |
|
} |
|
return //<5> |
|
} |
|
<CODE ENDS> |
|
|
|
At _1_ we use a named return parameter. If the length of "xs" is |
|
zero _2_, we return 0. Otherwise _3_, we calculate the average. At |
|
_4_ we convert the value to a "float64" to make the division work as |
|
"len" returns an "int". Finally, at _5_ we reutrn our avarage. |
|
|
|
5.7.3. Bubble sort |
|
|
|
1. Write a function that performs a bubble sort on a slice of ints. |
|
From [bubblesort]: |
|
|
|
<blockquote>: |
|
It works by repeatedly stepping through the list to be sorted, |
|
comparing each pair of adjacent items and swapping them if they are |
|
in the wrong order. The pass through the list is repeated until no |
|
swaps are needed, which indicates that the list is sorted. The |
|
algorithm gets its name from the way smaller elements "bubble" to the |
|
top of the list. |
|
|
|
It also gives an example in pseudo code: |
|
|
|
procedure bubbleSort( A : list of sortable items ) |
|
do |
|
swapped = false |
|
for each i in 1 to length(A) - 1 inclusive do: |
|
if A[i-1] > A[i] then |
|
swap( A[i-1], A[i] ) |
|
swapped = true |
|
end if |
|
end for |
|
while swapped |
|
end procedure |
|
|
|
5.7.4. Answer |
|
|
|
1. Bubble sort isn't terribly efficient. For "n" elements it scales |
|
"O(n^2)". But bubble sort is easy to implement: |
|
|
|
<CODE BEGINS> |
|
func main() { |
|
n := []int{5, -1, 0, 12, 3, 5} |
|
fmt.Printf("unsorted %v\n", n) |
|
bubblesort(n) |
|
fmt.Printf("sorted %v\n", n) |
|
} |
|
|
|
func bubblesort(n []int) { |
|
for i := 0; i < len(n)-1; i++ { |
|
for j := i + 1; j < len(n); j++ { |
|
if n[j] < n[i] { |
|
n[i], n[j] = n[j], n[i] |
|
} |
|
<CODE ENDS> |
|
|
|
Because a slice is a reference type, the "bubblesort" function |
|
works and does not need to return a sorted slice. |
|
|
|
5.7.5. For-loop II |
|
|
|
1. Take what you did in exercise to write the for loop and extend it |
|
a bit. Put the body of the for loop - the "fmt.Printf" - in a |
|
separate function. |
|
|
|
5.7.6. Answer |
|
|
|
1. <{{src/for-func.go}} |
|
|
|
5.7.7. Fibonacci |
|
|
|
1. The Fibonacci sequence starts as follows: "1, 1, 2, 3, 5, 8, 13, |
|
\ldots" Or in mathematical terms: "x_1 = 1; x_2 = 1; x_n = |
|
x_{n-1} + x_{n-2}\quad\forall n > 2". |
|
|
|
Write a function that takes an "int" value and gives that many |
|
terms of the Fibonacci sequence. |
|
|
|
5.7.8. Answer |
|
|
|
1. The following program calculates Fibonacci numbers: |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
import "fmt" |
|
|
|
func fibonacci(value int) []int { |
|
x := make([]int, value) <1> |
|
x[0], x[1] = 1, 1 <2> |
|
for n := 2; n < value; n++ { |
|
x[n] = x[n-1] + x[n-2] <3> |
|
} |
|
return x <4> |
|
} |
|
|
|
func main() { |
|
for _, term := range fibonacci(10) { <5> |
|
fmt.Printf("%v ", term) |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
At _1_ we create an array to hold the integers up to the value given |
|
in the function call. At _2_ we start the Fibonacci calculation. |
|
Then _3_: "x_n = x_{n-1} + x_{n-2}". At _4_ we return the _entire_ |
|
array. And at _5_ we use the "range" keyword to "walk" the numbers |
|
returned by the Fibonacci function. Here up to 10. Finally, we |
|
print the numbers. |
|
|
|
5.7.9. Var args |
|
|
|
1. Write a function that takes a variable number of ints and print |
|
each integer on a separate line. |
|
|
|
5.7.10. Answer |
|
|
|
1. For this we need the "{...}"-syntax to signal we define a |
|
function that takes an arbitrary number of arguments. |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
import "fmt" |
|
|
|
func main() { |
|
printthem(1, 4, 5, 7, 4) |
|
printthem(1, 2, 4) |
|
} |
|
|
|
func printthem(numbers ...int) { |
|
for _, d := range numbers { |
|
fmt.Printf("%d\n", d) |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
5.7.11. Functions that return functions |
|
|
|
1. Write a function that returns a function that performs a "+2" on |
|
integers. Name the function "plusTwo". You should then be able |
|
do the following: |
|
|
|
<CODE BEGINS> |
|
p := plusTwo() |
|
fmt.Printf("%v\n", p(2)) |
|
<CODE ENDS> |
|
|
|
Which should print 4. See Section 5.3. |
|
|
|
2. Generalize the function from above and create a "plusX(x)" which |
|
returns functions that add "x" to an integer. |
|
|
|
5.7.12. Answer |
|
|
|
1. Define a new function that returns a function: "return func(x |
|
int) int { return x + 2 }" Function literals at work, we define |
|
the +2--function right there in the return statement. |
|
|
|
<CODE BEGINS> |
|
func main() { |
|
p2 := plusTwo() |
|
fmt.Printf("%v\n",p2(2)) |
|
} |
|
|
|
|
|
func plusTwo() func(int) int { <1> |
|
return func(x int) int { return x + 2 } <2> |
|
} |
|
<CODE ENDS> |
|
|
|
2. Here we use a closure: |
|
|
|
<CODE BEGINS> |
|
func plusX(x int) func(int) int { <1> |
|
return func(y int) int { return x + y } <2> |
|
} |
|
<CODE ENDS> |
|
|
|
Here _1_, we again define a function that returns a function. We |
|
use the _local_ variable "x" in the function literal at _2_. |
|
|
|
5.7.13. Maximum |
|
|
|
1. Write a function that finds the maximum value in an "int" slice |
|
("[]int"). |
|
|
|
5.7.14. Answer |
|
|
|
1. This function returns the largest int in the slice \var{l}: |
|
|
|
<CODE BEGINS> |
|
func max(l []int) (max int) { <1> |
|
max = l[0] |
|
for _, v := range l { <2> |
|
if v > max { <3> |
|
max = v |
|
} |
|
} |
|
return <4> |
|
} |
|
<CODE ENDS> |
|
|
|
At _1_ we use a named return parameter. At _2_ we loop over "l". |
|
The index of the element is not important. At _3_, if we find a |
|
new maximum, we remember it. And at _4_ we have a "lone" return; |
|
the current value of "max" is now returned. |
|
|
|
5.7.15. Map function |
|
|
|
A "map()"-function is a function that takes a function and a list. |
|
The function is applied to each member in the list and a new list |
|
containing these calculated values is returned. Thus: |
|
|
|
"\mathrm{map}(f(), (a_1,a_2,\ldots,a_{n-1},a_n)) = (f(a_1), |
|
f(a_2),\ldots,f(a_{n-1}), f(a_n))" |
|
|
|
1. Write a simple "map()"-function in Go. It is sufficient for this |
|
function only to work for ints. |
|
|
|
5.7.16. Answer |
|
|
|
1. A possible answer: |
|
|
|
<CODE BEGINS> |
|
func Map(f func(int) int, l []int) []int { |
|
j := make([]int, len(l)) |
|
for k, v := range l { |
|
j[k] = f(v) |
|
} |
|
return j |
|
} |
|
|
|
|
|
func main() { |
|
m := []int{1, 3, 4} |
|
f := func(i int) int { |
|
return i * i |
|
} |
|
fmt.Printf("%v", (Map(f, m))) |
|
} |
|
<CODE ENDS> |
|
|
|
5.7.17. Stack |
|
|
|
1. Create a simple stack which can hold a fixed number of ints. It |
|
does not have to grow beyond this limit. Define "push" -- put |
|
something on the stack -- and "pop" -- retrieve something from |
|
the stack -- functions. The stack should be a LIFO (last in, |
|
first out) stack. |
|
|
|
A stack |
|
|
|
2. Write a "String" method which converts the stack to a string |
|
representation. The stack in the figure could be represented as: |
|
"[0:m] [1:l] [2:k]" . |
|
|
|
5.7.18. Answer |
|
|
|
1. First we define a new type that represents a stack; we need an |
|
array (to hold the keys) and an index, which points to the last |
|
element. Our small stack can only hold 10 elements. |
|
|
|
<CODE BEGINS> |
|
type stack struct { |
|
i int |
|
data [10]int |
|
} |
|
<CODE ENDS> |
|
|
|
Next we need the "push" and "pop" functions to actually use the |
|
thing. First we show the _wrong_ solution! |
|
|
|
In Go, data passed to functions is _passed-by-value_ meaning a copy |
|
is created and given to the function. The first stab for the |
|
function "push" could be: |
|
|
|
<CODE BEGINS> |
|
func (s stack) push(k int) { |
|
if s.i+1 > 9 { |
|
return |
|
} |
|
s.data[s.i] = k |
|
s.i++ |
|
} |
|
<CODE ENDS> |
|
|
|
The function works on the "s" which is of the type "stack". To use |
|
this we just call "s.push(50)", to push the integer 50 on the stack. |
|
But the push function gets a copy of "s", so it is _not_ working on |
|
the _real_ thing. Nothing gets pushed to our stack. For example the |
|
following code: |
|
|
|
<CODE BEGINS> |
|
var s stack |
|
s.push(25) |
|
fmt.Printf("stack %v\n", s); |
|
s.push(14) |
|
fmt.Printf("stack %v\n", s); |
|
<CODE ENDS> |
|
|
|
prints: |
|
|
|
stack [0:0] |
|
stack [0:0] |
|
|
|
To solve this we need to give the function "push" a pointer to the |
|
stack. This means we need to change "push" from |
|
|
|
<CODE BEGINS> |
|
func (s stack) push(k int) |
|
<CODE ENDS> |
|
|
|
to |
|
|
|
<CODE BEGINS> |
|
func (s *stack) push(k int). |
|
<CODE ENDS> |
|
|
|
We should now use "new()" (see Section 7.1.1). in Section 7 to create |
|
a _pointer_ to a newly allocated "stack", so line 1 from the example |
|
above needs to be "s := new(stack)" . |
|
|
|
And our two functions become: |
|
|
|
<CODE BEGINS> |
|
func (s *stack) push(k int) { |
|
s.data[s.i] = k |
|
s.i++ |
|
} |
|
|
|
func (s *stack) pop() int { |
|
s.i-- |
|
ret := s.data[s.i] |
|
s.data[s.i] = 0 |
|
return ret |
|
} |
|
<CODE ENDS> |
|
|
|
Which we then use as follows: |
|
|
|
<CODE BEGINS> |
|
func main() { |
|
var s stack |
|
s.push(25) |
|
s.push(14) |
|
fmt.Printf("stack %v\n", s) |
|
} |
|
<CODE ENDS> |
|
|
|
2. "fmt.Printf("%v")" can print any value ("%v") that satisfies the |
|
"Stringer" interface (see Section 8). For this to work we only |
|
need to define a "String()" function for our type: |
|
|
|
<CODE BEGINS> |
|
func (s stack) String() string { |
|
var str string |
|
for i := 0; i <= s.i; i++ { |
|
str = str + "[" + |
|
strconv.Itoa(i) + ":" + strconv.Itoa(s.data[i]) + "]" |
|
} |
|
return str |
|
} |
|
<CODE ENDS> |
|
|
|
6. Packages |
|
|
|
<blockquote>: |
|
"^(") |
|
|
|
A package is a collection of functions and data. |
|
|
|
You declare a package with the "package""package <name>" line. Let's |
|
define a package "even" in the file "even.go". |
|
|
|
|
|
|
|
<CODE BEGINS> |
|
package even <1> |
|
|
|
func Even(i int) bool { <2> |
|
return i%2 == 0 |
|
} |
|
|
|
func odd(i int) bool { <3> |
|
return i%2 == 1 |
|
} |
|
<CODE ENDS> |
|
|
|
Here _1_ we start a new namespace: "even". The function "Even" _2_ |
|
starts with a capital letter. This means the function is _exported_, |
|
and may be used outside our package (more on that later). The |
|
function "odd" _3_ does not start with a capital letter, so it is a |
|
_private_ function. |
|
|
|
Now we just need to build the package. We create a directory under |
|
"$GOPATH", and copy "even.go" there (see Section 4.2 in Section 4). |
|
|
|
% mkdir $GOPATH/src/even |
|
% cp even.go $GOPATH/src/even |
|
% go build |
|
% go install |
|
|
|
Now we can use the package in our own program "myeven.go": |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
import ( <1> |
|
"even" <2> |
|
"fmt" <3> |
|
) |
|
|
|
func main() { |
|
i := 5 |
|
fmt.Printf("Is %d even? %v\n", i, even.Even(i)) <4> |
|
} |
|
<CODE ENDS> |
|
|
|
Import _1_ the following packages. The _local_ package "even" is |
|
imported here _2_. This _3_ imports the official "fmt" package. And |
|
now we use _4_ the function from the "even" package. The syntax for |
|
accessing a function from a package is "<package>.FunctionName()". |
|
And finally we can build our program. |
|
|
|
% go build myeven.go |
|
% ./myeven |
|
Is 5 even? false |
|
|
|
If we change our "myeven.go" at _4_ to use the unexported function |
|
"even.odd": "fmt.Printf("Is %d even? %v\n", i, even.odd(i))" We get |
|
an error when compiling, because we are trying to use a _private_ |
|
function: |
|
|
|
myeven.go: cannot refer to unexported name even.odd |
|
|
|
Note that the "starts with capital "\rightarrow" exported", "starts |
|
with lower-case "\rightarrow" private" rule also extends to other |
|
names (new types, global variables) defined in the package. Note |
|
that the term "capital" is not limited to US-ASCII -- it extends to |
|
all bicameral alphabets (Latin, Greek, Cyrillic, Armenian and |
|
Coptic). |
|
|
|
6.1. Identifiers |
|
|
|
The Go standard library names some function with the old (Unix) names |
|
while others are in CamelCase. The convention is to leave well-known |
|
legacy not-quite-words alone rather than try to figure out where the |
|
capital letters go: "Atoi", "Getwd", "Chmod". CamelCasing works best |
|
when you have whole words to work with: "ReadFile", "NewWriter", |
|
"MakeSlice". The convention in Go is to use CamelCase rather than |
|
underscores to write multi-word names. |
|
|
|
As we did above in our "myeven" program, accessing content from an |
|
imported (with "import" "import "bytes"" the importing program can |
|
talk about "bytes.Buffer". A package name should be good, short, |
|
concise and evocative. The convention in Go is that package names |
|
are lowercase, single word names. |
|
|
|
The package name used in the "import" statement is the default name |
|
used. But if the need arises (two different packages with the same |
|
name for instance), you can override this default: "import bar |
|
"bytes"" The function "Buffer" is now accessed as "bar.Buffer". |
|
|
|
Another convention is that the package name is the base name of its |
|
source directory; the package in "src/compress/gzip" is imported as |
|
"compress/gzip" but has name "gzip", not "compress/gzip". |
|
|
|
It is important to avoid stuttering when naming things. For |
|
instance, the buffered reader type in the "bufio" "Reader", not |
|
"BufReader", because users see it as "bufio.Reader", which is a |
|
clear, concise name. |
|
|
|
Similarly, the function to make new instances of "ring.Ring" (package |
|
"container/ring"), would normally be called "NewRing", but since |
|
"Ring" is the only type exported by the package, and since the |
|
package is called "ring""New". Clients of the package see that as |
|
"ring.New". Use the package structure to help you choose good names. |
|
|
|
Another short example is "once.Do" (see package "sync"); |
|
"once.Do(setup)" reads well and would not be improved by writing |
|
"once.DoOrWaitUntilDone(setup)". Long names don't automatically make |
|
things more readable. |
|
|
|
6.2. Documenting packages |
|
|
|
When we created our "even" package, we skipped over an important |
|
item: documentation. Each package should have a _package comment_, a |
|
block comment preceding the "package" clause. In our case we should |
|
extend the beginning of the package, with: |
|
|
|
<CODE BEGINS> |
|
// The even package implements a fast function for detecting if an integer |
|
// is even or not. |
|
package even |
|
<CODE ENDS> |
|
|
|
When running "go doc" this will show up at the top of the page. When |
|
a package consists of multiple files the package comment should only |
|
appear in one file. A common convention (in really big packages) is |
|
to have a separate "doc.go" that only holds the package comment. |
|
Here is a snippet from the official "regexp" package: |
|
|
|
<CODE BEGINS> |
|
/* |
|
The regexp package implements a simple library for |
|
regular expressions. |
|
|
|
The syntax of the regular expressions accepted is: |
|
|
|
regexp: |
|
concatenation { '|' concatenation } |
|
*/ |
|
package regexp |
|
<CODE ENDS> |
|
|
|
Each defined (and exported) function should have a small line of text |
|
documenting the behavior of the function. Again to extend our "even" |
|
package: |
|
|
|
<CODE BEGINS> |
|
// Even returns true of i is even. Otherwise false is returned. |
|
func Even(i int) bool { |
|
<CODE ENDS> |
|
|
|
And even though "odd" is not exported, it's good form to document it |
|
as well. |
|
|
|
<CODE BEGINS> |
|
// odd is the opposite of Even. |
|
func odd(i int) bool { |
|
<CODE ENDS> |
|
|
|
6.3. Testing packages |
|
|
|
In Go it is customary to write (unit) tests for your package. |
|
Writing tests involves the "testing" package and the program "go |
|
test" |
|
|
|
The "go test" program runs all the test functions. Without any |
|
defined tests for our "even" package, "go test" yields: |
|
|
|
% go test |
|
? even [no test files] |
|
|
|
Let us fix this by defining a test in a test file. Test files reside |
|
in the package directory and are named "*_test.go". Those test files |
|
are just like other Go programs, but "go test" will only execute the |
|
test functions. Each test function has the same signature and its |
|
name should start with "Test": "func TestXxx(t *testing.T)" . |
|
|
|
When writing test you will need to tell "go test" whether a test was |
|
successful or not. A successful test function just returns. When |
|
the test fails you can signal this with the following functions. |
|
These are the most important ones (see "go doc testing" or "go help |
|
testfunc" for more): |
|
|
|
* "func (t *T) Fail()", "Fail" marks the test function as having |
|
failed but continues execution. |
|
|
|
* "func (t *T) FailNow()", "FailNow" marks the test function as |
|
having failed and stops its execution. Any remaining tests in |
|
this file are skipped, and execution continues with the next test. |
|
|
|
* "func (t *T) Log(args ...interface{})", "Log" formats its |
|
arguments using default formatting, analogous to "Print()", and |
|
records the text in the error log. |
|
|
|
* "func (t *T) Fatal(args ...interface{})", "Fatal" is equivalent to |
|
"Log()" followed by "FailNow()". |
|
|
|
Putting all this together we can write our test. First we pick a |
|
name: "even_test.go". Then we add the following contents: |
|
|
|
<CODE BEGINS> |
|
package even <1> |
|
|
|
import "testing" <2> |
|
|
|
func TestEven(t *testing.T) { <3> |
|
if !Even(2) { |
|
t.Log("2 should be even!") |
|
t.Fail() |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
A test file belongs to the current _1_ package. This is not only |
|
convenient, but also allows tests of unexported functions and |
|
structures. We then _2_ import the "testing" package. And finally |
|
the test we want to execute. The code here _3_ should hold no |
|
surprises: we check if the "Even" function works OK. And now, the |
|
moment we have been waiting form executing the test. |
|
|
|
% go test |
|
ok even 0.001s |
|
|
|
Our test ran and reported "ok". Success! If we redefine our test |
|
function, we can see the result of a failed test: |
|
|
|
<CODE BEGINS> |
|
// Entering the twilight zone |
|
func TestEven(t *testing.T) { |
|
if Even(2) { |
|
t.Log("2 should be odd!") |
|
t.Fail() |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
We now get: |
|
|
|
FAIL even 0.004s |
|
--- FAIL: TestEven (0.00 seconds) |
|
2 should be odd! |
|
FAIL |
|
|
|
And you can act accordingly (by fixing the test for instance). |
|
|
|
Writing new packages should go hand in hand with writing (some) |
|
documentation and test functions. It will make your code better and |
|
it shows that you really put in the effort. |
|
|
|
The Go test suite also allows you to incorporate example functions |
|
which serve as documentation _and_ as tests. These functions need to |
|
start with "Example". |
|
|
|
<CODE BEGINS> |
|
func ExampleEven() { |
|
if Even(2) { |
|
fmt.Printf("Is even\n") |
|
} |
|
// Output: <1> |
|
// Is even |
|
} |
|
<CODE ENDS> |
|
|
|
Those last two comments lines _1_ are part of the example, "go test" |
|
uses those to check the _generated_ output with the text in the |
|
comments. If there is a mismatch the test fails. |
|
|
|
6.4. Useful packages |
|
|
|
The standard libary of Go includes a huge number of packages. It is |
|
very enlightening to browse the "$GOROOT/src/pkg" directory and look |
|
at the packages. We cannot comment on each package, but the |
|
following are worth a mention: |
|
|
|
"fmt" |
|
"fmt" implements formatted I/O with functions analogous to C's |
|
"printf" and "scanf". The format verbs are derived from C's but |
|
are simpler. Some verbs (%-sequences) that can be used: |
|
|
|
* _%v_, the value in a default format. when printing structs, the |
|
plus flag (%+v) adds field names. |
|
|
|
* _%#v_, a Go-syntax representation of the value. |
|
|
|
* _%T_, a Go-syntax representation of the type of the value. |
|
|
|
"io" |
|
|
|
|
|
"bufio" |
|
"io.Reader" or "io.Writer" object, creating another object (Reader |
|
or Writer) that also implements the interface but provides |
|
buffering and some help for textual I/O. |
|
|
|
"sort" |
|
"sort" package provides primitives for sorting arrays and user- |
|
defined collections. |
|
|
|
"strconv" |
|
"strconv" package implements conversions to and from string |
|
representations of basic data types. |
|
|
|
"os" |
|
"os" package provides a platform-independent interface to |
|
operating system functionality. The design is Unix-like. |
|
|
|
"sync" |
|
"sync" provides basic synchronization primitives such as mutual |
|
exclusion locks. |
|
|
|
"flag" |
|
"flag" package implements command-line flag parsing. |
|
|
|
"encoding/json" |
|
"encoding/json" package implements encoding and decoding of JSON |
|
objects as defined in RFC 4627 [RFC4627]. |
|
|
|
"html/template" |
|
Templates are executed by applying them to a data structure. |
|
Annotations in the template refer to elements of the data |
|
structure (typically a field of a struct or a key in a map) to |
|
control execution and derive values to be displayed. The template |
|
walks the structure as it executes and the "cursor" @ represents |
|
the value at the current location in the structure. |
|
|
|
"net/http" |
|
"net/http" package implements parsing of HTTP requests, replies, |
|
and URLs and provides an extensible HTTP server and a basic HTTP |
|
client. |
|
|
|
"unsafe" |
|
"unsafe" package contains operations that step around the type |
|
safety of Go programs. Normally you don't need this package, but |
|
it is worth mentioning that _unsafe_ Go programs are possible. |
|
|
|
"reflect" |
|
"reflect" package implements run-time reflection, allowing a |
|
program to manipulate objects with arbitrary types. The typical |
|
use is to take a value with static type "interface{}" and extract |
|
its dynamic type information by calling "TypeOf", which returns an |
|
object with interface type "Type". See Section 8, |
|
Section Section 8.8. |
|
|
|
"os/exec" |
|
"os/exec" package runs external commands. |
|
|
|
6.5. Exercises |
|
|
|
6.5.1. Stack as package |
|
|
|
1. See the Stack exercise. In this exercise we want to create a |
|
separate package for that code. Create a proper package for your |
|
stack implementation, "Push", "Pop" and the "Stack" type need to |
|
be exported. |
|
|
|
2. Write a simple unit test for this package. You should at least |
|
test that a "Pop" works after a "Push". |
|
|
|
6.5.2. Answer |
|
|
|
1. There are a few details that should be changed to make a proper |
|
package for our stack. First, the exported functions should |
|
begin with a capital letter and so should "Stack". The package |
|
file is named "stack-as-package.go" and contains: |
|
|
|
<CODE BEGINS> |
|
package stack |
|
|
|
// Stack holds the items. |
|
type Stack struct { |
|
i int |
|
data [10]int |
|
} |
|
|
|
// Push pushes an item on the stack. |
|
func (s *Stack) Push(k int) { |
|
s.data[s.i] = k |
|
s.i++ |
|
} |
|
|
|
// Pop pops an item from the stack. |
|
func (s *Stack) Pop() (ret int) { |
|
s.i-- |
|
ret = s.data[s.i] |
|
return |
|
} |
|
<CODE ENDS> |
|
|
|
2. To make the unit testing work properly you need to do some |
|
preparations. We'll come to those in a minute. First the actual |
|
unit test. Create a file with the name "pushpop_test.go", with |
|
the following contents: |
|
|
|
<CODE BEGINS> |
|
package stack |
|
|
|
import "testing" |
|
|
|
func TestPushPop(t *testing.T) { |
|
c := new(Stack) |
|
c.Push(5) |
|
if c.Pop() != 5 { |
|
t.Log("Pop doesn't give 5") |
|
t.Fail() |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
For "go test" to work we need to put our package files in a directory |
|
under "$GOPATH/src": |
|
|
|
% mkdir $GOPATH/src/stack |
|
% cp pushpop_test.go $GOPATH/src/stack |
|
% cp stack-as-package.go $GOPATH/src/stack |
|
|
|
Yields: |
|
|
|
% go test stack |
|
ok stack 0.001s |
|
|
|
6.5.3. Calculator |
|
|
|
1. Create a reverse polish calculator. Use your stack package. |
|
|
|
6.5.4. Answer |
|
|
|
1. This is one answer: |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
import ( |
|
"bufio" |
|
"fmt" |
|
"os" |
|
"strconv" |
|
) |
|
|
|
var reader *bufio.Reader = bufio.NewReader(os.Stdin) |
|
var st = new(Stack) |
|
|
|
type Stack struct { |
|
i int |
|
data [10]int |
|
} |
|
|
|
func (s *Stack) push(k int) { |
|
if s.i+1 > 9 { |
|
return |
|
} |
|
s.data[s.i] = k |
|
s.i++ |
|
} |
|
|
|
func (s *Stack) pop() (ret int) { |
|
s.i-- |
|
if s.i < 0 { |
|
s.i = 0 |
|
return |
|
} |
|
ret = s.data[s.i] |
|
return |
|
} |
|
|
|
func main() { |
|
for { |
|
s, err := reader.ReadString('\n') |
|
var token string |
|
if err != nil { |
|
return |
|
} |
|
for _, c := range s { |
|
switch { |
|
case c >= '0' && c <= '9': |
|
token = token + string(c) |
|
case c == ' ': |
|
r, _ := strconv.Atoi(token) |
|
st.push(r) |
|
token = "" |
|
case c == '+': |
|
fmt.Printf("%d\n", st.pop()+st.pop()) |
|
case c == '*': |
|
fmt.Printf("%d\n", st.pop()*st.pop()) |
|
case c == '-': |
|
p := st.pop() |
|
q := st.pop() |
|
fmt.Printf("%d\n", q-p) |
|
case c == 'q': |
|
return |
|
default: |
|
//error |
|
} |
|
} |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
7. Beyond the basics |
|
|
|
<blockquote>: |
|
Go has pointers but not pointer arithmetic. You cannot use a pointer |
|
variable to walk through the bytes of a string. |
|
|
|
In this chapter we delve deeper in to the language. |
|
|
|
Go has pointers. There is however no pointer arithmetic, so they act |
|
more like references than pointers that you may know from C. |
|
Pointers are useful. Remember that when you call a function in Go, |
|
the variables are _pass-by-value_. So, for efficiency and the |
|
possibility to modify a passed value _in_ functions we have pointers. |
|
|
|
You declare a pointer by prefixing the type with an '"*"': "var p |
|
*int". Now "p" is a pointer to an integer value. All newly declared |
|
variables are assigned their zero value and pointers are no |
|
different. A newly declared pointer, or just a pointer that points |
|
to nothing, has a nil-value "nil". To make a pointer point to |
|
something you can use the address-of operator "&"), which we |
|
demonstrate here: |
|
|
|
<CODE BEGINS> |
|
var p *int |
|
fmt.Printf("%v", p) <1> |
|
|
|
var i int <2> |
|
p = &i <3> |
|
|
|
fmt.Printf("%v", p) <4> |
|
<CODE ENDS> |
|
|
|
This _1_ Prints "nil". Declare _2_ an integer variable "i". Make |
|
"p" point _3_ to "i", i.e. take the address of "i". And this _4_ |
|
will print something like "0x7ff96b81c000a". De-referencing a |
|
pointer is done by prefixing the pointer variable with "*". |
|
|
|
As said, there is no pointer arithmetic, so if you write: "*p++", it |
|
is interpreted as "(*p)++": first reference and then increment the |
|
value. |
|
|
|
7.1. Allocation |
|
|
|
Go also has garbage collection, meaning that you don't have to worry |
|
about memory deallocation. |
|
|
|
To allocate memory Go has two primitives, "new" and "make". They do |
|
different things and apply to different types, which can be |
|
confusing, but the rules are simple. The following sections show how |
|
to handle allocation in Go and hopefully clarifies the somewhat |
|
artificial distinction between "new" "make" |
|
|
|
7.1.1. Allocation with new |
|
|
|
The built-in function "new" is essentially the same as its namesakes |
|
in other languages: "new(T)" allocates zeroed storage for a new item |
|
of type "T" and returns its address, a value of type "*T". Or in |
|
other words, it returns a pointer to a newly allocated zero value of |
|
type "T". This is important to remember. |
|
|
|
The documentation for "bytes.Buffer" states that "the zero value for |
|
Buffer is an empty buffer ready to use.". Similarly, "sync.Mutex" |
|
does not have an explicit constructor or Init method. Instead, the |
|
zero value for a "sync.Mutex" is defined to be an unlocked mutex. |
|
|
|
7.1.2. Allocation with make |
|
|
|
The built-in function "make(T, args)" serves a purpose different from |
|
"new(T)". It creates slices, maps, and channels _only_, and it |
|
returns an initialized (not zero!) value of type "T", and not a |
|
pointer: "*T". The reason for the distinction is that these three |
|
types are, under the covers, references to data structures that must |
|
be initialized before use. A slice, for example, is a three-item |
|
descriptor containing a pointer to the data (inside an array), the |
|
length, and the capacity; until those items are initialized, the |
|
slice is "nil". For slices, maps, and channels, "make" initializes |
|
the internal data structure and prepares the value for use. |
|
|
|
For instance, "make([]int, 10, 100)" allocates an array of 100 ints |
|
and then creates a slice structure with length 10 and a capacity of |
|
100 pointing at the first 10 elements of the array. In contrast, |
|
"new([]int)" returns a pointer to a newly allocated, zeroed slice |
|
structure, that is, a pointer to a "nil" slice value. These examples |
|
illustrate the difference between "new" and "make". |
|
|
|
<CODE BEGINS> |
|
var p *[]int = new([]int) <1> |
|
var v []int = make([]int, 100) <2> |
|
|
|
var p *[]int = new([]int) <3> |
|
*p = make([]int, 100, 100) |
|
|
|
v := make([]int, 100) <4> |
|
<CODE ENDS> |
|
|
|
Allocates _1_ slice structure; rarely useful. "v" _2_ refers to a new |
|
array of 100 ints. At _3_ we make it unnecessarily complex, _4_ is |
|
more idiomatic. |
|
|
|
Remember that "make" applies only to maps, slices and channels and |
|
does not return a pointer. To obtain an explicit pointer allocate |
|
with "new". |
|
|
|
<aside>: |
|
*new* allocates; *make* initializes. |
|
The above two paragraphs can be summarized as: |
|
* "new(T)" returns "*T" pointing to a zeroed "T" |
|
|
|
* "make(T)" returns an initialized "T" |
|
And of course "make" is only used for slices, maps and channels. |
|
|
|
7.1.3. Constructors and composite literals |
|
|
|
Sometimes the zero value isn't good enough and an initializing |
|
constructor is necessary, as in this example taken from the package |
|
"os". |
|
|
|
<CODE BEGINS> |
|
func NewFile(fd int, name string) *File { |
|
if fd < 0 { |
|
return nil |
|
} |
|
f := new(File) |
|
f.fd = fd |
|
f.name = name |
|
f.dirinfo = nil |
|
f.nepipe = 0 |
|
return f |
|
} |
|
<CODE ENDS> |
|
|
|
There's a lot of boiler plate in there. We can simplify it using a |
|
_composite literal_ |
|
|
|
<CODE BEGINS> |
|
func NewFile(fd int, name string) *File { |
|
if fd < 0 { |
|
return nil |
|
} |
|
f := File{fd, name, nil, 0} |
|
return &f <1> |
|
} |
|
<CODE ENDS> |
|
|
|
It is OK to return the address of a local variable _1_ the storage |
|
associated with the variable survives after the function returns. |
|
|
|
In fact, taking the address of a composite literal allocates a fresh |
|
instance each time it is evaluated, so we can combine these last two |
|
lines. |
|
|
|
<CODE BEGINS> |
|
return &File{fd, name, nil, 0} |
|
<CODE ENDS> |
|
|
|
The items (called fields) of a composite literal are laid out in |
|
order and must all be present. However, by labeling the elements |
|
explicitly as field:value pairs, the initializers can appear in any |
|
order, with the missing ones left as their respective zero values. |
|
Thus we could say |
|
|
|
<CODE BEGINS> |
|
return &File{fd: fd, name: name} |
|
<CODE ENDS> |
|
|
|
As a limiting case, if a composite literal contains no fields at all, |
|
it creates a zero value for the type. The expressions "new(File)" |
|
and "&File{}" are equivalent. In fact the use of "new" is |
|
discouraged. |
|
|
|
Composite literals can also be created for arrays, slices, and maps, |
|
with the field labels being indices or map keys as appropriate. In |
|
these examples, the initializations work regardless of the values of |
|
"Enone", and "Einval", as long as they are distinct: |
|
|
|
<CODE BEGINS> |
|
ar := [...]string{Enone: "no error", Einval: "invalid argument"} |
|
sl := []string{Enone: "no error", Einval: "invalid argument"} |
|
ma := map[int]string {Enone: "no error", Einval: "invalid argument"} |
|
<CODE ENDS> |
|
|
|
7.2. Defining your own types |
|
|
|
Of course Go allows you to define new types, it does this with the |
|
"type""type foo int" |
|
|
|
This creates a new type "foo" which acts like an "int". Creating |
|
more sophisticated types is done with the "struct" "string") and age |
|
("int") in a single structure and make it a new type: |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
import "fmt" |
|
|
|
type NameAge struct { |
|
name string // Both non exported fields. |
|
age int |
|
} |
|
|
|
func main() { |
|
a := new(NameAge) |
|
a.name = "Pete" |
|
a.age = 42 |
|
fmt.Printf("%v\n", a) |
|
} |
|
<CODE ENDS> |
|
|
|
Apropos, the output of "fmt.Printf("%v\n", a)" is "&{Pete 42}" |
|
|
|
That is nice! Go knows how to print your structure. If you only |
|
want to print one, or a few, fields of the structure you'll need to |
|
use ".<field name>". For example to only print the name: |
|
|
|
<CODE BEGINS> |
|
fmt.Printf("%s", a.name) |
|
<CODE ENDS> |
|
|
|
7.2.1. More on structure fields |
|
|
|
As said each item in a structure is called a field"struct {}". Or |
|
one with four fields: |
|
|
|
<CODE BEGINS> |
|
struct { |
|
x, y int |
|
A *[]int |
|
F func() |
|
} |
|
<CODE ENDS> |
|
|
|
If you omit the name for a field, you create an anonymous field |
|
(((field, anonymous))), for instance: |
|
|
|
<CODE BEGINS> |
|
struct { |
|
T1 // Field name is T1. |
|
*T2 // Field name is T2. |
|
P.T3 // Field name is T3. |
|
x, y int // Field names are x and y. |
|
} |
|
<CODE ENDS> |
|
|
|
Note that field names that start with a capital letter are exported, |
|
i.e. can be set or read from other packages. Field names that start |
|
with a lowercase are private to the current package. The same goes |
|
for functions defined in packages, see Section 6 for the details. |
|
|
|
7.2.2. Methods |
|
|
|
If you create functions that work on your newly defined type, you can |
|
take two routes: |
|
|
|
1. Create a function that takes the type as an argument. |
|
|
|
<CODE BEGINS> |
|
func doSomething(n1 *NameAge, n2 int) { /* */ } |
|
<CODE ENDS> |
|
|
|
2. Create a function that works on the type (see _receiver_ in |
|
Section 5): |
|
|
|
<CODE BEGINS> |
|
func (n1 *NameAge) doSomething(n2 int) { /* */ } |
|
<CODE ENDS> |
|
|
|
This is a method call, which can be used as: |
|
|
|
<CODE BEGINS> |
|
var n *NameAge |
|
n.doSomething(2) |
|
<CODE ENDS> |
|
|
|
Whether to use a function or method is entirely up to the programmer, |
|
but if you want to satisfy an interface (see the next chapter) you |
|
must use methods. If no such requirement exists it is a matter of |
|
taste whether to use functions or methods. |
|
|
|
But keep the following in mind, this is quoted from [go_spec]: |
|
|
|
<blockquote>: |
|
If "x" is addressable and "&x"'s method set contains "m", "x.m()" is |
|
shorthand for "(&x).m()". |
|
|
|
In the above case this means that the following is _not_ an error: |
|
|
|
<CODE BEGINS> |
|
var n NameAge // Not a pointer |
|
n.doSomething(2) |
|
<CODE ENDS> |
|
|
|
Here Go will search the method list for "n" of type "NameAge", come |
|
up empty and will then _also_ search the method list for the type |
|
"*NameAge" and will translate this call to "(&n).doSomething(2)". |
|
|
|
There is a subtle but major difference between the following type |
|
declarations. Also see the Section "Type Declarations" [go_spec]. |
|
Suppose we have: |
|
|
|
<CODE BEGINS> |
|
// A Mutex is a data type with two methods, Lock and Unlock. |
|
type Mutex struct { /* Mutex fields */ } |
|
func (m *Mutex) Lock() { /* Lock impl. */ } |
|
func (m *Mutex) Unlock() { /* Unlock impl. */ } |
|
<CODE ENDS> |
|
|
|
We now create two types in two different manners: |
|
|
|
* "type NewMutex Mutex". |
|
|
|
* "type PrintableMutex struct{Mutex}". |
|
|
|
"NewMutex" is equal to "Mutex", but it _does not_ have _any_ of the |
|
methods of "Mutex". In other words its method set is empty. But |
|
"PrintableMutex" _has_ _inherited_ "Mutex". The Go term for this is |
|
_embedding_ [go_spec]: |
|
|
|
<blockquote>: |
|
The method set of "*PrintableMutex" contains the methods "Lock" and |
|
"Unlock" bound to its anonymous field "Mutex". |
|
|
|
7.3. Conversions |
|
|
|
Sometimes you want to convert a type to another type. This is |
|
possible in Go, but there are some rules. For starters, converting |
|
from one value to another is done by operators (that look like |
|
functions: "byte()") and not all conversions are allowed. |
|
|
|
+---------+-----------+-----------+-----------+-----------+--------+------------+ |
|
+=========+===========+===========+===========+===========+========+============+ |
|
+---------+-----------+-----------+-----------+-----------+--------+------------+ |
|
+---------+-----------+-----------+-----------+-----------+--------+------------+ |
|
+---------+-----------+-----------+-----------+-----------+--------+------------+ |
|
+---------+-----------+-----------+-----------+-----------+--------+------------+ |
|
+---------+-----------+-----------+-----------+-----------+--------+------------+ |
|
+---------+-----------+-----------+-----------+-----------+--------+------------+ |
|
+---------+-----------+-----------+-----------+-----------+--------+------------+ |
|
|
|
Table 4: Valid conversions, "float64" works the same as |
|
"float32". |
|
|
|
* From a "string" to a slice of bytes or runes. |
|
|
|
<CODE BEGINS> |
|
mystring := "hello this is string" |
|
byteslice := []byte(mystring) |
|
<CODE ENDS> |
|
|
|
Converts to a "byte" slice, each "byte" contains the integer value |
|
of the corresponding byte in the string. Note that as strings in |
|
Go are encoded in UTF-8 some characters in the string may end up |
|
in 1, 2, 3 or 4 bytes. |
|
|
|
<CODE BEGINS> |
|
runeslice := []rune(mystring) |
|
<CODE ENDS> |
|
|
|
Converts to an "rune" slice, each "rune" contains a Unicode code |
|
point. Every character from the string corresponds to one rune. |
|
|
|
* From a slice of bytes or runes to a "string". |
|
|
|
<CODE BEGINS> |
|
b := []byte{'h','e','l','l','o'} // Composite literal. |
|
s := string(b) |
|
i := []rune{257,1024,65} |
|
r := string(i) |
|
<CODE ENDS> |
|
|
|
For numeric values the following conversions are defined: |
|
|
|
* Convert to an integer with a specific (bit) length: "uint8(int)" |
|
|
|
* From floating point to an integer value: "int(float32)". This |
|
discards the fraction part from the floating point value. |
|
|
|
* And the other way around: "float32(int)". |
|
|
|
7.3.1. User defined types and conversions |
|
|
|
How can you convert between the types you have defined yourself? We |
|
create two types here "Foo" and "Bar", where "Bar" is an alias for |
|
"Foo": |
|
|
|
<CODE BEGINS> |
|
type foo struct { int } // Anonymous struct field. |
|
type bar foo // bar is an alias for foo. |
|
<CODE ENDS> |
|
|
|
Then we: |
|
|
|
<CODE BEGINS> |
|
var b bar = bar{1} // Declare `b` to be a `bar`. |
|
var f foo = b // Assign `b` to `f`. |
|
<CODE ENDS> |
|
|
|
Which fails on the last line with: "cannot use b (type bar) as type |
|
foo in assignment" |
|
|
|
This can be fixed with a conversion: "var f foo = foo(b)" |
|
|
|
Note that converting structures that are not identical in their |
|
fields is more difficult. Also note that converting "b" to a plain |
|
"int" also fails; an integer is not the same as a structure |
|
containing an integer. |
|
|
|
7.4. Exercises |
|
|
|
7.4.1. Map function with interfaces |
|
|
|
1. Use the answer from the earlier map exercise but now make it |
|
generic using interfaces. Make it at least work for ints and |
|
strings. |
|
|
|
7.4.2. Answer |
|
|
|
1. |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
import "fmt" |
|
|
|
// Define the empty interface as a type. |
|
type e interface{} |
|
|
|
func mult2(f e) e { |
|
switch f.(type) { |
|
case int: |
|
return f.(int) * 2 |
|
case string: |
|
return f.(string) + f.(string) + f.(string) + f.(string) |
|
} |
|
return f |
|
} |
|
|
|
func Map(n []e, f func(e) e) []e { |
|
m := make([]e, len(n)) |
|
for k, v := range n { |
|
m[k] = f(v) |
|
} |
|
return m |
|
} |
|
|
|
func main() { |
|
m := []e{1, 2, 3, 4} |
|
s := []e{"a", "b", "c", "d"} |
|
mf := Map(m, mult2) |
|
sf := Map(s, mult2) |
|
fmt.Printf("%v\n", mf) |
|
fmt.Printf("%v\n", sf) |
|
} |
|
<CODE ENDS> |
|
|
|
7.4.3. Pointers |
|
|
|
1. Suppose we have defined the following structure: |
|
|
|
<CODE BEGINS> |
|
type Person struct { |
|
name string |
|
age int |
|
} |
|
<CODE ENDS> |
|
|
|
What is the difference between the following two lines? |
|
|
|
<CODE BEGINS> |
|
var p1 Person |
|
p2 := new(Person) |
|
<CODE ENDS> |
|
|
|
2. What is the difference between the following two allocations? |
|
|
|
<CODE BEGINS> |
|
func Set(t *T) { |
|
x = t |
|
} |
|
<CODE ENDS> |
|
|
|
and |
|
|
|
<CODE BEGINS> |
|
func Set(t T) { |
|
x= &t |
|
} |
|
<CODE ENDS> |
|
|
|
7.4.4. Answer |
|
|
|
1. The expression, "var p1 Person" allocates a "Person"-_value_ to |
|
"p1". The type of "p1" is "Person". The second line: "p2 := |
|
new(Person)" allocates memory and assigns a _pointer_ to "p2". |
|
The type of "p2" is "*Person". |
|
|
|
2. In the first function, "x" points to the same thing that "t" |
|
does, which is the same thing that the actual argument points to. |
|
So in the second function, we have an "extra" variable containing |
|
a copy of the interesting value. In the second function, "x" |
|
points to a new (heap-allocated) variable "t" which contains a |
|
copy of whatever the actual argument value is. |
|
|
|
7.4.5. Linked List |
|
|
|
1. Make use of the package "container/list" to create a (doubly) |
|
linked list. Push the values 1, 2 and 4 to the list and then |
|
print it. |
|
|
|
2. Create your own linked list implementation. And perform the same |
|
actions as above. |
|
|
|
7.4.6. Answer |
|
|
|
1. The following is the implementation of a program using doubly |
|
linked lists from "container/list". |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
import ( |
|
"container/list" |
|
"fmt" |
|
) |
|
|
|
func main() { |
|
l := list.New() |
|
l.PushBack(1) |
|
l.PushBack(2) |
|
l.PushBack(4) |
|
|
|
for e := l.Front(); e != nil; e = e.Next() { |
|
fmt.Printf("%v\n", e.Value) |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
2. The following is a program implementing a simple doubly linked |
|
list supporting "int" values. |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
import ( |
|
"errors" <1> |
|
"fmt" |
|
) |
|
|
|
type Value int <2> |
|
|
|
type Node struct { <3> |
|
Value |
|
prev, next *Node |
|
} |
|
|
|
type List struct { |
|
head, tail *Node |
|
} |
|
|
|
func (l *List) Front() *Node { <4> |
|
return l.head |
|
} |
|
|
|
func (n *Node) Next() *Node { |
|
return n.next |
|
} |
|
|
|
func (l *List) Push(v Value) *List { |
|
n := &Node{Value: v} <5> |
|
|
|
if l.head == nil { <6> |
|
l.head = n |
|
} else { |
|
l.tail.next = n <7> |
|
n.prev = l.tail <8> |
|
} |
|
l.tail = n <9> |
|
|
|
return l |
|
} |
|
|
|
var errEmpty = errors.New("List is empty") |
|
|
|
func (l *List) Pop() (v Value, err error) { |
|
if l.tail == nil { <10> |
|
err = errEmpty |
|
} else { |
|
v = l.tail.Value <11> |
|
l.tail = l.tail.prev <12> |
|
if l.tail == nil { |
|
l.head = nil <13> |
|
} |
|
} |
|
|
|
return v, err |
|
} |
|
|
|
func main() { |
|
l := new(List) |
|
|
|
l.Push(1) |
|
l.Push(2) |
|
l.Push(4) |
|
|
|
for n := l.Front(); n != nil; n = n.Next() { |
|
fmt.Printf("%v\n", n.Value) |
|
} |
|
|
|
fmt.Println() |
|
|
|
for v, err := l.Pop(); err == nil; v, err = l.Pop() { |
|
fmt.Printf("%v\n", v) |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
Import <_1_> the packages we will need. At <_2_> we declare a type |
|
for the value our list will contain, this is not strictly neccesary. |
|
And at <_3_> we declare a type for the each node in our list. At |
|
<_4_> we define the "Front" method for our list. When pushing, |
|
create a new Node <_5_> with the provided value. If the list is |
|
empty <_6_>, put the new node at the head. Otherwise <_7_> put it at |
|
the tail and make sure <_8_> the new node points back to the |
|
previously existing one. At <_9_> we re-adjust tail to the newly |
|
inserted node. |
|
|
|
In the Pop _10_ method, we return an error if the list is empty. If |
|
it is not empty _11_ we save the last value. And then _12_ discard |
|
the last node from the list. Finally at _13_ we make sure the list |
|
is consistent if it becomes empty. |
|
|
|
7.4.7. Cat |
|
|
|
1. Write a program which mimics the Unix program "cat". |
|
|
|
2. Make it support the "-n" flag, where each line is numbered. |
|
|
|
3. The solution to the above question given in contains a bug. Can |
|
you spot and fix it? |
|
|
|
7.4.8. Answer |
|
|
|
1. The following is implemention of "cat" which also supports a -n |
|
flag to number each line. |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
import ( |
|
"bufio" |
|
"flag" |
|
"fmt" |
|
"io" <1> |
|
"os" |
|
) |
|
|
|
var numberFlag = flag.Bool("n", false, "number each line") // <<2>> |
|
|
|
func cat(r *bufio.Reader) { <3> |
|
i := 1 |
|
for { |
|
buf, e := r.ReadBytes('\n') <4> |
|
if e == io.EOF { <5> |
|
break |
|
} |
|
if *numberFlag { <6> |
|
fmt.Fprintf(os.Stdout, "%5d %s", i, buf) |
|
i++ |
|
} else { <7> |
|
fmt.Fprintf(os.Stdout, "%s", buf) |
|
} |
|
} |
|
return |
|
} |
|
|
|
func main() { |
|
flag.Parse() |
|
if flag.NArg() == 0 { |
|
cat(bufio.NewReader(os.Stdin)) |
|
} |
|
for i := 0; i < flag.NArg(); i++ { |
|
f, e := os.Open(flag.Arg(i)) |
|
if e != nil { |
|
fmt.Fprintf(os.Stderr, "%s: error reading from %s: %s\n", |
|
os.Args[0], flag.Arg(i), e.Error()) |
|
continue |
|
} |
|
cat(bufio.NewReader(f)) |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
At _1_ we include all the packages we need. Here _2_ we define a new |
|
flag "n", which defaults to off. Note that we get the help (-h) for |
|
free. Start the function _3_ that actually reads the file's contents |
|
and displays it; Read one line at the time at _4_. And stop _5_ if we |
|
hit the end. If we should number each line, print the line number |
|
and then the line itself _6_. Otherwise _7_ we could just print the |
|
line. |
|
|
|
2. The bug show itself when the last line of the input does not |
|
contain a newline. Or worse, when the input contains one line |
|
without a closing newline nothing is shown at all. A better |
|
solution is the following program. |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
import ( |
|
"bufio" |
|
"flag" |
|
"fmt" |
|
"io" |
|
"os" |
|
) |
|
|
|
var numberFlag = flag.Bool("n", false, "number each line") |
|
|
|
func cat(r *bufio.Reader) { |
|
i := 1 |
|
for { |
|
buf, e := r.ReadBytes('\n') |
|
if e == io.EOF && string(buf) == "" { |
|
break |
|
} |
|
if *numberFlag { |
|
fmt.Fprintf(os.Stdout, "%5d %s", i, buf) |
|
i++ |
|
} else { |
|
fmt.Fprintf(os.Stdout, "%s", buf) |
|
} |
|
} |
|
return |
|
} |
|
|
|
func main() { |
|
flag.Parse() |
|
if flag.NArg() == 0 { |
|
cat(bufio.NewReader(os.Stdin)) |
|
} |
|
for i := 0; i < flag.NArg(); i++ { |
|
f, e := os.Open(flag.Arg(i)) |
|
if e != nil { |
|
fmt.Fprintf(os.Stderr, "%s: error reading from %s: %s\n", |
|
os.Args[0], flag.Arg(i), e.Error()) |
|
continue |
|
} |
|
cat(bufio.NewReader(f)) |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
7.4.9. Method calls |
|
|
|
1. Suppose we have the following program. Note the package |
|
"container/vector" was once part of Go, but was removed when the |
|
"append" built-in was introduced. However, for this question |
|
this isn't important. The package implemented a stack-like |
|
structure, with push and pop methods. |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
|
|
import "container/vector" |
|
|
|
|
|
func main() { |
|
k1 := vector.IntVector{} |
|
k2 := &vector.IntVector{} |
|
k3 := new(vector.IntVector) |
|
k1.Push(2) |
|
k2.Push(3) |
|
k3.Push(4) |
|
} |
|
<CODE ENDS> |
|
|
|
What are the types of "k1", "k2" and "k3"? |
|
|
|
2. Now, this program compiles and runs OK. All the "Push" |
|
operations work even though the variables are of a different |
|
type. The documentation for "Push" says: |
|
|
|
<blockquote>: "func (p *IntVector) Push(x int)" Push appends x to |
|
the end of the vector. |
|
|
|
So the receiver has to be of type "*IntVector", why does the code |
|
above (the Push statements) work correctly then? |
|
|
|
7.4.10. Answer |
|
|
|
1. The type of "k1" is "vector.IntVector". Why? We use a composite |
|
literal (the "{}"), so we get a value of that type back. The |
|
variable "k2" is of "*vector.IntVector", because we take the |
|
address ("&") of the composite literal. And finally "k3" has |
|
also the type "*vector.IntVector", because "new" returns a |
|
pointer to the type. |
|
|
|
2. The answer is given in [go_spec] in the section "Calls", where |
|
among other things it says: |
|
|
|
<blockquote>: |
|
A method call "x.m()" is valid if the method set of (the type of) "x" |
|
contains "m" and the argument list can be assigned to the parameter |
|
list of "m". If "x" is addressable and "&x"'s method set contains |
|
"m", "x.m()" is shorthand for "(&x).m()". |
|
|
|
In other words because "k1" is addressable and "*vector.IntVector" |
|
_does_ have the "Push" method, the call "k1.Push(2)" is translated by |
|
Go into "(&k1).Push(2)" which makes the type system happy again (and |
|
you too -- now you know this). |
|
|
|
8. Interfaces |
|
|
|
<blockquote>: |
|
I have this phobia about having my body penetrated surgically. You |
|
know what I mean? |
|
|
|
In Go, the word _interface__set of methods defined_ for "S" with one |
|
field, and defines two methods for "S". |
|
|
|
<CODE BEGINS> |
|
type S struct { i int } |
|
func (p *S) Get() int { return p.i } |
|
func (p *S) Put(v int) { p.i = v } |
|
<CODE ENDS> |
|
|
|
Figure 1: Defining a struct and methods on it. |
|
|
|
You can also define an "I" with two methods: |
|
|
|
<CODE BEGINS> |
|
type I interface { |
|
Get() int |
|
Put(int) |
|
} |
|
<CODE ENDS> |
|
|
|
"S" is a valid _implementation_ for interface "I", because it defines |
|
the two methods which "I" requires. Note that this is true even |
|
though there is no explicit declaration that "S" implements "I". |
|
|
|
A Go program can use this fact via yet another meaning of interface, |
|
which is an interface value: |
|
|
|
<CODE BEGINS> |
|
func f(p I) { <1> |
|
fmt.Println(p.Get()) <2> |
|
p.Put(1) <3> |
|
} |
|
<CODE ENDS> |
|
|
|
At _1_ we declare a function that takes an interface type as the |
|
argument. Because "p" implements "I", it _must_ have the "Get()" |
|
method, which we call at _2_. And the same holds true for the "Put()" |
|
method at _3_. Because "S" implements "I", we can call the function |
|
"f" passing in a pointer to a value of type "S": "var s S; f(&s)" |
|
|
|
The reason we need to take the address of "s", rather than a value of |
|
type "S", is because we defined the methods on "s" to operate on |
|
pointers, see the definition in the code above. This is not a |
|
requirement -- we could have defined the methods to take values -- |
|
but then the "Put" method would not work as expected. |
|
|
|
The fact that you do not need to declare whether or not a type |
|
implements an interface means that Go implements a form of duck |
|
typing [duck_typing]. This is not pure duck typing, because when |
|
possible the Go compiler will statically check whether the type |
|
implements the interface. However, Go does have a purely dynamic |
|
aspect, in that you can convert from one interface type to another. |
|
In the general case, that conversion is checked at run time. If the |
|
conversion is invalid -- if the type of the value stored in the |
|
existing interface value does not satisfy the interface to which it |
|
is being converted -- the program will fail with a run time error. |
|
|
|
Interfaces in Go are similar to ideas in several other programming |
|
languages: pure abstract virtual base classes in C++, typeclasses in |
|
Haskell or duck typing in Python. However there is no other language |
|
which combines interface values, static type checking, dynamic run |
|
time conversion, and no requirement for explicitly declaring that a |
|
type satisfies an interface. The result in Go is powerful, flexible, |
|
efficient, and easy to write. |
|
|
|
8.1. Which is what? |
|
|
|
Let's define another type "R" that also implements the interface "I": |
|
|
|
<CODE BEGINS> |
|
type R struct { i int } |
|
func (p *R) Get() int { return p.i } |
|
func (p *R) Put(v int) { p.i = v } |
|
<CODE ENDS> |
|
|
|
The function "f" can now accept variables of type "R" and "S". |
|
|
|
Suppose you need to know the actual type in the function "f". In Go |
|
you can figure that out by using a type switch |
|
|
|
<CODE BEGINS> |
|
func f(p I) { |
|
switch t := p.(type) { <1> |
|
case *S: <2> |
|
case *R: <2> |
|
default: <3> |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
At _1_ we use the type switch, note that the ".(type)" syntax is |
|
_only_ valid within a "switch" statement. We store the value in the |
|
variable "t". The subsequent cases _2_ each check for a different |
|
_actual_ type. And we can even have a "default" _3_ clause. It is |
|
worth pointing out that both "case R" and "case s" aren't possible, |
|
because "p" needs to be a pointer in order to satisfy "i". |
|
|
|
A type switch isn't the only way to discover the type at _run-time_. |
|
|
|
<CODE BEGINS> |
|
if t, ok := something.(I); ok { <1> |
|
// ... |
|
} |
|
<CODE ENDS> |
|
|
|
You can also use a "comma, ok" form _1_ to see if an interface type |
|
implements a specific interface. If "ok" is true, "t" will hold the |
|
type of "something". When you are sure a variable implements an |
|
interface you can use: "t := something.(I)" . |
|
|
|
8.2. Empty interface |
|
|
|
Since every type satisfies the empty interface: "interface{}" we can |
|
create a generic function which has an empty interface as its |
|
argument: |
|
|
|
<CODE BEGINS> |
|
func g(something interface{}) int { |
|
return something.(I).Get() |
|
} |
|
<CODE ENDS> |
|
|
|
The "return something.(I).Get()" is the tricky bit in this function. |
|
The value "something" has type "interface{}", meaning no guarantee of |
|
any methods at all: it could contain any type. The ".(I)" is a type |
|
assertion "something" to an interface of type "I". If we have that |
|
type we can invoke the "Get()" function. So if we create a new |
|
variable of the type "*S", we can just call "g()", because "*S" also |
|
implements the empty interface. |
|
|
|
<CODE BEGINS> |
|
s = new(S) |
|
fmt.Println(g(s)); |
|
<CODE ENDS> |
|
|
|
The call to "g" will work fine and will print 0. If we however |
|
invoke "g()" with a value that does not implement "I" we have a |
|
problem: |
|
|
|
<CODE BEGINS> |
|
var i int |
|
fmt.Println(g(i)) |
|
<CODE ENDS> |
|
|
|
This compiles, but when we run this we get slammed with: "panic: |
|
interface conversion: int is not main.I: missing method Get". |
|
|
|
Which is completely true, the built-in type "int" does not have a |
|
"Get()" method. |
|
|
|
8.3. Methods |
|
|
|
Methods are functions that have a receiver (see Section 5). You can |
|
define methods on any type (except on non-local types, this includes |
|
built-in types: the type "int" can not have methods). You can |
|
however make a new integer type with its own methods. For example: |
|
|
|
<CODE BEGINS> |
|
type Foo int |
|
|
|
func (self Foo) Emit() { |
|
fmt.Printf("%v", self) |
|
} |
|
|
|
type Emitter interface { |
|
Emit() |
|
} |
|
<CODE ENDS> |
|
|
|
Doing this on non-local (types defined in other packages) types |
|
yields an error "cannot define new methods on non-local type int". |
|
|
|
8.4. Methods on interface types |
|
|
|
An interface defines a set of methods. A method contains the actual |
|
code. In other words, an interface is the definition and the methods |
|
are the implementation. So a receiver can not be an interface type, |
|
doing so results in a "invalid receiver type ..." compiler error. |
|
The authoritative word from the language spec [go_spec]: |
|
|
|
<blockquote>: |
|
The receiver type must be of the form "T" or "*T" where "T" is a type |
|
name. "T" is called the receiver base type or just base type. The |
|
base type must not be a pointer or interface type and must be |
|
declared in the same package as the method. |
|
|
|
<aside>: |
|
Creating a pointer to an interface value is a useless action in Go. |
|
It is in fact illegal to create a pointer to an interface value. The |
|
release notes for an earlier Go release that made them illegal leave |
|
no room for doubt: |
|
<blockquote>: |
|
The language change is that uses of pointers to interface values no |
|
longer automatically de-reference the pointer. A pointer to an |
|
interface value is more often a beginner's bug than correct code. |
|
|
|
8.5. Interface names |
|
|
|
By convention, one-method interfaces are named by the method name |
|
plus the _-er_ suffix: Read_er_, Writ_er_, Formatt_er_ etc. |
|
|
|
There are a number of such names and it's productive to honor them |
|
and the function names they capture. "Read", "Write", "Close", |
|
"Flush", "String" and so on have canonical signatures and meanings. |
|
To avoid confusion, don't give your method one of those names unless |
|
it has the same signature and meaning. Conversely, if your type |
|
implements a method with the same meaning as a method on a well-known |
|
type, give it the same name and signature; call your string-converter |
|
method "String" not "ToString". |
|
|
|
8.6. A sorting example |
|
|
|
Recall the Bubblesort exercise, where we sorted an array of integers: |
|
|
|
<CODE BEGINS> |
|
func bubblesort(n []int) { |
|
for i := 0; i < len(n)-1; i++ { |
|
for j := i + 1; j < len(n); j++ { |
|
if n[j] < n[i] { |
|
n[i], n[j] = n[j], n[i] |
|
} |
|
} |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
A version that sorts strings is identical except for the signature of |
|
the function: "func bubblesortString(n []string) { /* ... */ }" . |
|
Using this approach would lead to two functions, one for each type. |
|
By using interfaces we can make this more |
|
|
|
<CODE BEGINS> |
|
func sort(i []interface{}) { <1> |
|
switch i.(type) { <2> |
|
case string: <3> |
|
// ... |
|
case int: |
|
// ... |
|
} |
|
return /* ... */ <4> |
|
} |
|
<CODE ENDS> |
|
|
|
Our function will receive a slice of empty interfaces at _1_. We then |
|
_2_ use a type switch to find out what the actual type of the input |
|
is. And then _3_ then sort accordingly. And, when done, return _4_ |
|
the sorted slice. |
|
|
|
But when we call this function with "sort([]int{1, 4, 5})", it fails |
|
with: "cannot use i (type []int) as type []interface { } in function |
|
argument" |
|
|
|
This is because Go can not easily convert to a _slice_ of interfaces. |
|
Just converting to an interface is easy, but to a slice is much more |
|
costly. The full mailing list discussion on this subject can be |
|
found at [go_nuts_interfaces]. To keep a long story short: Go does |
|
not (implicitly) convert slices for you. |
|
|
|
So what is the Go way of creating such a "generic" function? Instead |
|
of doing the type inference ourselves with a type switch, we let Go |
|
do it implicitly: The following steps are required: |
|
|
|
* Define an interface type (called "Sorter" here) with a number of |
|
methods needed for sorting. We will at least need a function to |
|
get the length of the slice, a function to compare two values and |
|
a swap function. |
|
|
|
<CODE BEGINS> |
|
type Sorter interface { |
|
Len() int // len() as a method. |
|
Less(i, j int) bool // p[j] < p[i] as a method. |
|
Swap(i, j int) // p[i], p[j] = p[j], p[i] as a method. |
|
} |
|
<CODE ENDS> |
|
|
|
* Define new types for the slices we want to sort. Note that we |
|
declare slice types: |
|
|
|
<CODE BEGINS> |
|
type Xi []int |
|
type Xs []string |
|
<CODE ENDS> |
|
|
|
* Implementation of the methods of the "Sorter" interface. For |
|
integers: |
|
|
|
<CODE BEGINS> |
|
func (p Xi) Len() int {return len(p)} |
|
func (p Xi) Less(i int, j int) bool {return p[j] < p[i]} |
|
func (p Xi) Swap(i int, j int) {p[i], p[j] = p[j], p[i]} |
|
<CODE ENDS> |
|
|
|
And for strings: |
|
|
|
<CODE BEGINS> |
|
func (p Xs) Len() int {return len(p)} |
|
func (p Xs) Less(i int, j int) bool {return p[j] < p[i]} |
|
func (p Xs) Swap(i int, j int) {p[i], p[j] = p[j], p[i]} |
|
<CODE ENDS> |
|
|
|
* Write a _generic_ Sort function that works on the "Sorter" |
|
interface. |
|
|
|
<CODE BEGINS> |
|
func Sort(x Sorter) { <1> |
|
for i := 0; i < x.Len() - 1; i++ { <2> |
|
for j := i + 1; j < x.Len(); j++ { |
|
if x.Less(i, j) { |
|
x.Swap(i, j) |
|
} |
|
} |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
At _1_ "x" is now of the "Sorter" type and using the defined |
|
methods for this interface we implement Bubblesort at _2_. |
|
|
|
Now we can use our _generic_ "Sort" function as follows: |
|
|
|
<CODE BEGINS> |
|
ints := Xi{44, 67, 3, 17, 89, 10, 73, 9, 14, 8} |
|
strings := Xs{"nut", "ape", "elephant", "zoo", "go"} |
|
|
|
|
|
Sort(ints) |
|
fmt.Printf("%v\n", ints) |
|
Sort(strings) |
|
fmt.Printf("%v\n", strings) |
|
<CODE ENDS> |
|
|
|
8.7. Listing interfaces in interfaces |
|
|
|
Take a look at the following example of an interface definition, this |
|
one is from the package "container/heap": |
|
|
|
<CODE BEGINS> |
|
type Interface interface { |
|
sort.Interface |
|
Push(x interface{}) |
|
Pop() interface{} |
|
} |
|
<CODE ENDS> |
|
|
|
Here another interface is listed inside the definition of |
|
"heap.Interface", this may look odd, but is perfectly valid, remember |
|
that on the surface an interface is nothing more than a listing of |
|
methods. "sort.Interface" is also such a listing, so it is perfectly |
|
legal to include it in the interface. |
|
|
|
8.8. Introspection and reflection |
|
|
|
In the following example we want to look at the "tag" (here named |
|
"namestr") defined in the type definition of "Person". To do this we |
|
need the "reflect"_type_ definition. So we use the "reflect" package |
|
to figure out the type of the variable and _then_ access the tag. |
|
|
|
<CODE BEGINS> |
|
type Person struct { |
|
name string "namestr" |
|
age int |
|
} |
|
|
|
func ShowTag(i interface{}) { <1> |
|
switch t := reflect.TypeOf(i); t.Kind() { |
|
case reflect.Ptr: <2> |
|
tag := t.Elem().Field(0).Tag |
|
// <<3>> <<4>> <<5>> |
|
<CODE ENDS> |
|
|
|
Figure 2: Introspection using reflection. |
|
|
|
We are calling "ShowTag" at _1_ with a "*Person", so at _2_ we're |
|
expecting a "reflect.Ptr". We are dealing with a "Type" _3_ and |
|
according to the documentation : |
|
|
|
<blockquote>: |
|
Elem returns a type's element type. It panics if the type's Kind is |
|
not Array, Chan, Map, Ptr, or Slice. |
|
|
|
So on "t" we use "Elem()" to get the value the pointer points to. We |
|
have now dereferenced the pointer and are "inside" our structure. We |
|
then _4_ use "Field(0)" to access the zeroth field. |
|
|
|
The struct "StructField" has a "Tag" member which returns the tag- |
|
name as a string. So on the "0^{th}" field we can unleash ".Tag" _5_ |
|
to access this name: "Field(0).Tag". This gives us "namestr". |
|
|
|
To make the difference between types and values more clear, take a |
|
look at the following code: |
|
|
|
<CODE BEGINS> |
|
func show(i interface{}) { |
|
switch t := i.(type) { |
|
case *Person: |
|
t := reflect.TypeOf(i) <1> |
|
v := reflect.ValueOf(i) <2> |
|
tag := t.Elem().Field(0).Tag <3> |
|
name := v.Elem().Field(0).String() <4> |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
Figure 3: Reflection and the type and value. |
|
|
|
At _1_ we create "t" the type data of "i", and "v" gets the actual |
|
values at _2_. Here at _3_ we want to get to the "tag". So we need |
|
"Elem()" to redirect the pointer, access the first field and get the |
|
tag. Note that we operate on "t" a "reflect.Type". Now _4_ we want |
|
to get access to the _value_ of one of the members and we employ |
|
"Elem()" on "v" to do the redirection. we have "arrived" at the |
|
structure. Then we go to the first field "Field(0)" and invoke the |
|
"String()" method on it. |
|
|
|
Peeling away the layers using reflection. |
|
|
|
Setting a value works similarly as getting a value, but only works on |
|
_exported_ members. Again some code: |
|
|
|
<CODE BEGINS> |
|
type Person struct { |
|
name string |
|
age int |
|
} |
|
|
|
func Set(i interface{}) { |
|
switch i.(type) { |
|
case *Person: |
|
r := reflect.ValueOf(i) |
|
r.Elem(0).Field(0).SetString("Albert Einstein") |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
Figure 4: Reflect with _private_ member. |
|
|
|
<CODE BEGINS> |
|
type Person struct { |
|
Name string |
|
age int |
|
} |
|
|
|
func Set(i interface{}) { |
|
switch i.(type) { |
|
case *Person: |
|
r := reflect.ValueOf(i) |
|
r.Elem().Field(0).SetString("Albert Einstein") |
|
} |
|
} |
|
<CODE ENDS> |
|
|
|
Figure 5: Reflect with _public_ member. |
|
|
|
The first program compiles and runs, but when you run it, you are |
|
greeted with a stack trace and a _run time_ error: "panic: |
|
reflect.Value.SetString using value obtained using unexported field". |
|
|
|
The second program works OK and sets the member "Name" to "Albert |
|
Einstein". Of course this only works when you call "Set()" with a |
|
pointer argument. |
|
|
|
8.9. Exercises |
|
|
|
### Interfaces and max() |
|
|
|
In the maximum exercise we created a max function that works on a |
|
slice of integers. The question now is to create a program that |
|
shows the maximum number and that works for both integers and floats. |
|
Try to make your program as generic as possible, although that is |
|
quite difficult in this case. |
|
|
|
8.9.1. Answer |
|
|
|
The following program calculates a maximum. It is as generic as you |
|
can get with Go. |
|
|
|
<CODE BEGINS> |
|
package main |
|
|
|
import "fmt" |
|
|
|
func Less(l, r interface{}) bool { <1> |
|
switch l.(type) { |
|
case int: |
|
if _, ok := r.(int); ok { |
|
|