Go Functions project code

Go Functions, Making Code Reusable

 

We’ve been using functions right since we wrote our very first code, but we haven’t addressed what Go Functions are. In programming, a Function (sometimes called a subroutine) is a way of grouping code together so that we can reuse it in other parts of our program. When we want to use that group of previously defined code we call the function using the name we associated to it.

The Println function from the fmt package we’ve been using in nearly all of our programs so far is an excellent example of the utility of Functions. Without Functions, we would have had to have written all of the code (which is quite a lot) used to display text on the screen ourselves, in every place we used Println.

Declaring Go Functions

Not only have we used Go Functions in every program we’ve written so far, but we’ve also declared a function in every program. The “main” Function is a Function that every Go program must have as it’s how Go knows where to start running your program.

1
2
3
func main () {
    fmt.Println("Hello, Function!")
}

Go Functions are declared using the func keyword. Following the func keyword, we name our function, just like naming a variable. Next are the function argument declarations enclosed in parentheses, which we’ll cover later. The last part of the function declaration is the actual function body, the code we want to run when we call our function. The body of our Function is all of the code enclosed by the braces ({ }) at the end of our function declaration.

Go Function Arguments

The arguments for our Go Functions are defined as a list of variables separated by commas (,) including the variable types.

1
2
3
func addPrint(one int, two int) {
    fmt.Println(one + two)
}

When declaring multiple arguments of the same type you can skip the type for all but the last argument.

1
2
3
func addPrint(one, two int) {
    fmt.Println(one + two)
}

Calling Go Functions

At this point, you should feel pretty comfortable calling Go Functions. To call a Function you just have to use the Function name and provide any required arguments inside the parentheses. You must include the parentheses to call a Function even if the Function has no arguments.

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
    "fmt"
)

func addPrint(one, two int) {
    fmt.Println(one + two)
}

func main () {
    addPrint(5, 7)
}

Go Functions With Return Values

So far all of our Go Functions do something independent of all of the rest of our code. Println prints to the screen but we can’t use its result in our code, it merely Prints to the screen and then ends and our program moves on.

It’s possible to create a Go Function that does something and then returns the results of its action. We can then set that result to a variable or even directly feed the return value into another function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
    "fmt"
)

func repeatXTimes(repeat string, times int) string {
    var out string

    for i := 0; i < times; i++ {
        out += repeat + "\n"
    }

    return out
}

func main () {
    fmt.Print(repeatXTimes("hello", 3))
}

We define what types of values our Go Functions return in between the arguments and the Function body. In this case, we’ve created the repeatXTimes function which takes a string and an int as arguments and returns a string. The function loops the number of times we specified in our arguments setting a variable to our string argument repeated. We then output that variable from our Function using the return keyword.

You can have multiple return statements in your Function, such as in an if or switch expression. However, once your Functions execute a return, they won’t run any of the other code in the Function definition.

You can return multiple values from your functions by defining a list of types your function will return instead of providing a single type. When returning multiple values, you must enclose your return types in parentheses.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
    "fmt"
)

func repeatXTimes(repeat string, times int) (string, int) {
    var out string

    for i := 0; i < times; i++ {
        out += repeat + "\n"
    }

    return out, len(out)
}

func main () {
    repeated, length := repeatXTimes("hello", 3)

    fmt.Println(repeated)

    fmt.Printf("repeated was %d characters long\n", length)
}

Scope and Go Functions

It’s worth talking about scope in the context of Functions. Functions have the same scope rules we’ve discussed previously. Functions have access to variables that are declared inside of the function body. Function arguments are considered to be a part of the Function body. Functions also have access to any variables declared inside blocks the function is declared in. In this instance, we declared repeatXTimes outside of main, so it does not have access to any variables declared in main. However, if we defined repeatXTimes inside of the block of main, then repeatXTimes would have access to those variables.

The other element of scope to understand about Functions is about modifying Function arguments inside of the Function. Remember that I mentioned that a Function argument is considered to be in the scope of the Function definition? That’s because we’re creating a new variable to hold our Function argument. Go Functions are what’s called “pass by value”. We’re creating a new variable and assigning a copy of the value of the argument to it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
    "fmt"
)

func repeatXTimes(repeat string, times int) (string, int) {
    var out string

    for i := 0; i < times; i++ {
        out += repeat + "\n"
    }

    repeat = out

    return out, len(out)
}

func main () {
    repeat := "hello"

    fmt.Println("Before function:", repeat)

    repeated, length := repeatXTimes(repeat, 3)

    fmt.Println(repeated)
    fmt.Println("After function:", repeat)

    fmt.Printf("repeated was %d characters long\n", length)
}

Because of this copying, any change we make to the argument doesn’t affect any variables outside of the Function. A slight exception to this is when passing a Slice as an argument. I’ve mentioned that a Slice contains an underlying Array to hold its values. The Slice itself is copied to a new Slice when you pass it as an argument, but both Slices contain the same underlying array. Because both Slices contain the same underlying array, any changes you make to the data in the Slice can have effects outside of the function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
    "fmt"
)

func updateWorld(in []string) {
    in[1] = "Go!"
}

func main () {
    mySlice := []string{"Hello", "World!"}

    fmt.Println("Before function:", mySlice)

    updateWorld(mySlice)

    fmt.Println("After function:", mySlice)
}

You’ll only see these changes occur to the underlying array if Go does not need to change the array contained in the slice. In our example, we modified an element of the array which did not cause a new array to be created. However, if Go needs to create a new array within the Slice, such as to grow the Slice when we add an element then you won’t see this behavior. When Go creates a new array to hold the additional value now the Slices contain different arrays.

Chris Sotherden

Originally a native of upstate NY, I relocated to North Carolina in 2013 where I've since helped open the new R&D headquarters for Optanix. I've been with Optanix (formerly ShoreGroup) for over ten years, where I've worked my way up from entry-level to Lead Architect. I taught myself programming in high school and am an all around technology nerd from the design of programming languages and compilers, to quantum computing, and rockets. Outside of technology I've competed in powerlifting and medium distance running, as well as smoking some mean BBQ. Oh, my partner and I also breed, show, and sell fancy goldfish!

>