363 75 2MB
English Pages 240 [453] Year 2021
Hands-on Go Programming
Learn Google’s Golang Programming, Data Structures, Error Handling and Concurrency
Sachchidanand Singh
Prithvipal Singh
www.bpbonline.com
FIRST EDITION 2021 Copyright © BPB Publications, India ISBN: 978-93-89898-19-4
All Rights Reserved. No part of this publication may be reproduced, distributed or transmitted in any form or by any means or stored in a database or retrieval system, without the prior written permission of the publisher with the exception to the program listings which may be entered, stored and executed in a computer system, but they can not be reproduced by the means of publication, photocopy, recording, or by any electronic and mechanical means.
LIMITS OF LIABILITY AND DISCLAIMER OF WARRANTY
The information contained in this book is true to correct and the best of author’s and publisher’s knowledge. The author has made every effort to ensure the accuracy of these publications, but publisher cannot be held responsible for any loss or damage arising from any information in this book.
All trademarks referred to in the book are acknowledged as properties of their respective owners but BPB Publications cannot guarantee the accuracy of this information.
Distributors:
BPB PUBLICATIONS
20, Ansari Road, Darya Ganj
New Delhi-110002
Ph: 23254990/23254991
MICRO MEDIA Shop No. 5, Mahendra Chambers,
150 DN Rd. Next to Capital Cinema, V.T. (C.S.T.) Station, MUMBAI-400 001
Ph: 22078296/22078297 DECCAN AGENCIES 4-3-329, Bank Street,
Hyderabad-500195 Ph: 24756967/24756400
BPB BOOK CENTRE
376 Old Lajpat Rai Market, Delhi-110006
Ph: 23861747
Published by Manish Jain for BPB Publications, 20 Ansari Road, Darya Ganj, New Delhi-110002 and Printed by him at Repro India Ltd, Mumbai
www.bpbonline.com
Dedicated to
My Parents, Wife Nirmala & Loving Daughter Anvi and Son Vansh
— Sachchidanand Singh
My Parents, Wife Rajani & Loving son Parthiv — Prithvipal Singh
About the Authors
Mr. Sachchidanand Singh is working as Advanced Analytics, BI and Data Science SME at IBM India Software Labs (ISL), Pune. He is M.Tech from the Birla Institute of Technology and Science (BITS), Pilani. He has authored more than a dozen technical research papers in IEEE, international computer journals, and national/international conferences.
He holds several patents in Artificial Intelligence, Machine Learning, Cloud and Cognitive domain. He has rich experience in architecture design and solution implementation with technologies like Advanced Analytics and Business Intelligence (BI). He is an IEEE reviewer, Technical Program Committee (TPC) member of various national/international conferences, and a review board member of the American Journal of Computer Science and Information Engineering, USA.
Mr. Prithvipal Singh has been working in the IT industry for nearly a decade. He has vast experience working in Java, Golang, Spring, Node.js, and Python. He has expertise in microservice architecture and the cloud domain. He has an MCA from Savitribai Phule Pune University.
About the Reviewer
Dolly Talreja is currently working as the technical lead and core member of a team developing 5G Core network (Golang Linux) and is an integral part of Altran Technologies. In her capacity, she is the owner of the nodes UDM and AUSF and is involved in completing SDLC. She is also an expert in analyzing network traces. She is part of a team that is at the forefront of innovation to address the entire breadth of the clients’ opportunities in the evolving world of cloud and digital platforms. She is the alumni of NIT Warangal, with more than 6 years of experience in the industry, was the Lead Engineer at HP and played a key role in the development of LEDM. She is the owner of the Scan to Feature of HP. She was also part of the Samsung R&D Institute of India as the Senior Software Engineer and was part of a team developing SMB 3.0 protocol for Samsung’s MPF. She also has to her credit a published paper in IEEE on decentralized peer-to-peer networks.
Acknowledgements
At this movement of my substantial enhancement, before we get into the thick of the things, we would like to add a few heartfelt words for the people who gave their unending support with their unfair humor and warm wishes. First and foremost, praises and thanks to the God, the Almighty, for his showers of blessings throughout in completing this book successfully.
We want to acknowledge our colleagues who provided us with the impetus to write a more suitable text. We are thankful to the management, seniors and colleagues for constantly keeping on pushing us to move higher and higher. Besides them, we thank all our friends, well-wishers, respondents, and academicians who helped us throughout our journey from the inception to the completion.
Preface
Golang is regarded as a swift and flawless programming language having loads of useful features like efficient built-in concurrency, garbage collector with automatic garbage detection and cleaning, dynamic growable call stack of goroutines, extremely fast compiler and cross platform versatility, etc. A lot of cloud, networking, and DevOps softwares are written in Golang like Docker, Kubernetes, Terraform, etcd, and ist.io. The Go developers community is one of the most active open source communities on GitHub. This book focuses on learning Golang, starting from the basic concepts to interfaces, pointers, concurrency, etc. We have explained each concept in detail using programming examples. We start with the basics and get into more complex stuff with each chapter. Each chapter has exercises which will help you in understanding the concepts clearly.
Over the 12 chapters in this book, you will learn the following:
Chapter 1: [Introduction]
This chapter covers the basic concepts of data types, constants, variables, operators, reassignment, and redeclaration. You will learn how to use them in the Go programming language.
Chapter 2: [Functions]
This chapter will cover the function declaration, parameters, multiple returns, variadic function, and defer statement. It will also cover the concept of call by value and how to pass the address of the variable as a value.
Chapter 3: [Control flows]
The objective of this chapter is to introduce the concepts of control flows and loops to the readers and show how to use them.
Chapter 4: [Arrays] The objective of this chapter is to introduce the concept of arrays to the readers. It will also show how to declare and initialize arrays, and pass arrays to function.
Chapter 5: [Slices] The objective of this chapter is to introduce the concept of slices to the readers and tell them how to declare slices, the various ways of creating slices, modifying and comparing slices, about multi-dimensional slices, sorting slices, and iterate over slices.
Chapter 6: [Maps] This chapter explains the basic concept of map and how to use it in the Go programming language. It covers how to declare and
initialize a map, iterate over a map, how to perform retrieve, update, and delete over the map, and how to check if a key exists in a map, etc.
Chapter 7: [Structs] This chapter explains the basic concept of struct and how to use it in the Go programming language. It covers how to declare and initialize structs type, access fields of structs, pointer to structs, exported and unexported structs, and structs fields. Chapter 8: [Methods]
This chapter covers the concept of methods in the Go programming language. You will learn how to declare a method, how to call a method, the syntax of different types of methods, and the method resolution process. Chapter 9: [Interfaces]
This chapter will cover the concept of interface in Golang. You will learn what the interface in Golang is, how Golang interfaces are different from other languages, how to declare an interface, and how implicit implementation happens in Golang and what are its benefits. We will create the interface without methods and see its benefits with built-in functions. We will see the impact of value receiver and pointer receiver while implementing the interface and creating polymorphic objects.
Chapter 10: [Pointers] In this chapter, you will learn what pointers in Golang are. This chapter will explain to you the use of pointers, how to declare a pointer variable, and you will understand the pointer types. You will also learn why arithmetic operations are not allowed on pointer variables in Golang. Chapter 11: [Concurrency] This chapter will cover goroutines and channels. You will learn how to define a goroutine, what WaitGroup the WaitGroup to wait for other goroutines understand the concept of channel. We will communication between the goroutines. We of goroutines: buffered and unbuffered.
is, and how to use to finish. You will use the channel for will create two types
Chapter 12: [Error handling]
This chapter will cover error handling. At the end of this chapter, you will learn what an error in Golang is, how to return an error from a function or method, and how to handle these errors in the caller function. We will also see a built-in error interface, the error packages and the method, function, and struct in the errors package. You will learn how to define a new error, create a customized error type, what panic is, how to handle panic using recover, what defer is, and the importance of the defer function while handling panic, etc.
Downloading the coloured images:
Please follow the link to download the Coloured Images of the book: https://rebrand.ly/xqrsibm
Errata
We take immense pride in our work at BPB Publications and follow best practices to ensure the accuracy of our content to provide with an indulging reading experience to our subscribers. Our readers are our mirrors, and we use their inputs to reflect and improve upon human errors, if any, that may have occurred during the publishing processes involved. To let us maintain the quality and help us reach out to any readers who might be having difficulties due to any unforeseen errors, please write to us at :
[email protected]
Your support, suggestions and feedbacks are highly appreciated by the BPB Publications’ Family.
Did you know that BPB offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.bpbonline.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at [email protected] for more details.
At you can also read a collection of free technical articles, sign up for a range of free newsletters, and receive exclusive discounts and offers on BPB books and eBooks.
BPB is searching for authors like you
If you're interested in becoming an author for BPB, please visit www.bpbonline.com and apply today. We have worked with thousands of developers and tech professionals, just like you, to help them share their insight with the global tech community. You can make a general application, apply for a specific hot topic that we are recruiting an author for, or submit your own idea.
The code bundle for the book is also hosted on GitHub at In case there's an update to the code, it will be updated on the existing GitHub repository.
We also have other code bundles from our rich catalog of books and videos available at Check them out!
PIRACY
If you come across any illegal copies of our works in any form on the internet, we would be grateful if you would provide us with the location address or website name. Please contact us at [email protected] with a link to the material. If you are interested in becoming an author
If there is a topic that you have expertise in, and you are interested in either writing or contributing to a book, please visit
REVIEWS
Please leave a review. Once you have read and used this book, why not leave a review on the site that you purchased it from? Potential readers can then see and use your unbiased opinion to make purchase decisions, we at BPB can understand what you think about our products, and our authors can see your feedback on their book. Thank you! For more information about BPB, please visit
Table of Contents
1. Introduction Structure Objective Introduction Why Go programming? 1.1 Data types 1.1.1 Numeric types 1.1.2 String types 1.1.3 Boolean types 1.1.4 Derived types 1.2 Constants 1.2.1 Variable declaration 1.2.2 Short variable declaration 1.3 Operator 1.3.1 Arithmetic operators 1.3.2 Relational operators 1.3.3 Logical operators 1.3.4 Bitwise operators 1.3.5 Assignment operators 1.4 Typed constants and untyped constants 1.4.1 Typed constants 1.4.2 Untyped constants 1.5 Multiple constant declarations 1.6 Redeclaration concept 1.7 Reassignment concept 1.8 Code structure Conclusion Questions
2. Functions Structure Objective 2.1 Function 2.1.1 Function declarations 2.2 Parameters and returns 2.2.1 Parameters 2.2.2 Returns 2.3 Multiple returns and named return 2.3.1 Multiple returns 2.3.2 Named returns 2.4 Call by value 2.4.1 Call by value 2.5 Variadic functions 2.6 Defer 2.6.1 Defer Conclusion Questions
3. Control flows Structure Objective 3.1 Decision-making 3.1.1 If statement 3.1.2 if…else statement 3.1.3 if…else if…else statement 3.1.4 switch…case 3.2 Loops 3.2.1 For loops
3.2.2 Nested for loops
3.2.3 Loop control statements 3.2.3.1 Break statements 3.2.3.2 Goto statements 3.2.3.3 Continue statements 3.2.3.4 Infinite loop 3.2.3.5 Fallthrough Conclusion Questions 4. Arrays Structure Objective 4.1 Arrays 4.2 Array declaration 4.2.1 Using var keyword 4.2.2 Shorthand declaration 4.3 Array initialization 4.4 2D and 3D array 4.5 Array iteration 4.6 Passing arrays to functions Conclusion Questions
5. Slices Structure Objective 5.1 Concept of slices 5.2 Declaration of slice 5.3 Creating a slice
5.3.1 Creating a slice using a slice literal 5.3.2 Creating a slice from an array 5.3.3 Creating a slice from another slice 5.3.4 Create slice using make() function 5.4 5.5 5.6 5.7
Modifying a Slice Zero-value slice Comparison of slice Multi-dimensional Slice
5.8 Sorting of slice 5.9 Iterate over a slice 5.9.1 Iterate over a slice using for loop 5.9.2 Iterate over a slice using a range in for loop Conclusion Questions 6. Maps Structure Objective 6.1 What are Go maps? 6.2 Declaration of Go maps 6.3 Initialization of Go Maps 6.3.1 Initializing maps using map literal 6.3.2 Initializing a map using make() function 6.4 Iterating over Go maps 6.5 Adding key-value pairs in maps 6.6 Updating key-value pairs in maps 6.7 Retrieving the value of a key in maps 6.8 Checking if a key exists in Go maps 6.9 Deletion of a key from maps 6.10 Maps are reference types Conclusion
Questions 7. Structs Structure Objective 7.1 7.2 7.3 7.4
What are structs? Declaring and initializing structs type Zero value of structs Access fields of structs
7.5 Pointer to structs 7.5.1 Updating value of struct member using pointer 7.6 Exported vs. unexported structs and struct fields 7.7 Structs are value types 7.8 Struct equality Conclusion Questions 8. Methods Structure Objective 8.1 Definition 8.2 Value receiver and pointer receiver 8.3 Methods of different types 8.3.1 Method 8.3.2 Method 8.3.3 Method 8.4 Methods
on on on of
the basic type composite types the function type embedded type
8.4.1 Method resolution process 8.4.2 Polymorphism is not allowed Conclusion
Questions
9. Interfaces Structure Objective 9.1 Introduction 9.2 Implementing an interface 9.2.1 Why implicit implementation? 9.2.2 Polymorphism: Interface as a contract 9.3 Empty interface 9.4 Method set Conclusion of method set: 9.5 Frequently used built-in/stdlib interfaces 9.5.1 Stringer interface 9.5.2 Interface interface of sort package 9.6 Interface with struct 9.6.1 Interface variable as struct field 9.6.2 Embedding interface into the struct Conclusion Questions 10. Pointers Structure Objective 10.1 Declaration 10.2 Pointer type 10.3 Operators with the pointer 10.3.1 Relational operator 10.3.2 Arithmetic operator 10.4 Pointers on composite types
10.4.1 Pointer to array 10.4.1.1 Declaration 10.4.1.2 Pointer array as a function argument 10.4.2 Pointer to slice 10.4.3 Pointer to struct Conclusion Questions 11. Concurrency Structure Objectives 11.1 Goroutines 11.1.1 Introduction 11.1.2 WaitGroup 11.1.3 Mutex Lock 11.2 Some important functions 11.2.1 The GOMAXPROCS functions 11.2.2 The Goexit function 11.3 Channels 11.3.1 Introduction 11.3.2 Operations on channel 11.3.2.1 Closing the channel 11.3.2.2 Iterating over the channel 11.3.3 Types of channels 11.3.3.1 Unbuffered channel 11.3.3.2 Buffered channel 11.4 Read-only and send-only channel 11.5 Select statement 11.6 Channels in the time package 11.6.1 Ticker 11.6.2 Timer
Conclusion Questions 12. Error handling Structure Objective 12.1 The error 12.1.1 The error interface 12.1.2 User-defined custom error 12.2 Panic 12.2.1 Panic and defer 12.3 Recover 12.4 Best practice Conclusion Questions Index
CHAPTER 1 Introduction
The Go programming language was conceived by Robert Griesemer, Rob Pike, and Ken Thompson in 2007 at Google. It’s an open-source, general-purpose programming language that supports high-performance networking and multiprocessing. The Go compiler was initially written in C but it is now written in Go itself.
Structure
Data types Constants, variables, and operators
Typed constants and untyped constants
Multiple constant declarations Redeclaration concept
Reassignment concept
Code structure
Objective
This chapter covers the basic concepts of data types, constants, variables, operators, reassignment, and redeclaration. You will learn how to use them in the Go programming language.
Introduction
Go is a systems-level programming language for large distributed systems and highly scalable network servers. It’s well-suited for building infrastructures like networked servers and tools and is suitable for cloud, mobile applications, machine learning, etc. Go provides efficient concurrency, flexible approach to data abstraction, and supports automatic memory management, i.e., garbage collection.
Why Go programming?
Multithreading is supported by most of the programming languages, but race conditions and deadlocks create difficulties in creating the multithreaded application. For example, creating a new thread in Java consumes approximately 1 MB of the memory heap size. Now, consider a case wherein you need to spin thousands of such threads. Then, it will create out of memory.
Moreover, there is a limit to the number of cores you can add to the processors like quad-core and octa-core to increase processing power. You cannot keep on adding more cache to the processor in order to boost performance since there is a cost involved. Therefore, we are left with only one option to build a more efficient software having high performance.
Go provides a solution to the problem with goroutines. You can spin millions of them at a time since they consume ~2KB heap memory. Goroutines have faster startup time and they use more memory on a need basis only. Also, a single goroutine can run on multiple threads since goroutines and OS threads don’t have 1:1 mapping.
1.1 Data types
Data types categorize a set of related values and describe the operations that can be performed.
1.1.1 Numeric types
They are arithmetic types and represent either integer types or floating-point values.
Integer types:
types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types:
Table 1.1
Float types:
types: types: types: types: types: types: types: types:
types: types: types: types: types: types: types: types: types: types: types: types: types: types: types: types:
Table 1.2
1.1.2 String types
Strings are immutable types. This means that once created, you can’t change the contents of a string. Go supports two styles of string literals: the double-quote style and the back-quote style.
String literals can be created using double quotes, "Go Programming" or backticks, 'Go With regular double-quoted strings, the special sequences like newlines are interpreted as actual newlines while escape sequences are ignored in the backtick character and treated as normal values. For example, \n gets replaced with a newline in double-quoted strings as shown below:
Program 1.1 //Go program showing newline sequence
package main
import "fmt"
func main() { // newline sequence is treated as a special value x := "apple\norange" fmt.Println(x)
// newline sequence is treated as two raw characters
y := ‘apple\norange’ fmt.Println(y) }
The following will be the output for the above program:
apple orange apple\norange
1.1.3 Boolean types
Program 1.2 //Go program to explain boolen types
package main
import "fmt" func main() { var b bool fmt.Println(b) b = true fmt.Println(b) }
The following will be the output for the above program:
false true
1.1.4 Derived types
The derived type may include structure types, function types, slice types, interface types, map types, and channel types.
1.2 Constants
In Go, const is a keyword that introduces a name for a scalar value like 3.14159. Such values are called constants.
Type When a variable is declared without specifying an explicit type (either by using the := syntax or the var = expression syntax), the variable’s type is inferred from the value on the righthand side.
Constants are declared with the const keyword and can be a numeric, string, Boolean, or character values.
Constants cannot be declared using the := syntax
E.g.: const Pi = 3.14
1.2.1 Variable declaration
Variables declared without an explicit initial value are assigned the default zero value for numeric types, false for boolean types, and "" (the empty string) for string types. Let’s look at the following program:
Program 1.3 //Go program showing variables declared //without an explicit initial value package main
import "fmt"
func main() { var f float64 var i int var b bool var s string fmt.Printf("%v %v %v %q\n", f, i, b, s) } The following will be the output for the above program:
0 0 false ""
In Go, the type of a variable is specified after the variable name.
If you want to put two (or more) statements on one line, they must be separated with a semicolon
When declaring a variable, it is assigned the natural null value for the type.
This means that here, after var i i has a value of 0.
With var s s is assigned the zero string which is
Compare the following pieces of code which have the same effect:
Declaration with = var a int = 20 var b bool = true The var keyword is used to declare a variable and then assign a value to it.
Declaration with := You can drop var and data-type in this syntax.
a := 20 b := true When Go finds the := assignment syntax, it understands that a new variable needs to be declared with an initial value. But you cannot use this syntax to assign a value to a pre-defined variable. The variable type is deduced from the value. For example, a value of 20 indicates an int and a value of true tells Go that the type should be Boolean.
1.2.2 Short variable declaration
In Go, you can declare variables in the following two ways: Using var keyword
Using a short declaration operator
The short variable declaration operator is used to declare and initialize the local variables inside a function. It narrows the scope of the variable.
Using a short declaration operator, variables are declared without specifying the type. The type of variable is determined by the type of expression on the right-hand side of the := operator.
Syntax of short variable declaration operator:
variable_name := expression or value
Examples: a := 10 x, y := 100, 200 p, q, r := 150, 250, 300
Short variable declaration:
It is used to declare and initialize variables inside a function only.
The variable declaration and initialization are made at the same time.
Variables are declared inside the function, hence they have local scope.
It is not required to mention the type of the variable in the short variable declaration.
1.3 Operator
Operators are used to performing the given mathematical or logical calculations. The following built-in operators are supported by Golang:
Arithmetic operators
Relational operators Logical operators
Bitwise operators
Assignment operators
Miscellaneous operators
1.3.1 Arithmetic operators
Refer to the following table:
table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table:
Table 1.3
1.3.2 Relational operators
Refer to the following table:
table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table:
Table 1.4
1.3.3 Logical operators
Refer to the following table:
table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table: table:
Table 1.5
Program 1.4 // Go program to explain logical operators
package main
import "fmt"
func main() { var a int = 10 var b int = 20
// logical AND operators if a != b && a 1 = 256 512 >> 2 = 128 512 >> 3 = 64 512 >> 4 = 32 Program 1.7 /* Go program to explain bitwise AND assignment operator,
bitwise exclusive OR and assignment operator and bitwise inclusive OR and assignment operator */ package main import "fmt"
func main() { var a, b, c int //default value of a, b, c is 0 a &= 2 //Its same as a = a & 2, here & is Binary AND Operator fmt.Printf("Example of &= Operator: value of a = %d\n", a)
b ^= 2 //Its same as b = b ^ 2, here ^ is Binary XOR Operator fmt.Printf("Example of ^= Operator: value of b = %d\n", b) c |= 2 //Its same as c = c | 2, here | is Binary OR Operator fmt.Printf("Example of |= Operator: value of c = %d\n", c) }
We will get the following output: Example of &= Operator: value of a = 0 Example of ^= Operator: value of b = 2 Example of |= Operator: value of c = 2
1.4 Typed constants and untyped constants
The type of each variable in Go is inferred by the compiler at the compile time. Therefore, Go is called a statically typed programming language. Go doesn’t allow performing operations that mix numeric types. For example, you cannot add an int variable to int64 variable as shown below:
var x int = 100 var y int64 = 200 var sum = x + y // It will give compiler error
The above operation will give compiler error as you are trying to add int variable with the int64 variable. You need to explicitly cast variables so that they are of the same type to perform an operation like addition, subtraction, etc. as shown below:
var a int = 10 var b int64 = 20
var sum = a + int(b) // It’s allowed and works
1.4.1 Typed constants
In Go, any constant is typed if its type is explicitly mentioned. For example:
const x float64 = 9.75 //It’s types constant const x int = 10 //It’s types constant
1.4.2 Untyped constants
In Go, any constant (named or unnamed) is untyped if its type is explicitly not mentioned. For example:
20 // untyped integer constant "Golang" // untyped string constant false // untyped boolean constant
1.5 Multiple constant declarations
We can declare multiple constants in one statement and multiple variables of a single type in a single statement. For example:
const x, y, z int = 10, 20, 30
1.6 Redeclaration concept
If a variable is already declared, you cannot redeclare that variable in the same block. The redeclaration just assigns a new value to the original value as shown below:
Program 1.8 //Go redeclaration concept package main
import "fmt"
func main() { a := 10 a := 20 fmt.Println(a) }
We will get the following output:
no new variables on left side of := You can redeclare variables using short multi-variable declarations where at least one new variable is introduced as shown below:
Program 1.9 //Go program for short multi-variable declarations
package main
import "fmt"
func main() { a := 10 a, b := 20, 30 fmt.Println("value of a:", a) fmt.Println("value of b:", b) }
We will get the following output:
value of a: 20 value of b: 30
1.7 Reassignment concept
You can perform reassignment of variables in Golang. The set of values allowed to store in a variable are determined by the variable’s type. The rule which is used to govern the permitted values is called assignability. Let’s look at the following example:
Program 1.10 //Go reassignment concept package main
import "fmt"
func main() { x := 10 { fmt.Println("value of x:", //new local variable x x, y := 20, 30 fmt.Println("value of x:", fmt.Println("value of y:", } fmt.Println("value of x:", }
x)
x) y) x)
We will get the following output:
value value value value
of of of of
x: x: y: x:
10 20 30 10
In Program variable x has been shadowed due to y := The value of outside the inner scope, remained the same, i.e., 10 and inside the scope, it was 20 after it was shadowed.
1.8 Code structure
Go programs are constructed using several packages for efficient management of the dependencies. The following is a sample Go program with the print statement:
Program 1.11 //Sample Go program package main
import "fmt"
func main() { fmt.Println("Let us GO") }
To run the program, put the code in a file having the name suffix with something like and then hit the following command.
$ go run first-program.go Output 1.6 Let us GO
Conclusion
Golang is an open-source, general-purpose programming language that supports high-performance networking and multiprocessing, etc.
Go is well-suited for building infrastructures like networked servers and tools, and is also suitable for cloud, mobile applications, and machine learning. Goroutines have faster startup time and they use extra memory on a need basis only. Also, a single goroutine can run on multiple threads since goroutines and OS threads don’t have 1:1 mapping.
Go supports data types like numeric types, string types, boolean types, derived types, etc.
Golang provides built-in relational operators, arithmetic operators, logical operators, bitwise operators, assignment operators, etc.
The type of every variable in Go is inferred by the compiler at compile time. Hence, Go is called a statically typed programming language.
You can redeclare variables using short multi-variable declarations where at least one new variable is introduced.
Go programs are constructed using several packages for efficient management of dependencies.
Questions
What is Golang and what are its benefits? How can we print the type of a variable in Golang?
What are Goroutines? Explain the benefits of using them.
What is the static type declaration of a variable in Golang? What are typed constants and untyped constants?
What is the difference between = and := operator?
What is a string literal in Golang?
What are packages in Golang?
What is the default value of a local variable in Golang?
What is the static type variable declaration in Golang? How do we find the length of a string in Golang?
What is the string data type in Golang? Can you change a specific character in a string?
CHAPTER 2 Functions
Functions in Go are a set of statements which perform a task together. Every Go program will have one Functions help you to divide big tasks into smaller pieces of code and improve readability and maintainability of the program.
Structure
Function declaration Parameters and returns
Multiple returns and named return
Call by value Variadic functions
Defer
Programming examples
Objective
This chapter will cover function declaration, parameters, multiple returns, the variadic function, and defer statement. It will also cover the concept of call by value and how to pass address of the variable as a value. You will learn how to use them in the Go programming language.
2.1 Function
A function is an independent section of code that maps input parameters to output parameters. It is a collection of statements that are used to perform a specific task and return the result to the caller.
Functions are the building blocks of a Go program. They have inputs, outputs, and a series of steps called statements that are executed in order.
2.1.1 Function declarations
A function declaration consists of the keyword the name of the function, a parameter list (empty for main), a result list (also empty here), and the body of the function—the statements that define what it does—enclosed in braces.
Function declaration has a name, a list of parameters, an optional list of results, and a body as shown below: func name (parameter-list)(result-list){ //body of the function }
2.2 Parameters and returns
The function definition in Go programming language is as follows: func function_name([parameter_list]) [return_types] { //Body of function }
2.2.1 Parameters
When a function is invoked, you pass a value to the parameter. A parameter is like a placeholder. This value is referred to as an actual parameter or argument. The parameter list refers to the type, order, and the number of the parameters of a function. Parameters are optional in functions. Hence, a function may contain no parameters.
2.2.2 Returns
A function may return a list of values. The return_types is the list of data types of the values that the function returns. Some functions perform the desired operations without returning a value. In this case, the return_type is not required.
Here is a sample Go program to explain parameters and returns: Program 2.1 // Go program to explain parameters and returns
package main
import "fmt"
func main() { /* declare local variables*/ var x int = 50 var y int = 40 var sum_value int /* calling a function to get sum of values */ sum_value = sum(x, y)
fmt.Printf("Sum value is: %d\n", sum_value)
}
/* function returns the sum of two numbers */ func sum(num1, num2 int) int { /* declare local variables */ var result int result = num1 + num2 return result }
The following will be the output for the above program: Sum value is: 90
While creating a Go function, you define the task that the function will perform. To use a function, you will have to call that function to perform the predefined task. When a program calls a function, the program control is transferred to the called function. A called function performs a defined task, and when its return statement is executed or when its function-ending closing brace is reached, it returns the program control to the main program. To call a function, you simply need to pass the required parameters along with its function name. If the function returns a value, then you can store the returned value.
2.3 Multiple returns and named return
Golang allows giving names to the return or result parameters of the functions in the function definition. Also, Go has built-in support for multiple return values. This feature is used to return both result and error values from a function.
2.3.1 Multiple returns
In Go, a function can return multiple values using the return statement. The type of return values depends on the type of the parameters defined in the parameter list as shown below:
Program 2.2 // Go program to explain multiple returns package main
import "fmt"
// testfunc return 2 values of int type func testfunc(x, y int) (int, int) { return x + y, x - y }
func main() { // Return values are assigned into different variables var testvar1, testvar2 = testfunc(10, 20) fmt.Printf("addition result: %d", testvar1) fmt.Printf("\nsubtraction result: %d", testvar2) }
The following will be the output for the above program:
addition result: 30 subtraction result: -10
2.3.2 Named returns
Golang allows giving names to the return parameters of the functions in the function definition, i.e., explicit naming of return variables in the function definition. It eliminates the need to mention the variable’s name with the return statement.
This concept is generally used when a function needs to return multiple values. Golang provides this facility for the user’s comfort and to enhance the code readability. Syntax for named returns:
func func_name(Par-list)(result_par1 data_type, result_par2 data_type, ….){ // function body
return }
Let’s see an example: Program 2.3 // Golang program to show the use of named return arguments
package main
import "fmt"
// Main Method func main() {
// calling the function which returns one values x := sum(10, 20)
fmt.Println("10 + 20 =", x) } // function with named arguments func sum(a, b int) (add int) { add = a + b
// return keyword without any resultant parameters return } The following will be the output for the above program: 10 + 20 = 30
2.4 Call by value
Golang supports call by value to pass arguments to the function. Go does not have a call by reference since it doesn’t have reference variables. However, we can pass the address of the variable as a value. Golang uses, by default, the call by value way to pass the arguments to the function.
2.4.1 Call by value
The call by value method of passing arguments to a function copies the actual value of an argument into the formal parameter of the function. The values of the actual parameters are copied to the function’s formal parameters, and the two types of parameters are stored in different memory locations. The changes made to the parameter inside the function do not affect the argument, i.e., the changes made inside the functions are not reflected in the actual parameters of the caller: Program 2.4 // Go program to explain call by value package main
import "fmt"
// function to change the value of given variable func replace(x int) { x = 20 }
// main function func main() {
var x int = 10 fmt.Printf("value of x before function call = %d", x)
// call by value replace(x) fmt.Printf("\nvalue of x after function call = %d", x) }
The following will be the output for the above program:
value of x before function call = 10 value of x after function call = 10
As discussed earlier, Go does not have a call by reference since it doesn’t have reference variables. However, we can pass the address of the variable as a value. Here, we will use the concept of pointers and dereference operators. The address operator, & is used to get the address of a variable of any data type and the dereference operator, * is used to access the value at an address. Since the actual and formal parameters refer to the same locations, any changes made inside the function are reflected in the actual parameters of the caller. This is shown below: Program 2.5 // Go program to explain call by passing the address of the variable
package main
import "fmt"
// function to change the value of given variable func replace(x *int) { *x = 20 }
// main function func main() { var x int = 10 fmt.Printf("value of x before function call = %d", x) // call by reference replace(&x) fmt.Printf("\nvalue of x after function call = %d", x) } The following will be the output for the above program:
value of x before function call = 10 value of x after function call = 20
2.5 Variadic functions
Functions, in general, accept only a fixed number of arguments but the variadic function can accept a variable number of arguments.
Only the last parameter of a function can be variadic. If the last parameter of a function definition is prefixed by an ellipsis then the function can accept any number of arguments for that parameter. This is shown as follows: func variadic_function(a int, b …int) { // Body of variadic function }
In the above function, the parameter b is variadic since it’s prefixed by an ellipsis, and it can accept any number of arguments.
Let’s make the first parameter of the variadic_function variadic. The syntax will look like this:
func variadic_function(a …int, b int) { // Body of variadic function }
The above variadic_function will fail to compile with an error syntax error:
cannot use … with non-final parameter a
In the above function, it is not possible to pass arguments to the parameter b because whatever argument we pass will be assigned to the first parameter a since it is variadic. Hence, variadic parameters can only be present in last in the function definition. The way variadic functions work is by converting the variable number of arguments to a slice of the type of the variadic parameter. You will learn the concept of slices in Chapter 5: Slices in detail:
Program 2.6 // Go program to explain the variadic function package main
import ( "fmt" "strings" ) // Variadic function to join strings func join(element …string) string { return strings.Join(element, "_") }
func main() {
// multiple arguments fmt.Println(join("GO", "language", "book")) } The following will be the output for the above program:
GO_language_book The variadic function increases the readability of your program. It can be used in scenarios such as when the number of parameters is not known. Let’s look at the following program:
Program 2.7 // Go program to explain the variadic function
package main import ( "fmt" "strings" ) // Variadic function to join strings
func join(element …string) string {
return strings.Join(element, "_") } func main() { // pass a slice in variadic function element := []string{"GO", "language", "book"} fmt.Println(join(element…)) } The following will be the output for the above program:
GO_language_book Let’s look at another program: Program 2.8 //Select single argument from all arguments of variadic function package main
import "fmt" func main() { variadicExample("IT", "Finance", "HR", "Recruitement", "Payroll") }
func variadicExample(x …string) { fmt.Println(x[1])
fmt.Println(x[4]) } The following will be the output for the above program: Finance Payroll
2.6 Defer
Go has mechanisms for control flow: etc, but some of the less commonly used ones are deferred, panic, and recover.
2.6.1 Defer
A defer statement postpones the execution of a function and pushes a function call onto a list until the surrounding function returns, either normally or through a panic. Defer is commonly used to simplify functions that perform various clean-up actions as shown below:
Program 2.9 //Program to explain defer package main
import "fmt"
func main() { defer fmt.Println("GO") fmt.Println("Book") }
The following will be the output for the above program: Book GO
Conclusion
A function declaration consists of the keyword the name of the function, a parameter list (empty for main), a result list, and the body of the function—the statements that define what it does— enclosed in braces.
When a function is invoked, you pass a value to the parameter. This value is referred to as an actual parameter or argument. The parameter list refers to the type, order, and the number of the parameters of a function.
When a program calls a function, the program control is transferred to the called function. A called function performs a defined task, and when its return statement is executed or when its function-ending closing brace is reached, it returns the program control to the main program.
In Go, a function can return multiple values using the return statement. The type of return values depends on the type of the parameters defined in the parameter list.
Golang allows giving names to the return parameters of the functions in the function definition, i.e., explicit naming of return variables in the function definition. It eliminates the need to mention the variable’s name with the return statement.
Go does not have a call by reference since it doesn’t have reference variables. However, we can pass the address of the variable as a value. Here, we will use the concept of pointers and dereference operators. The address operator, & is used to get the address of a variable of any data type and the dereference operator, * is used to access the value at an address.
Functions, in general, accept only a fixed number of arguments but the variadic function can accept a variable number of arguments. Only the last parameter of a function can be variadic. If the last parameter of a function definition is prefixed by an ellipsis, then the function can accept any number of arguments for that parameter.
The variadic function increases the readability of your program. It can be used in the scenarios when the number of parameters is not known. A defer statement postpones the execution of a function and pushes a function call onto a list until the surrounding function returns, either normally or through a panic. Defer is commonly used to simplify functions that perform various clean-up actions.
Questions
Write the syntax for creating a function in Golang. What is the difference between multiple returns and named returns? Explain with a Go program.
Write a Go program to explain call by value in Golang. What are variadic functions? Explain scenarios where it can be used.
What is the defer statement in Golang?
CHAPTER 3 Control flows
In Go, the control flow statements are used to break the flow of execution and enable programs to execute code based on certain conditions. This chapter will cover the concept of control flows in Go. It will cover decision-making in Golang using if…else switch… case and fallthrough statements. It will also cover the concept of loops - for loops, nested for loops, loop control statements – etc.
Structure
Decision making (if, if…else, if…else if…else, switch…case) Loops (for loops, nested for loops, loop control statements-break, goto, continue, infinite loop) and fallthrough
Programming examples
Objective
The objective of this chapter is to introduce the concepts of control flows and loops to the readers and cover how to use them in the Go programming language.
3.1 Decision-making
Golang uses control statements to control the execution flow of the program based on certain conditions. These are used to ensure a logical flow during program execution based on changes to the state of a program.
3.1.1 If statement
A statement executes a piece of code if one condition is found true. If the statement looks as it does in C or Java, there is just one difference which is that we need to use {} instead of The variables declared by the statement are only in scope until the end of the if.
Syntax: If condition { // piece of code to be executed when the condition is true }
For example, the program given below will print if the variable a is true.
Program 3.1 // Go program to explain if statement
package main import ( "fmt" )
func main() { var x = "Cricket" a := true if a { fmt.Println(x) } }
The following will be the output for the above program:
Cricket
3.1.2 if…else statement
If the statement executes a block of statements when the condition is true, and if the condition is false, it will move to the else block and execute it.
Syntax:
If condition { // piece of code to be executed if condition is true } else { // piece of code to be executed if condition is false }
The following example will show the output Capital of India if the x is New
Program 3.2 // Go program to explain if …else statement
package main import ( "fmt" )
func main() { x := "New Delhi"
if x == "New Delhi" { fmt.Println("Capital of India") } else { fmt.Println("City is not the capital of India") } }
The following will be the output for the above program: Capital of India
3.1.3 if…else if…else statement
The if…else if…else statement allows for combining multiple if… else statements.
Syntax:
if condition-1 { // piece of code to be executed if condition-1 is true } else if condition-2 { // piece of code to be executed if condition-2 is true } else { // piece code to be executed if both condition1 and condition2 are false }
The example given below will show the following output:
"Capital of Maharashtra" if x is "Mumbai" "City of Maharashtra" if x is "Pune" and "Neither Pune nor Mumbai" if x is other than "Pune" or "Mumbai" Let’s look at the following example:
Program 3.3
// Go program to explain if …else if..else statement
package main
import ( "fmt" )
func main() { x := "Mumbai" if x == "Mumbai" { fmt.Println("Capital of Maharashtra") } else if x == "Pune" {
fmt.Println("City of Maharshtra") } else { fmt.Println("Neither Pune nor Mumbai") } } The above program will show the following output: Capital of Maharashtra
3.1.4 switch…case
A switch statement is a multiway branch statement. It provides an efficient way of transfering the execution to different parts of a code based on the value of the expression. Switch statements express conditionals across many branches. The cases are evaluated from top to bottom, stopping when a case succeeds. If no case matches and there is a default case, its statements are executed. The example given below will display the day of the week based on the value of a switch variable:
Program 3.4 // Go program to explain switch…case statement
package main
import "fmt"
func main() { switch day := 7; day { case 1: fmt.Println("Monday") case 2: fmt.Println("Tuesday") case 3:
fmt.Println("Wednesday") case 4: fmt.Println("Thursday") case 5: fmt.Println("Friday") case 6: fmt.Println("Saturday") case 7:
fmt.Println("Sunday") default: fmt.Println("Invalid") } } The above program will show below output:
Sunday
3.2 Loops
A loop statement is used to execute a block of code repeatedly. For is the only loop available in Go. Go doesn’t have while or do…while loops that are present in other languages like C.
3.2.1 For loops
The for loop is a repetition control structure. It allows you to write a loop that needs to be executed a specific number of times.
Syntax:
for [Initial Statement] ; [Condition] ; [Post Statement] { [Action] }
The example given below will display the sum of the numbers from 1 to 10 using for loop:
Program 3.5 // Go program to explain for loops
package main
import "fmt" func sum for i sum
main() { := 0 := 0; i 1000 }
// Matured User matured := func(user User) bool { return user.age > 35 }
frequestUsers := By(frequent).Filter(users) fmt.Println(frequestUsers)
appreciatedUsers := By(appreciated).Filter(users) fmt.Println(appreciatedUsers) respectedUsers := By(respected).Filter(users) fmt.Println(respectedUsers) maturedUsers := By(matured).Filter(users) fmt.Println(maturedUsers)
} We have created a struct named User with fields and We have a users variable which is a slice of User and has stored five user values into it. By is a function type that accepts the User type as a parameter and returns bool type. The Filter is a method By function type which has a slice of User as a parameter returns a slice of The Filter method returns a filtered user on it is called on the actual value of the By type function.
on the and based It
iterates over s1 (the User slice) and calls the by function in the if condition. The by value within the if condition returns true or false based on its implementation. The main function defines four values for the By function type, i.e., and All these values have their implementation for the By function type. The frequent function returns true if the total number of the visit is more than 100. The appreciated function returns true if the total number of likes are more than The respected function returns true if the total number of followers is more than The matured function returns true of the age is more than
All the function values are converted into the By type before calling the Filter method on these. So, the conclusion is that the functionality of Filter is reused. One filter functionality can be applied to a different type of filter, therefore it is reused. We don’t have to rewrite or duplicate filter functions for different types of conditions.
8.4 Methods of embedded type
Inheritance is the most important pillar of OOPs, but Go does not have an inheritance (IS-A), i.e., there is no parent-child relationship. We can achieve a similar feature by using embedding (HAS-A). We need to embed a type within type without specifying the variable name as shown below:
Program 8.12 package main
import "fmt"
type Person struct { id int firstName string lastName string }
func (p Person) getFullName() string { return p.firstName + " " + p.lastName } type Student struct { Person
marks []float32 }
func (s Student) getTotalMarks() (total float32) { for _, val := range s.marks { total += val } return }
func main() { p := Person{101, "Prithvi", "Singh"}
marks := []float32{10.2, 23.3, 19.5} s := Student{p, marks} fmt.Println(s.getFullName()) fmt.Println(s.getTotalMarks()) } We have declared the Person struct with fields and The Person struct has one method, getFirstName which returns the full name by combining the firstName and We have declared one more struct, i.e., Student with field marks and method The Person is embedded in the Please note here that we have not defined the variable name for the Person in In the main function, we have declared the value s for Student type. Please also note here that we are calling the getFullName() method on the Student’s variable. Like inheritance, it is directly available in Student type. This is called method
We can also call the getFullName() method on the Student type by specifying the s.Person.getFullName()
8.4.1 Method resolution process
The compiler uses the method resolution process to find the method. When a method is called on a type, it first tries to find it in the same type. If it is not found in the same type, it looks into all the embedded types; if none of the embedded types have that method, it tries to find in the embedded of embedded type:
Figure 8.1: Method resolution process
If a method is defined in two embedded types at the same level, then that is ambiguity. So the compiler gives a compilation error:
Figure 8.2: Ambiguity in the method resolution process
In Program the method m1() is defined in B and C types. Both B and C are embedded in It gives a compilation error in main() function when m1() is called on a variable of This is because when the compiler does not find the m1() method in it tried to find in the embedded type, i.e., B and But m1() is defined in both B and Program 8.13 package main
type A struct { B C } type B struct { } func (b B) m1() { } type C struct { } func (c C) m1() { } func main() { a := A{} a.m1() }
The following will be the output for the above program: Error 8.2: ./main.go:20:3: ambiguous selector a.m1 This can be resolved by specifying the embedded type while calling the method. If we want to call the m1 method of then we can specify C on variable a by dot operator and then the method name. Given below is the example: a.C.m1()
There will not be any problem when the same method is defined on the embedded type but at different levels. If B and C are embedded in A and D is embedded in the m1() method is defined in C and D which are at different levels. There is no ambiguity in this case. When m1() is called on the variable of it will always call m1() of
Figure 8.3: No ambiguity in the method resolution process
Program 8.14
package main import "fmt"
type A struct { B C }
type B struct { D }
type D struct { }
func (d D) m1() { fmt.Println("m1 of D") }
type C struct { }
func (c C) m1() { fmt.Println("m1 of C") } func main() { a := A{} a.m1() }
If we want to call the method of then we can specify it explicitly: a.B.D.m1()
If there is no ambiguity in B’s hierarchy, we can chain it till B and the method resolution will start from The statements given above and below have the same behavior. Both will execute m1() method on
a.B.m1()
8.4.2 Polymorphism is not allowed
Polymorphism is not allowed on embedded types. It can be achieved by an interface; we will cover interface in the next chapter. Let’s take an example of Person and The Person is embedded in If a function expects a Person as a parameter, we cannot pass the value of the
Program 8.15 package main
type Person struct { id int name string } type Student struct { Person marks []float32 } func processPerson(person Person) { } func main() { s := Student{Person{101, "Prithvi"}, []float32{9.3, 3.4, 6.7}} processPerson(s) }
The following will be the output for the above program:
Error 8.3: ./main.go:16:15: cannot use s (type Student) as type Person in argument to processPerson
In Program we have defined Person with id and name fields and Student with Person as embedded and There is a function processPerson() which expects a Person as a parameter. We have created a value of Student type and have tried to pass it to the processPerson() function. It gives a compilation error saying that we cannot use Student type as Person type in the argument to
We can call the processPerson() function by passing Person value which is part of Student value:
processPerson(s.Person) Method overloading is also not allowed in Go. The compiler will complain if we try to do method overloading.
Conclusion
Declaration of function and method are almost similar. There is only one difference in their declaration—method declaration contains receiver whereas the function does not.
Methods can always be associated with any type. We can specify that type by the receiver. We cannot declare a method without a type of body. We can declare a method to type anywhere.
We declare a method to a type when we want to modify and get properties of the type.
There are two types of receivers:
Pointer It is used when we want to modify the properties of the type.
Value It is used when we want to get the properties of the type. The modification done by the value receiver method does not reflect outside of the methods.
We can declare methods on the basic data type, composite type, and even on function.
A method can be called using the dot operator on an instance of the type.
If a method is available in embedded type, then Golang uses the method resolution process to find a method. There can be ambiguity if a method is found in two types embedded at the same level.
Questions
What is the output of the following code? package main import "fmt" type person struct { id int name string } func (p person) setName(name string) { p.name = name } func main() { p := person{101, "Xyz"} p.setName("Abc") fmt.Println(p) }
State true or false:
We can create an overloaded method in Golang. The pointer receiver method can only be called on the address of the type on which the method is declared.
We can create a method without specifying a receiver variable. The receiver type is enough to declare a method.
It is not necessary to declare a method of a type in the same file where the type is declared.
Write a program to define the following custom types of int: Distance, Millimeter, Centimeter, Meter, Kilometer. You need to add methods on each type to convert and return the values of other types. For example, you can define methods on Millimeter to convert its value to Centimeter, Meter, and Kilometer. Consider the program from question 1. What will be the output if we call the setName() method on the address of the person type? Find the problem in the following program:
package main import "fmt" type A struct { } func (a A) m1() { fmt.Println("M1 of A") } type B struct { A } func (b B) m1(a int) { fmt.Println("M1 of B")
} func main() { b := B{} b.m1() }
CHAPTER 9 Interfaces
Interfaces provide pure abstraction in Golang. The interface body can have only method declaration and embedded interfaces. The methods in the interface do not have the body and only the method signature can be defined in the interface. We cannot create an object of an interface. The interfaces are meant for the implementation. Any type which implements provides the body of the method.
Structure
Introduction Implementing an interface
Empty interface
Method set Frequently used built-in/stdlib interfaces
Interface with struct
Some important points
Objective
This chapter will cover the concept of interface in Golang. You will learn what the interface is in Golang, how Golang interfaces are different from other languages, how to declare an interface, how implicit implementation happens in Golang, and its benefits. We will create the interface without methods and see its benefits with built-in functions. We will see the impact of the value receiver and pointer receiver while implementing the interface and creating polymorphic objects.
9.1 Introduction
The syntax of interface is similar to struct. The following is the syntax:
type InterfaceName interface{ MethodName(argument argumentType) returnType } The above syntax has a method that has one argument and one return type. Argument type and return type should be valid Go types. The type defined in the above syntax is just to explain the syntax.
Program 9.1 has a valid syntax:
Program 9.1 type Executor interface { Execute() }
The interface name is It has one method, i.e., The method does not have an argument and does not return anything. If an interface has only one method, then it is an interface naming convention that interface name should be methodName plus In our example, the method name is So, the interface name is by
appending r at the end of the method name. It is not a hardcore rule but it is a convention.
There can be multiple methods in an interface. All methods can have a different signature. The code given below has defined an interface called You can understand from the definition of the interface that it can be implemented by any type which can connect to some external resources like database, socket, etc. It has two methods, i.e., Open and The Open() method expects string URI which is used to connect. It returns two values: session and The Close() method does not expect any argument but it returns the error type. Program 9.2 type Connection interface { Open(uri string) (Session, error) Close() error }
9.2 Implementing an interface
As we just discussed, an interface is purely abstract. Its method does not have the body. We cannot create a value/object of an interface. The whole purpose of an interface is that some types will implement it. There is no keyword to implement an interface. Implementing an interface is implicit in Go. If a type has all the methods which are defined in an interface, then it implements that interface by default. Program 9.3 package main
import "fmt"
type Executor interface { Execute() }
type Thread struct { }
func (t Thread) Execute() { fmt.Println("Executing thread") } func main() { var exe Executor
exe = Thread{} exe.Execute() }
The following will be the output of the above program:
Executing thread
The Thread type in the above code implements the Executor interface implicitly. Unlike Java, we have not used any specific keyword to implement the interface. What is the proof that Thread implements the The answer is in the main function. We have created a polymorphic object of We have defined a variable of i.e., exe and assigned an object of Thread to it. If the Execute() method was not defined in the Thread type, we would have gotten a compilation error in the main method while creating a polymorphic object.
cannot use (Thread literal) (value of type Thread) as Executor value in the assignment: missing method Execute As we have seen in Program we can create a variable of an interface and assign actual implementation value to it. If we don’t assign actual implementation value to the interface variable, the interface variable will be useless because the variable will be assigned nil. If we call the method on that variable, the code will panic. If we modify the above code and remove the assignment of
Thread to then this code will panic and generate the following error message: Error 9.1:
panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x108909f ] goroutine 1 [running]: main.main() /Users/prithvipalsingh/go/src/workspace/main.go:17 +0x1f exit status 2
To implement any interface, you need to define/override all the methods of the interface in the type. We will get a compilation error in the main function of the code given below because we have defined only the Close() method of Connection in The Open() method is missing.
Program 9.4 package main
import "fmt" type Session struct { } type Connection interface {
Open(uri string) (Session, error) Close() error } type DbConnection struct { } func (conn DbConnection) Close() error { fmt.Println("Closing Database Connection") return nil } func main() { var conn Connection conn = DbConnection{} conn.Close() } The following will be the error in the above program: Error 9.2:cannot use (DbConnection literal) (value of type DbConnection) as Connection value in assignment: missing method Open You can resolve this error by defining the Open() method in the DbConnection type.
9.2.1 Why implicit implementation?
If you come from a Java background, you might be wondering why we are doing implicit implementation of the interface. You might be thinking that explicit implementation is always proper as that becomes clear by looking at the type that implements which interface. But implicit implementation has its benefits:
You may want to implement your custom interface in type which is available in a third-party library. You don’t have access to the code, so you can’t change the code. The implicit implementation is beneficial in this case. If the type has methods that can satisfy your interface, it implements that interface implicitly.
If you want to create a new interface with very few methods and you want to implement that interface in many types, you don’t need to make code changes in all the places.
9.2.2 Polymorphism: Interface as a contract
We have already seen that if a type satisfies the interface, we can assign the value of that to the variable of the interface. So, if a function has an interface as a parameter, we can pass a value of any type which satisfies that interface. The interface behaves as a contract between the function and caller of the function. The function doesn’t know anything about actual implementation that is being passed to the function. It only knows that it can call all the methods of the interface. The call will go to the implementation. The caller of the function can only pass the type which has all the methods of the interface.
There is a phrase that fits perfectly the interface as a contract: If something can do this, then it can be used here.
Program 9.5 package main
import "fmt"
type Bird interface { Fly() }
type Eagle struct { }
func (e Eagle) Fly() { fmt.Println("Eagle is flying over the cloud") }
type Pigeon struct { }
func (p Pigeon) Fly() { fmt.Println("Pigeon is flying on normal height") }
type Penguin struct { }
func (p Penguin) Fly() { fmt.Println("Penguin cannot fly") } func flyNow(b Bird) { b.Fly() } func main() { flyNow(Eagle{}) flyNow(Pigeon{}) flyNow(Penguin{}) }
The following will be the output for the above program:
Eagle is flying over the cloud Pigeon is flying on normal height Penguin cannot fly
We have created an interface named The Bird interface has a method We have created three structs and all implement Bird by overriding Fly() methods. Most importantly, we have defined a function as The flyNow function expects Bird as an argument and calls the Fly method on the variable of The flyNow function does not know which implementation of Bird will be passed; it only knows that the implementation will have a Fly method. We can pass an object of any type which implements the Bird interface. In the main() function, we are calling the flyNow function three times and we have passed the value of When the flyNow function calls the Fly method, its actual call goes to the or
9.3 Empty interface
Till now, we have seen that if a type has all the methods of an interface, it means that it satisfies the interface and it implements the interface implicitly. But what if an interface doesn’t have any method? In this case, all types satisfy the interface. The interface without any method is called the empty interface.
Since all the types satisfy the empty interface, we can assign the value of any type to the variable of the interface. Program 9.6 package main
import "fmt" type MyEmpty interface { }
type Student struct { id int name string } func main() { var empty MyEmpty empty = 10 fmt.Println(empty)
empty = "a" fmt.Println(empty) empty = 22.3 fmt.Println(empty) empty = Student{101, "Shyam"} fmt.Println(empty) }
The following will be the output for the above program:
10 a 22.3 {101 Shyam}
We have created an empty interface called The interface MyEmpty doesn’t have any method. We have also created a Student struct with id and name as fields. In the main function, we have created a variable of MyEmpty interface with the name empty. We have assigned different types of values to this variable like and We have also printed these values. But what is the benefit of an empty interface? We will understand the benefit of it by looking at the standard library code. We frequently use the fmt package to print output on the console. Have you ever tried to understand how the fmt.Println() function works? Why does it allow us to pass any of the values? Let us first look at the signature of this function:
func Println(a …interface{}) (n int, err error) { return Fprintln(os.Stdout, a…) }
Please notice the parameter of the function. It accepts var-args of an empty interface. So, var-args means that you can pass any number of arguments and an empty interface means you can pass the value of any type.
9.4 Method set
The method set is a set of method that makes a type implicitly implement an interface. Till now, we have seen that when a type defines all the methods of an interface, that type implicitly implements all the methods of that interface. But we have not seen whether the method should be a pointer receiver or value receiver method. The interface methods do not specify whether the implementing type should have a pointer receiver or value receiver. If a type T has pointer receiver methods (of an interface), then *T implements the interface and not We can assign the address of T to interface variable. If we try to assign the value of T to an interface variable, we will get a compilation error. Let us understand this by the following program:
Program 9.7 package main
import "fmt"
type Itr interface { m1() m2() } type St struct { }
func (s *St) m1() fmt.Println("In m1 } func (s *St) m2() fmt.Println("In m2 }
{ of St") { of St")
func main() { var i Itr i = St{} i.m1() i.m2() }
The following will be the error in the above program: Error 9.3:
./main.go:20:4: cannot use St literal (type St) as type Itr in assignment: St does not implement Itr (m1 method has pointer receiver) In Program we have defined an interface as This interface has two methods: m1 and We have defined a struct type as The type St implements the Itr interface by defining m1 and m2 methods with a pointer receiver. In the main function, we have declared a variable of Itr using the var keyword and we have assigned the value of St to it. Then, we have called the m1 and m2 methods on the interface variable. But when we run this code, we get a
compilation error. The error says that St does not implement The reason behind this error is that *St implements Itr interface, not If we re-run the above code by assigning the address of St to the variable the code will work just fine. When we implement an interface by a value receiver, we can assign both the value and address of the type to the interface. Let us see this in the following code: Program 9.8 package main import "fmt"
type Itr interface { m1() m2() } type St struct { }
func (s St) m1() { fmt.Println("In m1 of St") } func (s St) m2() { fmt.Println("In m2 of St") } func main() { var i Itr fmt.Println("Assigning value of St") i = St{}
i.m1() i.m2() fmt.Println("Assigning Address of St") i = &St{} i.m1() i.m2() } The following will be the output for the above program:
Assigning value of St In m1 of St In m2 of St Assigning Address of St In m1 of St In m2 of St In the above code, we have implemented the Itr interface by defining the m1 and m2 methods with values receiver. In the main function, we have tried to assign both the value and address of St to an interface variable and it worked fine. We will not get a compilation error in this code.
Conclusion of method set:
If a type T implements an interface I by defining the pointer receiver methods, then only *T will implement interface not So, we can assign only the address of T to the interface variable.
If a type T implements an interface I by defining a value receiver, then both T and *T implement the interface So, we can assign both the value and address of T to the interface variable.
9.5 Frequently used built-in/stdlib interfaces
There are too many frequently used interfaces in the standard library. We cannot cover all those interfaces here. We will cover the Stringer interface in this section and we will see the error interface implementation in Section
9.5.1 Stringer interface
The Stringer interface is very useful for printing customized output. It has only one method called String which returns a string. When we print a value of a type using if the type overrides the String() method, the String() method gets called internally. The fmt package will print whatever the String() method returns.
Program 9.9 package main import "fmt"
type Dollar float64 func (d Dollar) String() string { return fmt.Sprintf("$%f", d) } func main() { var d Dollar = 23.3 fmt.Println(d) }
The following will be the output for the above program: $23.300000
We have created a new type of i.e., The Dollar type overrides the String() method of the Stringer interface. The String() method returns the value of Dollar with prefixing the The main function creates a new variable, of type and assigns 23.3 to it. When we print it prints $23.300000 on the console.
9.5.2 Interface interface of sort package
There is an interface in the Golang standard library called Please note that the interface name itself is This interface is defined in the sort package and used for sorting slices of user-defined types. This interface has three methods: and The code snippet given below is the definition of the Interface interface from the sort package:
Program 9.10 package sort
type Interface interface { Len() int Less(i, j int) bool Swap(i, j int) }
If we want to sort a slice of any type, then we need to call the Sort function of sort package. The Sort function expects the Interface type. So, we can pass any type which implements The type can implement the Interface interface by implementing all three methods: Program 9.11 package main import (
"fmt" "sort" ) type Student struct { Name string Marks int }
func (s Student) String() string { return fmt.Sprintf("%s: %d", s.Name, s.Marks) } type ByMarks []Student func (b ByMarks) Len() int {return len(b)} func (b ByMarks) Swap(i, j int) {b[i], b[j] = b[j], b[i]} func (b ByMarks) Less(i, j int) bool { return b[i].Marks < b[j].Marks } func main() { students := []Student{ {"Bob", 31}, {"John", 42}, {"Michael", 17}, {"Jenny", 26}, } fmt.Println("Before sorting =", students) sort.Sort(ByMarks(students)) fmt.Println("After sorting =", students) }
The following will be the output for the above program:
Before sorting = [Bob: 31 John: 42 Michael: 17 Jenny: 26] After sorting = [Michael: 17 Jenny: 26 Bob: 31 John: 42] In Program we have declared a struct called We have defined the String() method in Student to print Student objects in a beautified way. Then, we have created a new type of Student slice, i.e., ByMarks and we have defined all three methods of interface on ByMarks type as follows:
It returns the length of the slice.
It swaps ith index element with jth index element. It has logic to find which element is less between the ith index element and jth. In the main function, we have a slice of Student with four elements. When we print students variable, it prints in the sequence in which these elements were inserted. In the next line, we are sorting the students variable by passing the sort.Sort() function. We have typecasted to the ByMarks type before passing it to the sort.Sort() function.
The above is unnecessarily complicated for sorting. The Len and Swap methods are unnecessary because the Sort function should have an internal logic to find the length of the slice and swap elements. The sort package has a function to overcome this complexity. We can use the Slice function of the sort package. We
will not need to implement any The sort. Slice function expects two parameters: The slice of the elements that we want to sort. The function that will have logic for sorting.
The following code demonstrates the Slice function: Program 9.12 package main import ( "fmt" "sort" ) type Student struct { Name string Marks int } func (s Student) String() string { return fmt.Sprintf("%s: %d", s.Name, s.Marks) } func main() { students := []Student{
{"Bob", 31}, {"John", 42}, {"Michael", 17}, {"Jenny", 26}, } fmt.Println("Before sorting", students) sort.Slice(students, func(i, j int) bool { return students[i].Marks < students[j].Marks }) fmt.Println("After sorting", students) } The following will be the output for the above program:
Before sorting [Bob: 31 John: 42 Michael: 17 Jenny: 26] After sorting [Michael: 17 Jenny: 26 Bob: 31 John: 42] In Program the Student struct does not implement any interface. This code calls the sort.Slice() method. We have passed a slice of students and an anonymous function. The anonymous function has logic to sort students by Marks in ascending order.
9.6 Interface with struct
An Interface can be used within a struct body. We can either use the interface as an embedded type or we can define an interface variable. We cannot pass the actual value of an interface while creating a struct value because interfaces are abstract. We need to pass the value of the actual implementation type. So, when we call a method of the interface on a struct, the call will go to the actual implementation type.
9.6.1 Interface variable as struct field
An interface variable can be used as a struct field. When an object of that struct is created, we need to pass the actual implementation value for the interface as shown below:
Program 9.13 package main import "fmt"
type Executor interface { Execute() } type Thread struct { } func (t Thread) Execute() { fmt.Println("Executing thread") } type Process struct { exe Executor } func main() { p := Process{Thread{}} p.exe.Execute() }
The following will be the output for the above program:
Executing thread
In Program we have created a struct called Process with the interface variable, as a field. The exe field is a variable of the Executor interface. The Thread struct implements the In the main function, we have created a variable of Process and passed the Thread value for the Executor field. We have called the Execute method on the exe field of p variable.
9.6.2 Embedding interface into the struct
Like structs, interfaces can also be embedded in the struct. When an interface is embedded in the struct, all the methods of the interface are directly available in the struct. One point needs to be taken care of which is that while creating a value of a struct, we have to pass the actual implementation value of an interface; otherwise, panic will be raised while calling the methods of the interface. Program 9.14 is almost the same as Program The difference is that we are embedding the interface here whereas we had used the interface variable as a field in Program Here, we are calling the interface method directly whereas we had called the interface method on interface field of struct value previously.
Program 9.14 package main
import "fmt"
type Executor interface { Execute() }
type Thread struct { }
func (t Thread) Execute() { fmt.Println("Executing thread") }
type Process struct { Executor }
func main() { p := Process{Thread{}}
p.Execute() }
Conclusion
The methods in an interface do not have a body. All methods of an interface are purely abstract.
An interface’s methods can have arguments and return types.
Any custom type can implement the interface. There is no explicit syntax to implement an interface. When any type defines all methods of the interface, then that type implicitly implements the interface.
A type that implements an interface can also have other methods.
A type can implement more than one interface.
An interface can be implemented by many types.
When an interface does not have any method, it is called an empty interface. An empty interface is by default implemented by all types. An interface can be used as a field of a struct. When it is used, we need to pass the actual implementation of the interface while creating a struct object.
Polymorphism can be implemented using an interface in Golang.
Questions
What will be the result of the following program? package main
import "fmt"
type I interface { m1() }
type T struct { }
func (t *T) m1() { fmt.Println("M1 of T") }
func main() { var i I t := T{} i = t i.m1() }
M1 of T
Compilation error
What will be the result of the following program?
package main
import "fmt"
type I interface { m1() }
type T struct { }
func (t *T) m1() { fmt.Println("M1 of T") } func (t *T) m2() { fmt.Println("M2 of T") } func main() { var i I t := T{} i = &t
i.m2() } M2 of T
M1 of T Compilation error What is the stringer interface?
It converts every object into a string. It is an empty interface and every type by default implements this.
It has a method named When you implement the String method on any type, we can customize the output of the type.
There is no such interface in Golang. Which is the name of the interface which can be used for sorting?
Sorter SortInterface Comparator
Interface Select true statements from the followings.
The implements key can be used to implement an interface. A type can implement more than one interface.
An interface can be implemented by many types. There is no syntax to implement an interface To implement an interface in any type, the type needs to implement all the methods of the interface.
CHAPTER 10 Pointers
The pointer variable can hold the memory address of another variable. When the value of the variable is changed, it reflects in the pointer variable. Pointers are very useful when the variable is passed to the function. When any change happens to the variable, it reflects in the caller function.
Structure
What are pointers? How can we use pointers?
Pointer concepts
Programming examples
Objective
In this chapter, you will learn what pointers are in Golang. This chapter will explain to you the use of a pointer, how to declare a pointer variable, and what are the pointer types. You will also learn why arithmetic operations are not allowed on pointer variables in Golang.
10.1 Declaration
The ampersand can be used to access the memory address of any value. In the program, the address of variable x is accessed using &x and assigned to variable Program 10.1 will print the value and address of the variable
Program 10.1 package main import "fmt"
func main() { x := 15 a := &x fmt.Println("Value of x:", x) fmt.Println("Address of x:", a) }
The following will be the output for the above program:
Value of x: 15 Address of x: 0xc000014058 Note: Memory address of x can be different on each run.
The variable, is called a pointer variable because it holds the memory address of a variable. The value of a pointer variable (memory address) can be accessed using an asterisk Accessing the pointer’s underlying value using the * is called dereferencing or in Program 10.2 will print
Program 10.2 package main
import "fmt"
func main() { x := 15 a := &x fmt.Println("Value at address of x:", *a) } The following will be the output for the above program:
Value at address of x: 15 The above dereferencing statement is equivalent to the statement below:
fmt.Println("Value of x:", *(&x)) Since the pointer variable holds the address of any value, any modification to the value of the pointer variable will be reflected
in the actual value. Program 10.3 modifies the value of *a and it will reflect in variable x as well. Program 10.3 package main import "fmt"
func main() { x := 15 a := &x fmt.Println("Value of x(before modification):", x) fmt.Println("Value of *a:(before modification):", *a) *a = 20 fmt.Println("Value of x(After modification):", x) fmt.Println("Value of *a:(After modification):", *a) }
The following will be the output for the above program: Value of x(before modification): 15 Value of *a:(before modification): 15 Value of x(After modification): 20 Value of *a:(After modification): 20 The built-in function new() can be used to create a pointer:
Program 10.4 package main
import "fmt" func main() { a := new(int) *a = 15 fmt.Println(*a) fmt.Println(a) } The following will be the output for the above program:
15 0xc000014058 The pointer variable can also be created using the var keyword. The following statements create a pointer of type *int and the assigned address of x to the pointer:
x:= 15 var a *int a = &x
10.2 Pointer type
Till now, we have created a pointer variable using := and the type of variable was inferred as We can print the type of variable using the reflect package as shown below:
Program 10.5 package main import ( "fmt" "reflect" )
func main() { x := 15 a := &x fmt.Println(reflect.TypeOf(a)) }
The following will be the output for the above program:
*int It is also an important point to note that the address of type T can only be assigned to type Violating this rule gives a
compilation error. Program 10.6 will not compile because the address of an int variable is assigned the *float64 variable which is a type mismatch.
Program 10.6 package main func main() { x := 15 var a *float64 a = &x }
The following will be the output for the above program: Error 10.1: cannot use &x (type *int) as type *float64 in assignment If a pointer variable created using a var keyword has not assigned a memory address, it will point to the nil pointer. If we try to print it, will be printed on the console. var a *float64 fmt.Println(*a) What will happen if we try to access the value of the pointer using the asterisk? If we try to access the value at nil pointer, the Go program will panic with a runtime error. The following program gives a
demonstration:
Program 10.7 package main
import "fmt" func main() { var a *float64 fmt.Println(*a) }
The following will be the output for the above program: Error 10.2: panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1091507]
10.3 Operators with the pointer
Like C and C++, we can apply operators on a pointer but with limited usage. We can use a relational operator on a pointer in Golang. We cannot use arithmetic or any other operators on pointers.
10.3.1 Relational operator
The only relation operator allowed on pointers is the equality operator. The equality operator can be used to check whether two pointers are pointing to the same memory address or not. This is demonstrated in Program
Program 10.8 package main import "fmt" func main() { x := 15 y := 15 a := &x b := &x c := &y fmt.Println(a == b) fmt.Println(a == c) }
The following will be the output for the above program:
true false
The equality operator or can be used to check whether a particular variable has a memory address or nil pointer as shown
below:
var a *float64 fmt.Println(a == nil)
The following will be the output for the above statements:
true
10.3.2 Arithmetic operator
Those who are coming from a C/C++ background would expect arithmetic operators on pointers. But for the sake of simplicity, the arithmetic operators are not allowed on pointers in the Go language. The arithmetic operators on pointers may lead to an illegal address which is very dangerous.
In the C family languages, the pointer arithmetic is very useful when iterating over an array because it is very fast. However, the hardware and compiler are so advanced nowadays that iterating arrays using indices is more efficient than using pointer arithmetic. So, there is no need for the pointer arithmetic to perform a fast iteration over the array.
10.4 Pointers on composite types
We have seen pointers on basic data types. Now, we will see the pointer in composite data types like and
10.4.1 Pointer to array
We can create a pointer to the array and pass it to the function. When the calling function makes any modification to an array, it will reflect in the caller function. Before understanding how modification is done by calling a function on array that reflects in the caller function, let’s first see how to create a pointer to an array.
10.4.1.1 Declaration
There are two ways of creating a pointer to an array: by using the var keyword and by using the := operator.
Declaration using the var keyword
Program 10.9 declares a pointer to the array using the var keyword: Program 10.9 package main
import "fmt"
func main() { var arr1 *[5]int var arr2 = [5]int{1, 2, 3, 4, 5} arr1 = &arr2 fmt.Println((*arr1)[0]) } The following will be the output for the above program:
1
In Program we have declared a pointer to an array variable, using an asterisk We have declared the array of i.e., Then, we have assigned the address arr2 to We have printed the zeroth element of We can see in the output that the zeroth element arr1 is 1 because we have assigned arr2 to arr1 and arr2 has value 1 at the zeroth array location. Please note that we have used an asterisk to access the value on pointer array as
It is not necessary to use an asterisk to access elements on the pointer array. We can access an element of the pointer array, the same as not an array variable. It looks very clear. Program 10.10 accesses the pointer array element without using an asterisk:
Program 10.10 package main import "fmt"
func main() { var arr1 *[5]int var arr2 = [5]int{1, 2, 3, 4, 5} arr1 = &arr2 fmt.Println(arr1[0]) } The following will be the output for the above program:
1
The output Program 10.10 is similar to Program The code is almost similar except for one change, i.e., we are accessing the pointer array element without using an asterisk. It is just not It looks very clear and more readable. When a pointer array is declared using the var keyword but has not assigned any address of the array and we try to access any element of the pointer array, the program will panic. This is demonstrated in Program Program 10.11 package main
import "fmt" func main() { var arr1 *[5]int fmt.Println(arr1[0]) } The following will be the error in the above program:
Error 10.3
panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x109d09f ] goroutine 1 [running]:
main.main() /Users/prithvipalsingh/go/src/workspace/main.go:7 +0x1f exit status 2 In Program we have declared the pointer array but we have not assigned the address of the any array. When we run, it panics because we tried to get the zeroth element of the pointer array but there is no underlying array that exists. Declaration using := operator
Program 10.12 creates the pointer array using the := operator: Program 10.12 package main import "fmt" func main() { var arr2 = [5]int{1, 2, 3, 4, 5} arr1 := &arr2 fmt.Println("Zeroth element of arr1:", arr1[0]) }
The following will be the output for the above program: Zeroth element of arr1: 1
Please note that we have created an array using the var keyword. The pointer array type will be inferred from the right side of the := operator.
10.4.1.2 Pointer array as a function argument
We can pass a pointer array as a function argument. When we modify any element of the pointer array in calling the function, then that change will be reflected in the caller function. In Program we will modify one array index in the calling function and the new value will be available in the caller function:
Program 10.13 package main import "fmt"
func main() { arr := [5]int{10, 20, 30, 40, 50} modify(&arr) fmt.Println("Array Elements", arr) }
func modify(arr *[5]int) { arr[0] = 100 } The following will be the output for the above program:
Array Elements [100 20 30 40 50]
In Program we have initialized the array of int with size We have passed the address of the array into the modify function. In the modify function, we have changed the zeroth element of the array and assigned 100 to it. In the main function, we have printed the array. It prints the array elements mentioned in the output of Program You can see that the zeroth element of the array is 100 in the main function as well. This means that modification has been done in the calling function which is visible in the caller function.
Note: It is not a good practice to send the address array to the function. We should create a slice from the array with the default indexes and pass the slice in the function.
When we create a slice from the array with default indexes, the start index will be zero and the last index will be the length of the array minus This means that it will create a slice with the whole array, and the underlying data structure of that slice will be an original array. Any modification to the slice will reflect in the original array. Program 10.14 package main import "fmt"
func main() { arr := [5]int{10, 20, 30, 40, 50} modify(arr[:])
fmt.Println("Slice Elements", arr) } func modify(arr []int) { arr[0] = 100 }
The following will be the output for the above program: Slice Elements [100 20 30 40 50] In Program we have defined a function, with the slice as an argument. The modify function modifies the zeroth element of slice and assigns In the main function, we have declared an array with the default value. In the next line, we have called the modify function. Please note that we have created a slice from the array (using and passed to the function. Finally, we have printed the array. We can see in the output of Program 10.14 that the zeroth element of the array has
10.4.2 Pointer to slice
We can create a pointer to the slice, but why do we need a pointer to the slice? We have already seen in Section 9.4.1.3 that if we modify any element of the slice, it will be visible to the caller function. So what is the use of a pointer to a slice?
A pointer to a slice is very frequently used in standard or custom libraries. Let us understand the importance of the pointer to a slice in Golang. As we know, slice is a dynamic data structure. We can append as many elements to a slice as we need. We have seen in Program 10.14 that when we modify an existing element of the slice in the calling function, it reflects in the caller function. But, when we append new elements to the slice in the calling, it does not reflect in the caller function. Let us see this in Program
Program 10.15 package main
import "fmt"
func main() { s1 := []int{10, 20, 30, 40, 50} modify(s1) fmt.Println("Slice Elements", s1) }
func s1[0] s1 = s1 = }
modify(s1 []int) { = 100 append(s1, 60) append(s1, 70)
The following will be the output for the above program:
Slice Elements [100 20 30 40 50]
In Program we have modified the modify function. Apart from modifying the zeroth element of the slice, we are appending 60 and 70 to the slice. In the main function, we are calling the modify function and printing the slice in the next line. Please note that in the output of Program the appended values are not printed. Only the zeroth element is changed. This concludes that when the new element is appended to the slice in the calling function, the appended values are not reflected in the caller function. As slice is a dynamic data structure, it is very important that when the calling function adds new values to the slice, it should be reflected in the caller function as well otherwise, there is no use of adding new value in the slice. This can be achieved using a pointer to the slice.
Program 10.16 package main
import "fmt"
func main() { s1 := []int{10, 20, 30, 40, 50} modify(&s1) fmt.Println("Slice Elements", s1) } func modify(s1 *[]int) { (*s1)[0] = 100 *s1 = append(*s1, 60) *s1 = append(*s1, 70) } The following will be the output for the above program:
Slice Elements [100 20 30 40 50 60 70]
In Program we have changed the modify function to take a pointer to the slice as an argument. Now when we run this program, the output will not be the same as the output of Program We can see that the appended elements are reflected in the main function. So, the pointer to the slice is very useful.
10.4.3 Pointer to struct
A pointer to a struct can be created using ampersand We can access or modify the field of a pointer to a struct variable by using or without using an asterisk. We can assign the address of one struct to the pointer of the struct variable. When any changes are done on the pointer to the struct variable, it will be reflected in the original struct.
Program 10.17 package main
import "fmt"
type person struct { id int name string }
func main() { p1 := person{101, "XYX"} p2 := &p1 fmt.Println("Person *p2.id:", (*p2).id) fmt.Println("Person p2.id:", p2.id) p2.name = "ABC" fmt.Println("Person p1:", p1) }
The following will be the output for the above program:
Person *p2.id: 101 Person p2.id: 101 Person p1: {101 ABC}
In Program we have defined a struct named as person with the fields id and In the main function, we have created an object of the person struct, i.e., We have created a pointer to the struct variable p2 and assigned the address of p1 to it. We have two print statements to print the id of the p2 variable. In the first print statement, we have used asterisk whereas in the second print statement, we have not used asterisk. Both the print statements output the same result. In the next line, we have changed the name field of the p2 variable. The changed name field is reflected in p1 because p2 holds the address
A pointer to struct can also be created at the time of struct initialization. We can use an ampersand while initializing the p1 := &person{101, "XYX"}
Like array and slice, a pointer to struct can be passed as a function argument. When a calling function changes any field of a pointer to the struct variable, it will be reflected in the calling function.
Program 10.18
package main
import "fmt" type person struct { id int name string } func main() { p1 := &person{101, "XYX"} modify(p1) fmt.Println("Person p1:", p1) } func modify(p1 *person) { p1.name = "NewXYZ" }
The following will be the output for the above program: Person p1: &{101 NewXYZ} In Program the modify function takes the pointer of the person struct as an argument. The modify function changes the name field from XYZ to In the main function, we have created a pointer to the struct variable p1 and passed it to the modify function. When we print it prints the changed value of the name field.
Conclusion
A pointer variable can hold the address of another variable. The ampersand can be used to access the memory address of the variable.
The asterisk can be used to access the value at the address. The reflect package can be used to find the type of the pointer variable.
The pointer variable must be assigned with the address of another variable before the access value of the pointer variable, otherwise the code will panic.
The address of the int variable can only be assigned to the pointer variable type of The same is applicable to other types as well.
The only relational operator that can be used on the pointer variable is the equality operator. We can create a pointer to the array but it is not recommended to use it. We should create a slice from an array and pass it to another function.
When we want to append values to the slice which is an argument to the function, we must define an argument as a pointer to slice.
Questions
What will be the output of the following program? package main import "fmt" type person struct { id int name string } func main() { p1 := &person{101, "XYX"} modify(p1) fmt.Println("Person p1:", *p1) }
func modify(p1 *person) { p1 = &person{202, "ABC"} }
Person p1: {101 XYX} Person p1: {202 ABC}
What will be the result in the following program?
package main
import "fmt"
func main() { var a *int b := 10.3 a = &b fmt.Println(*a) }
The program will panic with nil pointer dereferencing error.
It will print 10.3.
There will be a compilation error because we cannot assign the address of the float variable to the int type pointer variable.
Point out the error in the following program: package main import "fmt"
func main() { var a *int fmt.Println(*a)
}
Which of the following statements will return true? var a *int fmt.Println(a == nil) var a *int var b *int fmt.Println(a == b) a := 10 b := 10 x := &a y := &b fmt.Println(x == y)
What will be the output of the following program? package main import "fmt" func main() { a := 10 b := &a fmt.Println(a == b) }
true false Compilation error
CHAPTER 11 Concurrency
A process is running a computer program that may have multiple threads. When a process runs multiple threads on a single core/processor, it is called a concurrent program. When a process runs multiple threads on multiple cores/processors, it is called a parallelized program. In Golang, we can use goroutines to write a concurrent program and channel for communication between goroutines.
Structure
Concurrency with goroutines Communication between goroutines using channel
Programming examples
Objectives
This chapter will cover goroutines and channels. You will learn how to define a goroutine, what is WaitGroup, and how to use WaitGroup to wait for other goroutines to finish. You will understand the concept of channel. We will use channel for communication between the goroutines. We will create two types of goroutines: buffered and unbuffered.
11.1 Goroutines
The concurrent programs in Golang are called goroutines. Multiple goroutines can run in parallel. Each Go program has at least one goroutine which is called the main goroutine. The main goroutine can spawn any numbers of goroutines.
11.1.1 Introduction
We need to prepend the go keyword before the function call to start the function as a new goroutine, and it will have a separate call stack. The new goroutine will be the child of the main goroutine. When the main ends, the Go runtime terminates all its children’s goroutines.
Program 11.1 package main import ( "fmt" "time" )
func main() { go f1("F1") go f1("F2") fmt.Println("Sleeping for 5 second") time.Sleep(5 * time.Second) fmt.Println("Main completed") } func f1(name string) { for index := 0; index < 10; index++ { fmt.Printf("%v: index %d\n", name, index) time.Sleep(1 * time.Second)
} }
The following will be the output for the above program:
F1: index 0 F2: index 0 Sleeping for 5 second
F2: F1: F2: F1:
index index index index
1 1 2 2
F2: index 3 F1: index 3 F2: index 4 F1: index 4 Main completed Please note that the above output is not predictable. There could be a different output each time we execute this code. In Program we have defined a function, f1 with a parameter of string, i.e., name. This function has a which iterates 10 times from 0 to Within the it prints the name and index of the loop and then it sleeps for 1 second. So, for each iteration of it sleeps for one second after the print statement. In the main function, we have called the f1 function twice as the goroutines. These are called as goroutines because we have
prepended the go keyword before the function call. We have passed F1 as an argument in the first function call and F2 as an argument to the second call. The main function sleeps for five seconds after calling the goroutines, so the goroutines get some time to execute.
Please note the output of the above code. The goroutine does not print the values of all the iterations. It prints till 4th for both the goroutines and the program ends, whereas we have written for loop to print from 0 to This happened because of each iteration of sleeping for 1 second. The iterates 10 times so the total sleep time is 10 seconds. But the main function sleeps only for 5 seconds. So when the main ends, it does not wait for the child goroutine to complete. It terminates the child goroutines when the main goroutine ends. Again, the output is not predictable and it may be a possibility that you’ll run this program.
11.1.2 WaitGroup
In the previous section, we have used the Sleep function to wait for child goroutines to finish. But the Sleep function is not a good practice to wait for the child goroutine to finish. We can use WaitGroup of the sync package to wait for the child goroutine to finish.
The sync.WaitGroup is a struct type. It has three useful methods: The Add method adds an integer number to the The integer number is a count that says the main function will wait for that number of a goroutine to complete.
The Done method decreases the count, which is added in the Add function. We should call this at the end of the goroutine.
The Wait method waits for all goroutines to finish. When the count reaches 0, it finishes the wait.
Let’s look at the following program: Program 11.2 package main
import (
"fmt" "sync" "time" )
func main() { var wg sync.WaitGroup wg.Add(2) go f1("F1", &wg)
go f1("F2", &wg) fmt.Println("Main: Waiting for Goroutines to finish") wg.Wait() fmt.Println("Main completed") } func f1(name string, wg *sync.WaitGroup) { for index := 0; index < 10; index++ { fmt.Printf("%v: index %d\n", name, index) time.Sleep(1 * time.Second) } wg.Done() } The following will be the output for the above program:
Main: Waiting for Goroutines to finish F1: index 0 F2: index 0 F2: index 1 F1: index 1
F1: index 2 F2: index 2 F2: index 3 F1: index 3 F2: index 4 F1: index 4 F1: index 5 F2: index 5 F2: index 6 F1: index 6 F1: index 7 F2: index 7 F1: index 8 F2: index 8 F2: index 9 F1: index 9 Main completed Program 11.2 is almost the same as Program The only difference here is that we have defined a variable wg of We have called the Add method and passed 2 as an argument. We have passed the address wg variable to the goroutines. We have called the same function two times as the goroutine. We have called the Done function wg variable at the end of the function. It means that the execution of the goroutine is complete. We have called the Wait method at the end of the main function. If all goroutines are completed but in wait, the count has not reached zero, there will be a deadlock in the Wait method. So,
the code will panic at the Wait method call. Please try this as an exercise.
11.1.3 Mutex Lock
When multiple goroutines update any shared variable, that scenario is called a race condition. The race condition causes an inconsistent update of shared variables. The code which causes the inconsistently update of the variable is called a critical section. We can use sync. Mutex struct to lock the critical section so that only one goroutine can update the variables:
Program 11.3 package main
import ( "fmt" "sync" ) var amount = 1000 func main() { var wg sync.WaitGroup wg.Add(100) for index := 0; index < 100; index++ { go withdraw(&wg) } wg.Wait() fmt.Println(amount) }
func withdraw(wg *sync.WaitGroup) { defer wg.Done() amount = amount - 1 }
The output of Program 11.3 is not predictable. The expected output is 900, but when you run this program, you will get values between 900 to
We have executed the withdraw function as 100 goroutines. The withdraw function reduces 1 from the amount in each goroutine call. The amount variable is a shared variable, so decrement 1 from the amount is a critical section. We need a wrap decrement statement in the mutex lock. We have done this in Program
Program 11.4 package main
import ( "fmt" "sync" ) var amount = 1000
func main() { var wg sync.WaitGroup var m sync.Mutex wg.Add(100)
for index := 0; index < 100; index++ { go withdraw(&wg, &m) } wg.Wait() fmt.Println(amount) } func withdraw(wg *sync.WaitGroup, m *sync.Mutex) { defer wg.Done() m.Lock() amount = amount - 1 m.Unlock() }
The following will be the output for the above program: 900
We have created a variable of sync.Mutex in the main method and we have passed its address to the withdraw function. We have called Lock before the decrement statement and Unlock after it, so now decrement is safe. It will always give the same result.
A drawback of the Mutex lock: Since it locks for other goroutines to access the critical section, it slows down the overall execution of the program.
11.2 Some important functions
There are some functions available in the Go standard library. We can use those functions to control the execution of the goroutines.
11.2.1 The GOMAXPROCS functions
The Go runtime runs goroutines within a logical processor. One logical processor is bound to one operating system thread. We can specify how many logical processors will be used in a program. We can set a number of logical processors by setting the value to runtime.GOMAXPROCS() function. The default value for GOMAXPROCS is 1. It means that multiple goroutines can be mapped on an OS thread. Adding more values to GOMAXPROCS means adding more logical processors. If we run a program where GOMAXPROCS is and the machine has multiple core/processors, then the program will run parallely. If the machine has a single-core, then the program will run concurrently using multiple threads:
Program 11.5 package main
import ( "fmt" "runtime" "sync" )
func main() { runtime.GOMAXPROCS(2)
var wg sync.WaitGroup wg.Add(2) go f1("F1", &wg) go f1("F2", &wg) fmt.Println("Main: Waiting for Goroutines to finish") wg.Wait() fmt.Println("Main completed")
} func f1(name string, wg *sync.WaitGroup) { for index := 0; index < 10; index++ { fmt.Printf("%v: index %d\n", name, index) } wg.Done() }
The output of Program 11.5 is not predictable, but it will run parallelly. We have called runtime.GOMAXPROCS at the first line of the main function.
11.2.2 The Goexit function
We can use the Goexit function to stop the execution of the goroutine. The Goexit function will stop a goroutine in which it is called.
Program 11.6 package main import ( "fmt" "runtime" "sync" )
func main() { var wg sync.WaitGroup wg.Add(1) go f1("F1", &wg) fmt.Println("Main: Waiting for Goroutines to finish") wg.Wait() fmt.Println("Main completed") } func f1(name string, wg *sync.WaitGroup) { defer wg.Done() for index := 0; index < 10; index++ { if index == 5 {
runtime.Goexit() } fmt.Printf("%v: index %d\n", name, index) } }
The following will be the output for the above program:
Main: Waiting for Goroutines to finish F1: index 0 F1: index 1 F1: index 2 F1: index 3 F1: index 4 Main completed In Program we have executed the f1 function as a goroutine. The f1 function has a for loop iterating from 0 to Within the for loop, we have a condition that if an index value is we call the Goexit function. So, the for loop executes till index 0 to 5 and it exists and prints from 0 to The main function waits until the f1 function completes.
11.3 Channels
Channel is a type in Golang by which two goroutines can communicate. One goroutine can send data and another goroutine can receive data. It is like a pipeline. Channels in Golang introduce a new way of concurrency. The implementation of concurrency using channels works as said in the statement given below:
Do not communicate by sharing memory; instead, share memory by communicating.
We have seen it in Section 11.1.3 where we used amount as a shared variable, and multiple goroutines were accessing it. The Golang does not encourage concurrency implementation in this way. It strongly says that do not communicate by sharing It suggests we should use channels wherever communication is required.
11.3.1 Introduction
The make built-in function can be used to declare a channel. make(chan , size>)
A keyword is used for channel declaration. The type is any valid type and the channel will hold values of that type. A type could be a basic data type or a custom type.
buffer This is an optional value. The default value is 0. We can declare a buffered channel by providing value. We will discuss buffered and unbuffered channels later in this chapter.
Let’s declare a channel that can host integer values. ch := make(chan int)
The