Packages & Import packages#

  • package is a namespace that groups related Go files together.
  • by convention, package name is the last directory name where the sources are stored eg-import "math/rand" , rand is the directory name where the sources are stored and will have package rand for all sources.
  • directory which stores all related go source files
/*
mathutils/  
  add.go  
  subtract.go
*/
package mathutils // Both uses mathutils lib
  • Single Import package, multiple packages import, alias imports & blank imports
import "fmt" // Single Import

// Multiple Imports
import (  
	"fmt"  
	"math"  
)

// Alias Imports
import m "math"
import (  
	f "fmt"  
	m "math"  
)

// Blank Imports => forces to run init() of the package, but don't want to use its symbols.
import _ "net/http/pprof"
  • Special package main -> Entry point to the application. And must have main() method as the entry point to the application.
  • main() function takes no arguments and returns nothing.
  • Package can have automatic initialisation function, which runs before main()
func init() {  
  fmt.Println("Package initialized")  
}

// Order of execution
/*
imported packages init()  
current package init()  
main()
*/
  • Package visibility is handled with identifiers which starts with capital case i.e
    • Name -> Visible outside the package
    • name -> Only visible inside the package

Modules#

  • Creating new Module go mod init github.com/user/project
  • Importing module import "github.com/user/project/mathutils"

Functions#

  • A function in Go is a reusable block of code that performs a specific task.
  • Parameter vs Arguments
TermMeaning
ParametersVariables defined in function signature
ArgumentsActual values passed to the function
// Decleration practices
func functionName(parameters) returnType {  
  // function body  
}

// Decleration & calling
func add(a int, b int) int { // parameters  
  return a + b  
}  
  
result := add(3, 5) // arguments
  • Types Grouping & Multiple returns
func add(a, b int) int {  
  return a + b  
}

func divide(a, b int) (int, error) {  
	if b == 0 {  
		return 0, errors.New("division by zero")  
	}  
	return a / b, nil  
}
q, err := divide(10, 3) // Multiple Return, Mainly used in error handling
  • Named return in functions.
func rectangle(l, w int) (area int) {  
	area = l * w  
	return  
}
  • Variadic functions accept variable number of arguments.
func sum(nums ...int) int {  
	total := 0  
	for _, v := range nums {  
		total += v  
	}  
	return total  
}

// Usage
sum(1,2,3)  
sum(10,20,30,40)

nums := []int{1,2,3}  
sum(nums...)
  • Functions are first-class citizens (can be assigned to variables & passed).
  • Annonymouns functions & lambdas
go func() {  
  fmt.Println("hello")  
}()
  • closure is a function that captures variables from its surrounding scope.
func counter() func() int {  
	count := 0  
	  
	return func() int {  
		count++  
		return count  
	}  
}

// Usage
c := counter()  
fmt.Println(c()) // 1  
fmt.Println(c()) // 2
  • Pass by value
func modify(x int) {  
	x = 10  
}  
  
a := 5  
modify(a)  
  
fmt.Println(a) // still 5
  • Pass by reference (still pass by value, but the value which is passed is a pointer to the original varaible)
func modify(x *int) {  
    *x = 10  
}  
  
a := 5  
modify(&a)  
  
fmt.Println(a) // 10
  • Struct methods - r Rectangle is called a receiver (value receiver). & pointer receiver (helps avoid copying a large struct)
type Rectangle struct {
    width  int
    height int
}

func (r Rectangle) Area() int { // value reciever
    return r.width * r.height
}

func (r *Rectangle) Scale(factor int) {  // pointer reciever
    r.width *= factor  
}
  • defer function call is heavily used for locks, safe file closing etc. This uses LIFO ordering of stack. executed when surrounding method is completed.
defer fmt.Println("1")  
defer fmt.Println("2")  
defer fmt.Println("3")

/*
3
2
1
*/

Datatypes & Variables#

  • Basic usage
var age int = 25 // Type explicity specified
age := 25 // Type implicity inferred (short hand decleration), ONLY functions scope (NOT in global scope).
  • Type implicitly inferred, can only be used within the functions. It cannot be used in global scope.
  • Primitive types
// Whole Numbers
int
int8
int16
int32
int64
uint  
uint8  
uint16  
uint32  
uint64

// Floating Numbers
float32
float64

//Boolean true/false
bool

// String - immutable sequence of bytes
string
/*
- immutable
- UTF-8 encoded
- concatenation using `+`
*/
  • Composite Types
// Arrays => Fixed size, cannot be changed once its already declated & initialized.
var nums [3]int = [3]int{1,2,3}

// Slices => Dynamic size, automatically resized
nums := []int{1,2,3}
/*
- pointer to array
- length
- capacity
*/

// Maps => key-value pairs
ages := map[string]int{  
	"John": 25,  
	"Anna": 30,  
}
value := ages["John"] // fetching values 
age, ok := ages["John"] // checking if value exists

// Struct
type User struct {  
	Name string  
	Age int  
}
u := User{
    Name: "John",
    Age: 25
}
  • Default values
TypeDefault value
int0
float0.0
boolfalse
string""
slicenil
mapnil
pointernil
structzero values for all fields
var age int  
fmt.Println(age) // 0
  • Constants
    • Constants MUST be known at compile time. Otherwise the compiler will panic.
    • Generally numbers, strings, booleans or expressions made from those are good candidates for constants.
    • Hence, maps, slices, struct and arrays CANNOT be constants. these are runtime data structures & involves memory allocation at runtime.
const pi float64 = 3.14159
  • Multiple declerations, variable shadowing,
var a, b int = 1, 2

var (  
    name string = "John"  
    age int = 30  
)

// Shadowing
x := 10  
if true {  
    x := 20  
    fmt.Println(x) // 20  
}  
fmt.Println(x) // 10
  • nil values
    • pointers
    • slices
    • maps
    • channels
    • interfaces
    • functions
  • Iota - auto increment values (Similar to enum)
const (  
	Sunday = iota  
	Monday  
	Tuesday  
)

Sunday // value 0
  • Type aliases
type Age int
var myAge Age = 25
  • Blank Identifier for return values value, _ := someFunction()
  • Type conversion / Type casting
var a int = 10
var b float64 = float64(a)
  • Type conversions between string & primitive types
import "strconv"
var intString string = "42"
var i, err = strconv.Atoi(intString)

var number int = 12
var s string = strconv.Itoa(number)

Comments#

  • Comments should explain why, NOT just what
  • Single-Line Comments - Used for short explanations.
// This is a single-line comment  
x := 10
  • Multi-Line Comments - Used for longer explanations or temporarily disabling code.
/*  
This is a  
multi-line comment  
*/
  • Documentation Comments (Important) - Go uses comments to generate documentation automatically.
  • Documentation comments:
    • start with the name of the thing being described
    • end with a period
// Add returns the sum of two integers.  
func Add(a, b int) int {  
    return a + b  
}
  • Package Comments - Every package should have a package-level comment.
  • Placed before the package declaration.
// Package mathutils provides basic math helper functions.  
package mathutils

“fmt” package#

  • Mainly used for
    • printing to console
    • formatting strings
    • reading user input
  • Print, Println & printf
fmt.Print("Hello")  
fmt.Print("World")
// HelloWorld

fmt.Println("Hello")  
fmt.Println("World")
/*
Hello  
World
*/

fmt.Printf("Age: %d", 25)
// Age: 25
  • printf formatters
VerbMeaning
%vDefault value representation
%+vStruct with field names
%#vGo syntax representation
%TType of value
%dInteger
%fFloating-point number
%sString
%tBoolean
%pPointer address
  • formatted string instead of printing.
msg := fmt.Sprintf("User: %s Age: %d", "John", 25)
  • Formatted error
err := fmt.Errorf("invalid user id: %d", id)
  • Reading
  • Scan - Reads input from console.
  • Scanln - Reads input until newline.
  • Scanf - Reads formatted input.
// scan with default space delimiter
var name string  
fmt.Scan(&name)

// scan with \n as delimiter
fmt.Scanln(&name)

// scan specific type
var age int  
fmt.Scanf("%d", &age)