Function Declarations And Function Calls

Except the operator operations introduced in the last article, function operations are another kind of popular operations in programming. Function operations are often called function calls. This article will introduce how to declare functions and call functions.

Function Declarations

Let's view a function declaration.
func SquaresOfSumAndDiff(a int64, b int64) (s int64, d int64) {
	x, y := a + b, a - b
	s = x * x
	d = y * y
	return // <=> return s, d
}
We can find that, a function declaration is composed of several portions. From left to right,
  1. the first portion is the func keyword.
  2. the next portion is the function name, which must be an identifier. Here the function name is SquareOfSumAndDiff.
  3. the third portion is the input parameter declaration list, which is enclosed in a ().
  4. the fourth portion is the output (or return) result declaration list. Go functions can return multiple results. For this specified example, the result definition list is also enclosed in a ().
  5. the last portion is the function body, which is enclosed in {}. In a function body, the return keyword is used to end the normal forward execution flow and enter the exiting phase (see the next section).

In the above example, each parameter and result declaration is composed of a name and a type (the type follows the name). We can view parameter and result declarations as standard variable declarations without the var keywords. The above declared function has two parameters, a and b, and has two results, x and y. All the types of the parameters and results are int64. Parameters and results are treated as local variables within their corresponding function bodies.

The names in the result declaration list of a function declaration can be present or absent all together. Either case is used common in practice. If a result is defined with a name, then then result is called a named result, otherwise, it is called an unnamed result.

When all the results in a function declaration are unnamed, then, within the corresponding function body, the return keyword must be followed by a sequence of return values, each of them corresponds to a result declaration of the function declaration. For example, the following function declaration is equivalent to the above one.
func SquaresOfSumAndDiff(a int64, b int64) (int64, int64) {
	return (a+b) * (a+b), (a-b) * (a-b)
}

In fact, the names in the parameter declaration list of a function declaration can be also be omitted all together, if all the the prameters are never used within the corresponding function body. However, such circumstance is very rare in practice.

Although it looks the parameter and result variables are declared outside of the body of a function declaration, they are viewed as general local variables within the function body. The difference is that local variables with non-blank names declared within a function body must be ever used in the function body. Non-blank names of top-level local variables, parameters and results in a function declaration can't be duplicated.

Go doesn't support default parameter values. The initial value of each result is the zero value of its type. For example, the following function will always print 0 false.
func f() (x int, y bool) {
	println(x, y) // 0 false
	return
}

If the types of some successive parameters or results in a function declaration are the same one, then the types of the former parameters or results can be absent. For example, the above two function declarations are equivalent to
func SquaresOfSumAndDiff(a, b int64) (s, d int64) {
	return (a+b) * (a+b), (a-b) * (a-b)
}

Here, the type of the parameter a is not specified, for its type is the same as the parameter b. The same situation for the result s. And please note, even of both the two results are named, the return keyword can be followed with return values.

If the result declaration list in a function declaration only contains one unnamed result declaration, then the result declaration list doesn't need to be enclosed in a (). In particular, if the function declaration has no return results, then the result declaration list can be omitted totally. Ihe parameter declaration list can never be omitted, even if the number of parameters of the decalred function is zero.

Here are more function declaration examples.
func CompareLower4bits(m, n uint32) (r bool) {
	// The following two lines <=> return m&0xFF > n&0xff
	r = m&0xF > n&0xf
	return
}

// This function has no parameters.
func versionString() string {
	return "go1.10"
}

// This function has no results. And the parameter names
// are all amitted for we don't care about them.
func doNothing(string, int) {
}

One fact we have learned from the earlier articles in Go 101 is that the main entry function in each Go program is declared wothout parameters and results.

Please note that, functions must be declared at package level. In other words, a function can't be declared within the body of another function.

Function Calls

A declared function can be called through its name plus an argument list. The argument list must be enclosed in a (). Each argument corresponds to a parameter definition.

The type of an argument is not required to be identical with the corresponding parameter type. The only requirement for the argument is it must be assignable to the corresponding parameter type.

The following is a full example to show how to call other declared functions in the main entry function.
package main

func SquaresOfSumAndDiff(a int64, b int64) (int64, int64) {
	return (a+b) * (a+b), (a-b) * (a-b)
}

func CompareLower4bits(m, n uint32) (r bool) {
	r = m&0xF > n&0xf
	return
}

func versionString() string {
	return "v1.0"
}

func doNothing(string, int32) {
}

// Initialize a package-level variable with a function call.
var v = versionString()

func main() {
	println(v) // v1.0
	x, y := SquaresOfSumAndDiff(3, 6)
	println(x, y) // 81 9
	b := CompareLower4bits(uint32(x), uint32(y))
	println(b) // false
	// "Go" is deduced as a string, and 1 is deduced as an int32.
	doNothing("Go", 1)
}

Function calls can be deferred and invoked in new goroutines (green threads) in Go. Please read the next article deferred function calls and goroutines for details.

In Go, each function call has an exiting phase. The exiting phase of a function call starts when the called function is returned. In other words, when a function is returned, it is possible it still hasn't exited yet. The just mentioned deferred calls in the called function will be executed in the exiting phase.

Anonymous Functions

Go support anonymous functions. The definition of an anonymous function is almost the same as a function declaration, except there is no the function name portion in the anonymous function definition.

An anonymous function can be called right after it is defined. Example:
package main

func main() {
	// This anonymous fucntion has no parameters.
	x, y := func() (int, int) {
		println("This fucntion has no parameters.")
		return 3, 4
	}() // no arguments are needed.
	
	// This anonymous fucntion has no results.
	func(a, b int) {
		println("a*a + b*b =", a*a + b*b)
	}(x, y) // pass arguments.
	
	// This anonymous fucntion has no parameters and results.
	func() {
		println("x*x + y*y =", x*x + y*y)
	}() // no arguments are needed.
}

Please note that, for the last anonymous function is in the scope of the x and y variables declared above, it can use the two variable directly. Such functions are called closures. In fact, all custom functions in Go can be viewed as closures. This is why Go functions are as flexible as many dynamic languages.

Later, we will learn that an anonymous function can be assigned to a function value and call it at any time.

Built-in Functions

There are some built-in functions in Go, for example, the println and print functions. We can call these functions without importing any packages.

We can use the built-in real and imag functions to get the real and imaginary parts of a complex value. We can use the built-in complex function to produce a complex value. Please note, if any of the arguments of a call to any of the three functions are all constants, then the call will be evaluated at compile time, and the result value of the call is also a constant. In particular, if any of the arguments is an untype constant, then the result value is also an untyped constant. The call is viewed as a constant expression.

Example:
const c = complex(1.6, 3.3) // c is a untyped complex constant.

// The results of real(c) and imag(c) are both untyped floating-point values.

var a, b float32 = real(c), imag(c)
var d = complex(a, b) // d is deduced as a typed value of type complex64.
var e = c             // e is deduced as a typed value of type complex128.

// The results of real(d) and imag(d) are both typed values of type float32.
// The results of real(e) and imag(e) are both typed values of type float64.

More built-in functions will be introduced in other Go 101 articles later.

More About Functions

There are more about function related concepts and details which are not touched in the current article. We can learn those concepts and details in the article function types and values later.

Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies description flaws and code bugs.