Go Concepts - Functions
External
- Function type specification https://golang.org/ref/spec#Function_types
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:
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:
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:
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:
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
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.
- Length and capacity len(), cap()
- close()
- Allocation: new()
- Making slices, maps and channels: make()
- Appending to and copying slices: append(), copy()
- Deletion of map elements delete()
- Handling panics panic(), recover()
- Manipulating complex numbers: complex(), real(), imag()
- Bootstrapping print(), println()
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 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, the receiver must be declared as a pointer to the type in question.
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 the receiver is declared as pointer in the method definition.
If the receiver is declared as value, 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. If the receiver was declared as value, 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. If the receiver was declared as pointer, 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:
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:
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 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")