Go Concepts - Functions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search

External

Internal

Function Definition

A function declaration starts with the func keyword, followed by the function name and its signature: input parameters and the results, followed by a series of statements in a block. If the function does not return anything, the result declaration can be missing.

The statements are applied on a number of inputs, resulting in a number of outputs. Same inputs will always result in the same outputs. Function do not depend on a type instance's state. From this perspective, functions are conceptually different from methods.

Function Signature

The parameters and the return types are known as the function's signature.

Syntax

func <name>(
    [parameter-identifier1] [type1], [parameter-identifier2] [type2], ...) 
    [(<return-type1>, [return-type2], ...)] {

   // function's body
   statement1
   statement2
   ...

   return <return-value1>, <return-value2>
}
func add(a int, b int) int {
   return a + b;
}

The return type declarations are optional, if the function does not return anything.

Multiple Return Values

A function may return one or more results:

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

It is idiomatic for Go to return an error value among the return values, see:

Error Handling Idioms

Return Types can Have Names

func f() (r int) {
   r = 1
   return
}

Varidic Functions

Variadic parameters are declared using ellipsis (...). A function can only have one variadic parameter.

func f(args ...<type>) <return-type> {

    //
    // range can be used to iterate over the individual parameters
    //
   for _, arg := range args {
        // ...
   }
}

Also see:

range

A slice can be "converted" into its individual components which can be passed as individual arguments to a variadic function, by appending an ellipsis (...) to the slice identifier:

s := []int{1, 2, 3}
f(s...)

Function Literals

There are cases when a function definition is needed in-line - to be passed as argument to another function, for example. In this case we use a function literal, which consists in the func keyword, followed by a signature, and the body. No name is required. Example:

func (i int, j int) int {
    return i + j
}

Also see:

Closures
Function Types

Anonymous Functions

An anonymous function is a function that is declared without a name. Anonymous functions are useful in implementing closures.

Anonymous function invocation example:


a := 10

func (i int) {
    fmt.Println(i)
}(a)

Functions and Variable Scopes

A function does not have access to variables declares in functions that call it.

A function does have access to package level variables declared in the function's package.

Also see:

Variable Scopes

Pass by Value vs. Pass by Reference

In Go, all variables are passed by value: when an argument is passed to a function, that argument is copied on the stack.

This is true for built-in types, including arrays, and for user defined types (structs). Because of that, if you want to change a struct inside a function, you should pass a pointer to that struct, not the struct itself.

Even for pointer variables, since the value of the pointer is a memory address, passing pointer variables is still considered pass by value.

Closures

Closures

defer

The defer keyword schedules the function it is invoked with to be executed immediately after the function it is invoked from exits, irrespective of how the function exits (even with a panic). The execution is done on the same thread.

defer is used in Go idioms where a resource needs to be closed after it was opened: it keeps the "close" call near the "open" call, so the code is easier to understand. The recommended way to do it is:

f, _ := os.Open(filename)
defer f.Close()

Built-in Functions

The built-in functions are available by default, without the need to import any package. They are what the specification calls pre-defined function identifiers. They give access to Go's internal data structures and their semantics depends on the arguments.

Special Functions

The init() Function

The init() function contains code executed on package initialization. All init() functions in any code file that is part of the program will be called before the main() function.

Each package has the ability to provide as many init() functions as necessary.

A way to trigger package initialization code to execute, even if none of the package identifiers is used in the source code, is to use the blank identifier _. For more details see using the blank identifier when importing a package.

An example of where init() is useful is with database drivers. They register themselves with the sql package with their init() function is executed at startup.

The main() Function

A main() function that takes no arguments and returns no value is the entry point in a Go program. Once the main() function exists, the program exits, regardless of whether there are other in-flight goroutines. More about the main() function, the main package and Go executables can be found here: compiling an executable.

Conversion between Types

Conversions between types look like function invocations, see Conversion between Types.

Methods

A method defines the behavior of a type, and it relies on the state of an instance of the type. The method will - and it is supposed to - change the state. From this point of view, the method is conceptually different from a function.

Lexically, methods are declared independently from the struct or the aliased type they are related to: they can declared in a different section of the file from where the type is declared, or in a different file altogether, as long as that file belongs to the same package.

A method is always exported by the package it is enclosed in.

Syntax

A method declaration is similar to a function's, except that in between the func keyword and the name of function we add an extra parameter named receiver. The receiver syntax is similar to a function's parameters - (name type):

func (<receiver>) <method-name> (...) {

    // the rest of declaration is similar to a function's
    ...
}

Example:

func (bag Bag) changeColor(color string) {
    ...
}


func (bagPtr *Bag) changeColor(color string) {
    ...
}

Receivers

A receiver is what turns a simple function into a method - the presence of the receiver with a function declaration indicates that the function becomes a method bound to the specified receiver type. When declaring receivers, both a value or a pointer can be used in the declaration. The method invocation will also work with both target pointers and values - the compiler will reference or dereference as necessary:

type user struct {
    name string
}

func (u user) displayFromValue() {
    fmt.Println("name", u.name)
}

func (u *user) displayFromPointer() {
    fmt.Println("name", u.name)
}

...
u := user{"Alice"}

// all these work fine
u.displayFromValue()
u.displayFromPointer()
(&u).displayFromValue()
(&u).displayFromPointer()


However, if a receiver was declared as a value, the method won't be able to


However, the way the receiver was declared (pointer vs. value) has implications on whether the method can (or not) change the state of the instance is applied to.

The Difference between Value Receivers and Pointer Receivers

Observation: if I use a value receiver, the method does not change the state of the instance the method was invoked with. Probably because the value is copied on the method's stack.

Value Receivers

Pointer Receivers

It is best practice to declare methods using pointer receivers, since many of the methods we implement need to manipulate the state of the value being used to make the method call.

Unlike when you call methods directly from values and pointers, when you call a method via an interface type value, the rules are different. Methods declared with pointer receivers can only be called by interface type values that contain pointers. Methods declared with value receivers can be called by interface type values that con- tain both values and pointers. See "Go in Action" page 50 for examples.

Method Invocation

Once a method was associated to the type, the type's instances or pointers to the type's instances allow invocation of the method using the . operator. The . operator work with both the instance and the pointer. For the method declaration above, both invocations that follow are valid:

var b Bag
bPtr := &b

b.changeColor("red")
bPtr.changeColor("blue")

Difference between Functions and Methods