[Golang] Function deep-dive

2021-11-21 hit count image

Let's see the details about Function more deeply and the various ways to use Function in Golang.

Outline

In this blog post, I will introduce the details about Function more deeply and the various ways to use Function in Golang. You can see the full source code of this blog post on the link below.

Function basic

If you want to know the basic of the function in Golang, see the previous blog post.

Variable argument list function

In Golan,g you can use the variable argument list function like the below.

fmt.Println()
fmt.Println(1)
fmt.Println(1, 2, 3, 4, 5)

You can make the variable argument list function with the ... keyword in Golang.

func FUNCTION_NAME(param ...TYPE) TYPE {
  // Code block
}

To check this, create the main.go file and modify it like the below.

package main

import "fmt"

func sum(nums ...int) int {
  fmt.Printf("nums type: %T\n", nums)

  sum := 0
  for _, num := range nums {
    sum += num
  }
  return sum
}
func main() {
  fmt.Println(sum(1, 2, 3, 4, 5))
  fmt.Println(sum(10, 20))
  fmt.Println(sum())
}

When you run the above code, you can see the following result.

# go run main.go
nums type: []int
15
nums type: []int
30
nums type: []int
0

defer

Golang provides the defer keyword that is the delay execution statement. The defer ensures that the statement is executed before the end of the function.

defer STATEMENT

To check this, modify the main.go file like the below.

package main

import "fmt"

func print() {
  defer fmt.Println("World!")
  fmt.Println("Hello")
}

func main() {
  print()
}

When the code is executed, you can seeh the following result.

# go run main.go
Hello
World!

The defer is mainly used for returning the OS resource like closing the file that was opend to use it. To check this, modify the main.go file like the below.

package main

import "os"

func readFile() {
  f, err := os.Open("file.txt")

  if err != nil {
    return
  }

  f.Close()
}

func readFileDefer() {
  f, err := os.Open("file.txt")
  defer f.Close()

  if err != nil {
    return
  }
}

func main() {
  readFile()
  readFileDefer()
}

If the defer is not used here and the the error occurs before calling the Close function, the Close function will not be called.

func readFile() {
  f, err := os.Open("file.txt")

  if err != nil {
    return
  }

  f.Close()
}

However, if the defer is used here, the Close function will be called.

func readFileDefer() {
  f, err := os.Open("file.txt")
  defer f.Close()

  if err != nil {
    return
  }
}

Function type variable

In Golang, the function can be also used as the type. In here, Function type variable means the variables have the function as value. The function also has the memory address, so we can assign it to the variable.

You can define the function type by Function signature like the below.

// Function
func add(a, b int) int {
  return a + b
}

// Signature
func (int int) int

To check this, modify the main.go file like the below.

package main

import "fmt"

func add(a, b int) int {
  return a + b
}

func multiple(a, b int) int {
  return a * b
}

func getOperator(op string) func(int, int) int {
  if op == "+" {
    return add
  } else if op == "*" {
    return multiple
  } else {
    return nil
  }
}

func main() {
  var op func(int, int) int

  op = getOperator("+")
  result := op(10, 20)
  fmt.Println(result)

  op = getOperator("*")
  result = op(10, 20)
  fmt.Println(result)
}

When the code is executed, you can see the following result.

# go run main.go
30
200

Function signature alias type

You can use the function type more simpley with the alias type and the function signature.

// Function signature alias type
type OperateFunc func(int, int) int

func getOperator(op string) OperateFunc {
}

To check this, modify the main.go file like the below.

package main

import "fmt"

type OperateFunc func(int, int) int

func add(a, b int) int {
  return a + b
}

func multiple(a, b int) int {
  return a * b
}

func getOperator(op string) OperateFunc {
  if op == "+" {
    return add
  } else if op == "*" {
    return multiple
  } else {
    return nil
  }
}

func main() {
  var op OperateFunc

  op = getOperator("+")
  result := op(10, 20)
  fmt.Println(result)

  op = getOperator("*")
  result = op(10, 20)
  fmt.Println(result)
}

When you execute the code, you can see the following result.

# go run main.go
30
200

The alias type makes it easier to use the function signature.

Function literal

In Golang, we can use the function literal(anonymous function).

f := func(a, b int) int {
}

To check this, modify the main.go file like the below.

package main

import "fmt"

type OperateFunc func(int, int) int

func getOperator(op string) OperateFunc {
  if op == "+" {
    return func(a, b int) int {
      return a + b
    }
  } else if op == "*" {
    return func(a, b int) int {
      return a * b
    }
  } else {
    return nil
  }
}

func main() {
  var op OperateFunc

  op = getOperator("+")
  result := op(10, 20)
  fmt.Println(result)

  op = getOperator("*")
  result = op(10, 20)
  fmt.Println(result)
}

When the code is executed, you can see the following result.

# go run main.go
30
200

In Golang, the function can’t have the state normally, but the function literal can have the state. To check this, modify the main.go file like the below.

package main

import "fmt"

func main() {
  i := 0

  f := func() {
    i += 10
  }

  i++

  f()

  fmt.Println(i)
}

When you execute the code, you can see the following result.

# go run main.go
11

Normal function can’t access the variables defined outside of the function. However, the function literal can access(capture) and use the variables defined outside of the function. The captured value is not copied value, but as a reference copy. In other words, it can be said that the pointer is copied.

To check this, modify the main.go file like the below.

package main

import "fmt"

func CaptureLoop1() {
  f := make([]func(), 3)
  fmt.Println("CaptureLoop1")

  for i := 0; i < 3; i++ {
    f[i] = func() {
      fmt.Println(i)
    }
  }

  for i := 0; i < 3; i++ {
    f[i]()
  }
}

func CaptureLoop2() {
  f := make([]func(), 3)
  fmt.Println("CaptureLoop2")

  for i := 0; i < 3; i++ {
    v := i
    f[i] = func() {
      fmt.Println(v)
    }
  }

  for i := 0; i < 3; i++ {
    f[i]()
  }
}

func main() {
  CaptureLoop1()
  CaptureLoop2()
}

When the code is executed, you can see the following result.

# go run main.go
CaptureLoop1
3
3
3
CaptureLoop2
0
1
2

The CaptureLoop1 refers to the i variable of the for directly, so 2 that is the last of i is printed. The CaptureLoop2 refers to the v variable that is copied from i and the v is newly created every time in the for loop, so you can see 0~2 are printed.

Dependency Injection

In Golang, you can use the function type for dependency injection(DI). Dependency injection is a technique that allows you to inject the business logic from the outside instead of defining it inside.

package main

import "fmt"

type PrintConsole func(string)

func PrintHello(p PrintConsole) {
  p("Hello, World!")
}

func main() {
  PrintHello(func(msg string) {
    fmt.Println(msg)
  })
}

WHen the code is executed, you can see the following result.

# go run main.go
Hello, World!

PrintHello doesn’t know what the role of PrintConsole is(what the business logic has). So, we can say PrintHello is injected dependency via PrintConsole.

Completed

Done! we’ve seen the function deeply, and how to use it. Next, try to use the techniques that I introduced to make awsome codes.

Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!

Posts