Skip to content

Basic of Functions

Basic Syntax

func funcName(param1, param2 int, param3 string) (int, error) {
    ....
}

Parameters

Functions can take zero or more parameters and are declared with their type. When function parameters share the same type, you can declare the type once for all of them, as param1 and param2 in the example above show.

Call by Value

When you pass parameters to a function, they are typically passed by copy. This means that a copy of the value is made, and any changes you make to the parameter within the function do not affect the original value outside of the function. To change the parameters passed, use pointers and pointers receivers.

Maps and Slices are different...

Maps and Slices are both implemented with pointers. This works for parameters or struct fields of map and slice type. Changes made to a map are reflected, for slices, you can modify any element but can't lengthen the slice.

Checking Mutations on Params

run command
$ go run src/function/basic.go
Memory allocated for num1: 0xc0000120c8
    Memory allocated for num (inside print_double): 0xc0000120f0
    Double:  10
After call print_double:  5
package main

import "fmt"

func print_double(num int) {
    num = num * 2
    fmt.Printf("    Memory allocated for num (inside print_double): %p\n", &num)
    fmt.Println("   Double: ", num)
}

func sum(num1, num2 int) int {
    return num1 + num2
}

func main() {
    num1 := 5
    fmt.Printf("Memory allocated for num1: %p\n", &num1)
    print_double(num1)
    fmt.Println("After call print_double: ", num1)
}

Pointers Params

When a pointer is passed to a function, the function receives a copy of the pointer. However, this pointer still points to the original data, which means that the original data can be changed. Using pointers as parameters has some implications.

First Implication

When passing a nil pointer to a function, making the value non-nil is impossible. Since Go is call-by-value, it's not possible to change the memory address. It's possible to reassign the value only if a value is already assigned to the pointer.

Second Implication

If you want the change of the memory pointed by a pointer parameter to persistent after exiting the function, it's necessary to dereference the pointer and set the value. Changing the pointer, the copy is changed and not the original.

Dereferencing puts the new value in the memory location pointed to by both the original and the copy.

Change the Memory of Pointer Parameter

run command
$ go run src/function/pointer_param.go
10
20
package main

import "fmt"

func wrongUpdate(p *int) {
    num2 := 20
    p = &num2
}

func Update(p *int) {
    *p = 20
}

func main() {
    x := 10
    wrongUpdate(&x)
    fmt.Println(x)
    Update(&x)
    fmt.Println(x)
}

Named and Optional Parameters

Go doesn't have support for named and optional parameters. To simulate named and optional parameters, it's necessary to create a struct with the desired parameters and pass the struct to the function.

Variadic Param

Go allows you to define functions that can accept a variable number of arguments. You use the ... notation before the type of the last parameter to define variadic parameter. The veriadic parameter is a slice of the specified type and can be used just like any other slice.

The variadic parameter must be the last or the only parameter in the input parameter list.

Receiving a Variadic Parameter

run command
$ go run src/function/func_variadic.go
numbers param:  [1 2 3 4 5 6 7 8 9]
Sum Result : 45
package main

import "fmt"

func sum(numbers ...int) int {
    fmt.Println("numbers param: ", numbers)
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

func main() {
    result := sum(1, 2, 3, 4, 5, 6, 7, 8, 9)
    fmt.Println("Sum Result :", result)
}

Return

Functions can return zero or more values.

Multiple Return Values

Functions in Go can indeed return multiple values. If a function return multiple values, the function must return all values.

Ignoring return value

The _ is used to ignore a return value when the value is not necessary.

Multiple Return

run command
$ go run src/function/return.go
result is: 3
result is: 4
result is even!!!
package main

import "fmt"

func sum_check_even(num1, num2 int) (int, bool, error) {
    result := num1 + num2
    is_even := result%2 == 0
    return result, is_even, nil
}

func main() {
    result, is_even, _ := sum_check_even(1, 2)

    fmt.Printf("result is: %d\n", result)
    if is_even {
        fmt.Printf("result is even!!!\n")
    }

    result, is_even, _ = sum_check_even(2, 2)
    fmt.Printf("result is: %d\n", result)
    if is_even {
        fmt.Printf("result is even!!!\n")
    }

}

Named Return Values

Functions can declare named return values, which act as variables. This can make the code more readable by specifying what the function is returning. When return values are named, the variables are predeclared that can be used within the function to hold the return values.

However, besides that return values can be created, it is not necessary to use them in the return statement. Also, return values named can cause shadowing variables. These two scenarios can lead to bugs in the code.

Nameless and Named Returns

The _ can be used for nameless return values, when just some of the return values needs to be named.

Blank Returns

Using blank returns makes some changes to the function. When there’s invalid input, the function returns immediately. In this case, their zero values are returned. This makes it harder to understand data flow.

Named Returns

run command
$ go run src/function/return_named.go
result:  6  is_even:  true
result:  100  is_even:  false
result:  6  is_even:  true
package main

import "fmt"

func sum_check_even(num1, num2 int) (sum_result int, is_even bool, _ error) {
    sum_result = num1 + num2
    is_even = sum_result%2 == 0
    return sum_result, is_even, nil
}

func sum2(num1, num2 int) (sum_result int, is_even bool, _ error) {
    sum_result = num1 + num2
    is_even = sum_result%2 == 0
    return 100, false, nil
}

// NEVER USER BLANK RETURN
func sum_blank(num1, num2 int) (sum_result int, is_even bool) {
    sum_result = num1 + num2
    is_even = sum_result%2 == 0
    return
}

func main() {
    var num = 3
    result, is_even, _ := sum_check_even(num, num)
    fmt.Println("result: ", result, " is_even: ", is_even)

    result, is_even, _ = sum2(num, num)
    fmt.Println("result: ", result, " is_even: ", is_even)

    result, is_even = sum_blank(num, num)
    fmt.Println("result: ", result, " is_even: ", is_even)
}