Go Series: Functions and their Parameters / Return Values

Apr 12, 2025 min read

This is the fifth post in the Go series. You can see a list of all of the posts in this series by visiting the Go Series tag. This week we look at functions in Go.

Functions are fundamental building blocks in Go, allowing developers to encapsulate code for reuse and clarity. They enable the definition of a set of instructions that can be called multiple times throughout your program.

Defining a Function

In Go, you define a function using the func keyword, followed by the function name, parameters (if any), return type (if any), and the function body enclosed in curly braces {}.

Here’s a simple example:

package main

import "fmt"

func greet(name string) {
    fmt.Printf("Hello, %s!\n", name)
}

func main() {
    greet("Alice")
    greet("Bob")
}

Here greet is a function that takes a single parameter name of type string. Inside greet, we use fmt.Printf to print a greeting message.

In the main function, we call greet with the argument "Alice" and we call it again with the argument "Bob". The main function is the entry point of the Go application and is what is called first.

So the application outputs two greetings and the code has been reused.

Hello, Alice!
Hello, Bob!

Function Parameters

Functions can have multiple parameters, each with a specific type. When defining multiple parameters of the same type, you can list the names first, followed by the type.

For example:

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

Here, add takes two parameters a and b, both of type int, and returns an int representing their sum.

Variadic Parameters

Go allows you to define functions with variadic parameters, enabling you to pass a variable number of arguments of the same type. You define a variadic parameter by using an ellipsis ... before the type.

For instance:

func sum(numbers ...int) int {
    total := 0
    for _, number := range numbers {
        total += number
    }
    return total
}

In this sum function:

  • numbers ...int indicates that sum can accept any number of int arguments and they well be available inside the function as slice []int called numbers.
  • Inside the function, we iterate over numbers using a for loop and accumulate the total.

Examples of calling sum with any number of integer arguments are like so:

func main() {
    fmt.Println(sum(1, 2, 3))        // Outputs: 6
    fmt.Println(sum(10, 20, 30, 40)) // Outputs: 100
}

Rule of variadic parameters

  • You can only have one variadic parameter in a function.
  • It must be the final parameter in the list.

This is because Go needs to know where the fixed parameters end and where the variable-length list begins. For example:

func log(level string, messages ...string) {
    for _, msg := range messages {
        fmt.Printf("[%s] %s\n", level, msg)
    }
}

This is valid — a fixed level parameter followed by a variadic messages parameter. But the reverse wouldn’t compile:

// Invalid: variadic parameter not last
func invalid(messages ...string, level string) {
    // ...
}

Go will raise a compile-time error if you try to do this.

Return Values

Functions in Go can return multiple values, which is a powerful feature that allows for elegant error handling and results reporting.

Here’s an example of a function that returns two values:

func divide(dividend, divisor float64) (float64, error) {
    if divisor == 0 {
        return 0, fmt.Errorf("cannot divide by zero")
    }
    return dividend / divisor, nil
}

In this divide function:

  • We take two float64 parameters: dividend and divisor.
  • The function returns two values: a float64 result and an error.
  • If the divisor is zero, we return an error using fmt.Errorf.
  • Otherwise, we return the result of the division and nil for the error.

You can use this function as follows:

func main() {
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
}

This will output:

Result: 5

If you attempt to divide by zero:

func main() {
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
}

The output will be:

Error: cannot divide by zero

Named Return Values

Go also supports named return values, which can make your functions more readable by specifying the names of the return variables in the function signature.

For example:

func rectangleDimensions(length, width float64) (area, perimeter float64) {
    area = length * width
    perimeter = 2 * (length + width)
    return
}

In this rectangleDimensions function:

We define two named return values: area and perimeter, both of type float64. Inside the function, we assign values to area and perimeter. The return statement without arguments returns the named values. You can call this function and use the returned values:

func main() {
    area, perimeter := rectangleDimensions(5, 3)
    fmt.Printf("Area: %.2f\n", area)
    fmt.Printf("Perimeter: %.2f\n", perimeter)
}

This will output:

Area: 15.00
Perimeter: 16.00

Wrapping Up

Functions are a cornerstone of Go programming, enabling you to write modular and reusable code. Understanding how to define functions, use parameters (including variadic parameters), and handle return values will significantly enhance your Go programming skills. As you continue exploring Go, you’ll find that functions provide a robust foundation for building efficient and maintainable applications.

Last Updated: Apr 12, 2025