Sage-Code Laboratory
index<--

Go Functions

Go functions are defined using keyword "func". Usually a function has a name and can have zero or more "receivers" declared in enclosed parenthesis after the function name. When a function is called, we can send values to the function receivers using "arguments". Functions can produce zero, one or more results. Let's study more about this.
function

Function Concept

Pattern:

In general, a function looks like this:

func function_name(receiver type, receiver type, ...receiver type) result_type {
    // statements
    ...
    return expression
}

Example:

Let's define a function into a real program to add two numbers.

//file func_add.go
package main

import "fmt"

// user defined function
func add(x int, y int) int {
    return x + y
}

// declare the main function and call add()
func main() {
    fmt.Println(add(42, 13)) //first call
    fmt.Println(add(40, 2))  //second call
}

Function result

Notice a function can return a result of a particular type. The result type is specified after the list of arguments. In the example above the function add has the result type int. The result can be created using the "return" statement. This is the normal exit point from a function.

One result:

When two or more consecutive named function receivers share same type, you can omit the type from all but the last.The result type must be declared after the parenthesis before the block begins.

func add(x, y int) int {
	return x + y
}

Multiple results:

A function can return one or multiple results. In next example we use "return" with two expressions separated by coma. We capture the results by using a list of variables as left argument in the assign statement.

//file swap_func.go
package main

import "fmt"

//function with two results
func swap(x, y string) (string, string) {
	return y, x
}

func main() {
	a, b := swap("hello", "world")
	fmt.Println(a, b)
}

Value Receivers

You can declare methods with value receivers. This means you can have a function that accept values when it is called. Value receivers have role of input parameters. One function can have multiple receivers, defined in round parenthesis after function name.

Notes:

Variadic Receiver

By using 3 dots in front of type (...), we can declare a variadic parameter that can receive multiple values. This is the only way a function can receive optional arguments. When a function have several receivers only the last receiver can be variadic.

Example:

//file varargs.go
package main

import "fmt"

func main() {

    variadicExample()
    variadicExample("red", "blue")
    variadicExample("red", "blue", "green")
    variadicExample("red", "blue", "green", "yellow")
}

func variadicExample(s ...string) {
    fmt.Println(s)
}

Pointer Receivers

You can declare methods with pointer receivers. This means the receiver type has the literal syntax *T for some type T. (Where T cannot itself be a pointer such as *int.). Pointer receivers have role of input output parameters.

//file ptr_params.go
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

//Abs method here is using a value receiver not a pointer receiver
func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// scale method here is defined on pointer receiver *Vertex.
func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	v.Scale(10)
	fmt.Println(v.Abs())
}

Methods with pointer receivers can modify the value to which the receiver points. Since methods often need to modify their receiver, pointer receivers are more common than value receivers.

If we do not use "*" we have a value receiver. In this case the Scale method operates on a copy of the originalVertex value. (This is the same behavior as for any other function argument.) The Scale method must have a pointer receiver to change the Vertex value declared in the main function.

Function values

In Go functions can be values and can be assigned to variables. They can be passed around just like other values. Function values may be used as arguments to call other functuins and can be returned as results from other functions.

//file func_val.go
package main

import (
	"fmt"
	"math"
)

func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}

func main() {
	hypot := func(x, y float64) float64 {
		return math.Sqrt(x*x + y*y)
	}
	fmt.Println(hypot(5, 12))

	fmt.Println(compute(hypot))
	fmt.Println(compute(math.Pow))
}

Closures

A closure is a function value that references variables from outside its body. The function may access and assign to the referenced variables; in this sense the function is "bound" to the variable.

For example, the "adder" function returns a closure. Each closure is bound to its own "sum" variable:

//file closure.go
package main

import "fmt"

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),
        )
    }
}

Readers

The io package specifies the io.Reader interface, which represents the read end of a stream of data.

The Go standard library contains many implementations of these interfaces, including files, network connections, compressors, ciphers, and others. The io.Reader interface has a Read method:

func (T) Read(b []byte) (n int, err error)

Read populates the given byte slice with data and returns the number of bytes populated and an error value. It returns an io.EOF error when the stream ends. The next example code creates a strings. Reader and consumes its output 8 bytes at a time.

//file string_reader.go
package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	r := strings.NewReader("Hello, Reader!")

	b := make([]byte, 8)
	for {
		n, err := r.Read(b)
		fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
		fmt.Printf("b[:n] = %q\n", b[:n])
		if err == io.EOF {
			break
		}
	}
}

Read next: Go Objects