1 Function Basics

In programming, a function is a piece of code that accomplishes a specific task and can have input parameters and return values. In the Go language, functions are widely used to organize and reuse code. Using functions effectively can make the code more concise, highly readable, and easy to maintain.

Functions are a core component of the Go language, and understanding how to declare and define functions is crucial for writing efficient and readable code.

2 Defining Functions

2.1 Function Declaration

In the Go language, the general form of function declaration is:

func functionName(parameters) returnType {
    // Function body
}

Let's break down these components:

  1. The func keyword is used to declare a function.
  2. functionName is the name of the function, following the Go language's naming conventions. A function with a capital initial letter is exportable, meaning it is visible outside the package; a function with a lowercase initial letter is not exportable and can only be used within the same package.
  3. parameters is the list of parameters the function receives, separated by commas. The type of each parameter needs to be specified, and if multiple parameters have the same type, the type can be specified only once.
  4. returnType is the type of the function's return value. If the function doesn't return a value, this part can be omitted. If the function returns multiple values, they need to be enclosed in parentheses.

For example, we can declare a simple function to calculate the sum of two integers:

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

In this example, the function name is Add, it takes two parameters of type int (a and b), and it returns their sum with the return type int.

2.2 Parameter List

The parameter list defines which parameters the function accepts and the type of each parameter. Parameters are a special kind of variable used to pass data to a function.

func Greet(name string, age int) {
    fmt.Printf("Hello, %s! You are %d years old.\n", name, age)
}

In this Greet function, name and age are parameters. The type of name is string, and the type of age is int. When calling this function, actual values must be provided for these parameters.

2.3 Return Types

Functions can return computed results, and the return type defines the data type of the function's return value. Functions can have no return value, or they can have one or more return values.

If a function has multiple return values, their types should be enclosed in parentheses during declaration:

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

The Divide function here returns two values: the quotient and an error message. If the divisor is zero, an error is returned.

2.4 Variadic Parameters

In Golang, when we are not sure how many parameters the caller will pass when defining a function, we can use variadic parameters. Variadic parameters are indicated by an ellipsis ..., meaning the function can accept any number of parameters. This is very useful when dealing with an uncertain amount of data or implementing certain types of functions such as formatting, summarizing, or iterating functionalities.

Application Scenarios

Variable parameters are commonly used in the following scenarios:

  1. String concatenation: such as the functions fmt.Sprintf and strings.Join.
  2. Array/slice processing: when dealing with arrays or slices of variable lengths, such as calculating the sum or concatenating multiple slices.
  3. Logging and error handling: when handling multiple errors or recording multiple log information, such as log.Printf or custom error summary functions.
  4. Helper functions: used in APIs or libraries to provide users with more flexible ways to call functions.

Using Variable Parameters

Here's a simple example of using variable parameters:

// Define a function that takes a variable number of integer parameters and returns their sum
func Sum(nums ...int) int {
    total := 0
    for _, num := range nums {
        total += num
    }
    return total
}

func main() {
    // You can pass any number of parameters
    fmt.Println(Sum(1, 2))          // Output: 3
    fmt.Println(Sum(1, 2, 3, 4))    // Output: 10
    
    // You can also pass a slice and use the ellipsis after the slice
    numbers := []int{1, 2, 3, 4, 5}
    fmt.Println(Sum(numbers...))    // Output: 15
}

In the Sum function, nums is a slice of integers that contains all the parameters passed to the Sum function. We can use a range loop to iterate through this slice and calculate the sum.

It's worth noting that variable parameters inside the function are actually a slice, so all slice-related operations can be used. When you need to pass a slice as a variable parameter to a function, just add an ellipsis ... after the slice.

Using variable parameters can make function calls more flexible and concise, but it will also have a slight impact on performance, as using variable parameters involves slice creation and memory allocation. Therefore, caution should be exercised when strict performance requirements are in place.

Tip: Detailed explanation about slices will be provided in later chapters. If you have experience with other programming languages, you can temporarily think of them as arrays.

3 Calling Functions

3.1 Basic Function Calls

Calling a function means executing the code of the function. In Go, calling a defined function is very straightforward, just use the function name and pass the appropriate parameters. For example:

result := add(3, 4)
fmt.Println(result)  // Output: 7

In this example, the add function is called and two integers are passed as parameters, and then the returned result is assigned to the result variable.

3.2 Passing Parameters

When passing parameters to a function, Go defaults to using pass-by-value, which means passing a copy of the parameter value, and the original data will not be changed. However, if you want the function to modify external variables or for performance reasons, you can use pass-by-reference, such as using pointers or passing large structs.

Here are examples of pass-by-value and pass-by-reference:

// Example of pass-by-value
func double(val int) {
    val *= 2
}

// Example of pass-by-reference
func doublePtr(val *int) {
    *val *= 2
}

func main() {
    value := 3
    double(value)
    fmt.Println(value)  // Outputs 3, value remains unchanged

    doublePtr(&value)
    fmt.Println(value)  // Outputs 6, value doubled
}

In the example above, the double function tries to double the passed val, but it only doubles its copy, leaving the original value variable unchanged. However, the doublePtr function changes the value of the original variable by receiving a pointer to an integer variable as a parameter.