go go/book/learninggo

Introduction

Tip

Functions names are also variables, if declared outside of the block they are by default package scoped variables.

The main function has no input parameters or return values.

Just as in many other languages, functions in Go are values. The type of a function is built out of the keyword func and the types of the parameters and return values. This combination is called the signature of the function. Any function that has the exact same number and types of parameters and return values meets the type signature.

The default zero value for a function variable is nil

Call by Value, Call by Reference

Important

In Go, everything is passed by value.
But if the value includes a pointer, you can still modify what it points to

Go is a call by value language.
“Pass by value” → how arguments are given to a function.
“Call by value” → what happens during the function call.

func modifyFails(i int, s string, p person)
  • i, s, and p are local copies of the variables from main.
  • Any modification affects only the copies, not the originals.

In Java or Python, passing an object means passing a reference — so changing its internal state does affect the original.
In Go, a struct is copied when passed, so changing its fields doesn’t affect the original.

To modify the original variables, pass their addresses:

func modifyWorks(i *int, s *string, p *person) {
    *i = 99
    *s = "Changed"
    p.age = 100 // Shorter: no *p.age, Go handles it
}
 
func main() {
    p := person{}
    i := 2
    s := "Hello"
    modifyWorks(&i, &s, &p)
    fmt.Println(i, s, p)
}

Map and Slice Behaviour

Go - Composite Types

Even maps and slices are the Pass by value.
But,

  • When you pass a slice or map, you’re passing a value.
  • But that value contains a pointer under the hood.(Go - Arrays & Slices > Memory Allocation)
  • So when the function changes the contents that the pointer points to, you see the changes outside the function — even though the slice/map itself is passed by value.
func modify(s []int) {
    s[0] = 100      // âś… Modifies original array
    s = append(s, 200) // ❌ Only changes local copy of s
}
 
func main() {
    nums := []int{1, 2, 3}
    modify(nums)
    fmt.Println(nums) // [100 2 3] — not [100 2 3 200]
}

So when you pass a map:

  • The map header is copied (passed by value).
  • But it points to the same underlying data.
  • So changes to keys/values are visible outside.
func modify(m map[string]int) {
    m["x"] = 42 // âś… modifies the original map
}
 
func main() {
    m := make(map[string]int)
    modify(m)
    fmt.Println(m) // map[x:42]
}

Named and Optional Parameters

Go doesn't have name or optional input parameters.

# Named Parameters in Python
def connect(host, port):
    ...
connect(port=5432, host="localhost")
 
# Optional Parameters / Default Arguments
def greet(name="Guest"):
    print("Hello,", name)
 
greet()         # Hello, Guest
greet("Alice")  # Hello, Alice

Simulate

type Options struct {
    Host string
    Port int
}
 
func connect(opts Options) {
    fmt.Println("Connecting to", opts.Host, "on port", opts.Port)
}
 
// Call it like this:
connect(Options{Host: "localhost", Port: 5432})

Variadic Input Parameters (Function)

A variadic function in Go is a function that can accept a variable number of arguments of a specified type.

Warning

A variadic parameter must always be the last parameter in the function signature. You cannot have a regular parameter after a variadic one.

func addTo(base int, vals ...int) []int {
	out := make([]int, 0, len(vals))
	for _, v := range vals {
		out = append(out, base+v)
	}
	return out
}
 
func main() {
	fmt.Println(addTo(3))
	fmt.Println(addTo(3, 2))
	fmt.Println(addTo(3, 2, 4, 6, 8))
	a := []int{4, 3}
	fmt.Println(addTo(3, a...))
	fmt.Println(addTo(3, []int{1, 2, 3, 4, 5}...))
}
 
// Output
[]
[5]
[5 7 9 11]
[7 6]
[4 5 6 7 8]

Return Values

Multiple Return Values

Use _ whenever you don’t need to read a value that’s returned by a function. Surprisingly, Go does let you implicitly ignore all of the return values for a function.

Named Return Values

You must surround named return values with parentheses, even if there is only a single return value.

Named return values are initialized to their zero values when created. This means that you can return them before any explicit use or assignment.

If you want to name only some of the return values, you can do so by using _ as the name for any return values you want to remain nameless.

func divAndRemainder(num, denom int) (result int, remainder int, err error) {
	if denom == 0 {
		err = errors.New("cannot divide by zero")
		return result, remainder, err
	}
	result, remainder = num/denom, num%denom
	return result, remainder, err
}

Blank / Naked Returns (đź”´ Never use this)

Anonymous function

https://www.practical-go-lessons.com/chap-24-anonymous-functions-and-closures

Inner functions are anonymous; they don’t have a name. You declare an anonymous function with the keyword func immediately followed by the input parameters.

func main() {
	f := func(j int) {
		fmt.Println("printing", j, "from inside of an anonymous function")
	}
	for i := 0; i < 5; i++ {
		f(i)
	}
}

To create an anonymous function, we use a function literal.

package main
import "fmt" 
func main() { 
	// an anonymous function 
	func() { 
		// the instructions of the anonymous function 
		fmt.Println("I am anonymous function") 
	}() 
}
func(msg string) {
	fmt.Println(msg)
}("Hello Anonymous")

Function as Parameters

Since functions are values and you can specify the type of a function using its parameter and return types, you can pass functions as parameters into functions.

// sort by age
sort.Slice(people, func(i, j int) bool {
	return people[i].Age < people[j].Age
})
fmt.Println(people)

Go - Functions > Call by Value, Call by Reference#Map and Slice Behaviour

Function Types

  • Function types are first-class citizens in Go.
  • Functions with the same signature share the same function type.

Function type defines the signature of a function: parameter types and return types.

package main
 
import "fmt"
 
type MathOperation func(int, int) int
 
func add(a, b int) int { return a + b }
func subtract(a, b int) int { return a - b }
 
func main() {
 
	// Variables can hold functions matching the type:
    var op MathOperation
    op = add
    fmt.Println("Add:", op(5, 3))  // Output: Add: 8
 
    op = subtract
    fmt.Println("Subtract:", op(5, 3))  // Output: Subtract: 2
}

Functions as parameters or return values:

func apply(f MyFunc, i int, s string) bool {
    return f(i, s)
}
 
res := apply(func(i int, s string) bool { return s == "go" }, 2, "go")

Callback Functions

A callback function is a function that is passed as an argument to another function, which then executes the callback function at a certain point.

func greet(name string, callback func(string)) {
    fmt.Printf("Hello, %s!\n", name)
    callback(name) // invoke the callback with name
}
 
func main() {
    greet("Alice", func(n string) {
        fmt.Printf("Nice to meet you, %s!\n", n)
    })
}

Callback in Struct fields / Function as a struct field

type Button struct {
    OnClick func()
}
 
func main() {
    btn := Button{
        OnClick: func() {
            fmt.Println("Button clicked!")
        },
    }
 
    // Later when clicked:
    if btn.OnClick != nil {
        btn.OnClick()
    }
}

Closure

https://www.practical-go-lessons.com/chap-24-anonymous-functions-and-closures#closures
https://www.kelche.co/blog/go/closure/

An anonymous function defined into another function F can use elements that are not defined in its scope but in the scope of F.

Just as with any inner scope, you can shadow a variable inside a closure. If you change the code to:

func main() {
	a := 20
	f := func() {
		fmt.Println(a)
		a := 30
		fmt.Println(a)
	}
	f()
	fmt.Println(a)
}
 
// Output
20
30
20

Returning Functions from Functions / Higher-Order Functions

If you spend any time with programmers who use functional programming languages like Haskell, you might hear the term higher-order functions. That’s a fancy way to say that a function has a function for an input parameter or a return value.

While Go is not a purely functional language, it allows functions to be treated as first-class citizens. This means you can:

  • Assign functions to variables.
  • Pass functions as arguments.
  • Return functions from other functions.

higher-order functions are functions that either:

  1. Take other functions as arguments.
  2. Return functions as results.

Deferred Functions

A deferred function is executed after the surrounding function completes, typically for cleanup tasks (e.g., closing files or releasing resources).

func main() {
    defer fmt.Println("This will be printed last")
    fmt.Println("This will be printed first")
}

Go - Defer,Panic & Recover > Defer

You can supply a function that returns values to a defer, but there’s no way to read those values:

func example() {
	defer func() int {
		return 2 // there's no way to read this value
	}()
}

Usage of defer in database transaction cleanup

func DoSomeInserts(ctx context.Context, db *sql.DB, value1, value2 string) (err error) {
	tx, err := db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
 
	defer func() {
		if err == nil {
			err = tx.Commit()
		}
		if err != nil {
			tc.Rollback
		}
	}()
 
	_,err = tx.ExecContext(ctx, "INSERT INTO FOO (val) values $1, value1)
	if err != nil {
		return err
	}
 
	return nil
}