Functions and methods

Functions in Go are identified by the func keyword, followed by an identifier, eventual arguments, and return values. Functions in Go can return more than one value at a time. The combination of arguments and returned types is referred to as a signature, as shown in the following code:

func simpleFunc()
func funcReturn() (a, b int)
func funcArgs(a, b int)
func funcArgsReturns(a, b int) error

The part between brackets is the function body, and the return statement can be used inside it for an early interruption of the function. If the function returns values, then the return statement must return values of the same type.

The return values can be named in the signature; they are zero value variables and if the return statement does not specify other values, these values are the ones that are returned:

func foo(a int) int {        // no variable for returned type
if a > 100 {
return 100
}
return a
}

func bar(a int) (b int) { // variable for returned type
if a > 100 {
b = 100
return // same as return b
}
return a
}

Functions are first-class types in Go and they can also be assigned to variables, with each signature representing a different type. They can also be anonymous; in this case, they are called closures. Once a variable is initialized with a function, the same variable can be reassigned with another function with the same signature. Here's an example of assigning a closure to a variable:

var a = func(item string) error { 
if item != "elixir" {
return errors.New("Gimme elixir!")
}
return nil
}

The functions that are declared by an interface are referred to as methods and they can be implemented by custom types. The method implementation looks like a function, with the exception being that the name is preceded by a single parameter of the implementing type. This is just syntactic sugar  the method definition creates a function under the hood, which takes an extra parameter, that is, the type that implements the method.

This syntax makes it possible to define the same method for different types, each of which will act as a namespace for the function declaration. In this way, it is possible to call a method in two different ways, as shown in the following code:

type A int

func (a A) Foo() {}

func main() {
A{}.Foo() // Call the method on an instance of the type
A.Foo(A{}) // Call the method on the type and passing an instance as argument
}

It's important to note that a type and its pointer share the same namespace, so the same method can be implemented just for one of them. The same method cannot be defined for both the type and for its pointer, since declaring the method twice (for a type and its pointer) will produce a compile error (method redeclared). Methods cannot be defined for interfaces, only for concrete types, but interfaces can be used in composite types, including function parameters and return values, as we can see in the following examples:

// use error interface with chan
type ErrChan chan error
// use error interface in a map
type Result map[string]error

type Printer interface{
Print()
}
// use the interface as argument
func CallPrint(p Printer) {
p.Print()
}

The built-in package already defines an interface that is used all over the standard library and in all the packages that are available online – the error interface:

type error interface {
Error() string
}

This means that whatever type has an Error() string method can be used as an error, and each package can define its error types according to its needs. This can be used to concisely carry information about the error. In this example, we are defining ErrKey, which is specifying that a string key was not found. We don't need anything else besides the key to represent our error, as shown in the following code:

type ErrKey string

func (e Errkey) Error() string {
returm fmt.Errorf("key %q not found", e)
}

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.148.104.124