Go Concepts - Functions: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
Line 233: Line 233:
==Receivers==
==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 declarations are referred to as ''value receiver'' and ''pointer receiver'' respectively. The method invocation will also work with both target pointers and values - the compiler will reference or dereference as necessary:
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 declarations are referred to as ''value receiver'' and ''pointer receiver'' respectively.  
 
The method invocation will also work with both target pointers and values - the compiler will reference or dereference as necessary:


<pre>
<pre>

Revision as of 20:52, 12 April 2016

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_declaration>) <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 declarations are referred to as value receiver and pointer receiver respectively.

The method invocation will also work with both target pointers and values - the compiler will reference or dereference as necessary:

type thing struct {
    color string
}

//
// accessor - in this case pointer/value does not matter
//
func (t thing) displayFromValue() {
    fmt.Println("color", t.color)
}

//
// accessor - in this case pointer/value does not matter
//
func (tPtr *thing) displayFromPointer() {
    fmt.Println("color", tPtr.color)
}

...

t := thing{"blue"}

//
//  all these work fine
//

t.displayFromValue()
t.displayFromPointer()
(&t).displayFromValue()
(&t).displayFromPointer()

Receiver Declaration and Changing the State of the Instance the Method is Invoked Upon

If the method is supposed to change the state of the instance is invoked upon, a pointer receiver declaration must be used.

Invocation itself can be made both into a pointer or the value, both options will work and the state will be changed - as long as a pointer receiver is used in the method definition.

If a value receiver is used, the mutator will execute without any compile-time or runtime error, but the state will not be changed. The explanation is that a copy of the receiver is transferred to the method. For a value receiver, a copy of the value is transferred to the method, and the method mutates its own private copy; the change is not reflected on the value the copy was made from. For a pointer receiver, a copy of the pointer is transferred to the method, so the method mutates the original value - via its (copy of the) pointer.

Good mutator:

//
// good mutator: pointer receiver
//
func (tPtr *thing) changeColor(newColor string) {
    tPtr.color=newColor
}

...

t := thing{"red"}

// will display "red"
fmt.Println(t.color)


//
// both invocations (on value and on pointer) are valid
//

t.changeColor("blue")

// will display "blue"
fmt.Println(t.color)

(&t).changeColor("green")

// will display "green"
fmt.Println(t.color)

Invalid mutator:

//
// invalid mutator: value receiver
//
func (t thing) invalidChangeColor(newColor string) {
    t.color = newColor
}

...

t := thing{"red"}

// will display "red"
fmt.Println(t.color)

//
// both invocations (on value and on pointer) will execute but the invocation won't change the state
//

t.invalidChangeColor("blue")

// will display "red"
fmt.Println(t.color)

(&t).invalidChangeColor("green")

// will display "red"
fmt.Println(t.color)

Receivers and Interfaces

TODO

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.

Receiver Best Practice

If the method is not supposed to modify state, use a value receiver.

If the method is supposed to modify state, use a pointer receiver.

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 works 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")