0%

A Tour Of Go/ Method and interface

Methods and interface

Method

a method is just a function with a receiver argument.

1
2
3
4
5
6
7
8
9
10
11
12
type Vertex struct {
X, Y float64
}

func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

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

You can declare a method on non-struct types, too.

1
2
3
4
5
6
7
8
9
10
11
12
13
type MyFloat float64

func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}

func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}

注意:同一名稱的函式在一個 block 只能宣告一個, 就算一個是普通函式, 另一個是 Mathod 也一樣

Q: You cannot declare a method with a receiver whose type is defined in another package ??

You can only declare a method with a receiver whose type is defined in the same package as the method.
You cannot declare a method with a receiver whose type is defined in another package (which includes the built-in types such as int).

pointer receiver

Methods with pointer receivers can modify the value to which the receiver points (as Scale does here).

Since methods often need to modify their receiver, pointer receivers are more common than value receivers.

With a value receiver, the method operates on a copy of the original value.

This is the same behavior as for any other function argument.

1
2
3
4
5
6
7
8
type Vertex struct {
X, Y float64
}

func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}

Pointers and functions

1
2
3
4
5
6
7
8
9
10
func Scale(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}

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

Methods and pointer indirection

1
2
3
4
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK

For the statement v.Scale(5), even though v is a value and not a pointer, the method with the pointer receiver is called automatically. That is, as a convenience, Go interprets the statement v.Scale(5) as (&v).Scale(5) since the Scale method has a pointer receiver.

compare method with function

Functions that take a value argument must take a value of that specific type:

1
2
3
4
5
func AbsFunc(v Vertex){}
// function
var v Vertex
fmt.Println(AbsFunc(v)) // OK
fmt.Println(AbsFunc(&v)) // Compile error!

Methods with value receivers take either a value or a pointer as the receiver when they are called:

1
2
3
4
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK

In this case, the method call p.Abs() is interpreted as (*p).Abs().

Choosing a value or pointer receiver

There are two reasons to use a pointer receiver:

  1. The method can modify the value that its receiver points to.
  2. To avoid copying the value on each method call.

    This can be more efficient if the receiver is a large struct, for example.

In general, all methods on a given type should have either value or pointer receivers, but not a mixture of both.

We’ll see why over the next few pages.

Interfaces

An interface type is defined as a set of method signatures.

A value of interface type can hold any value that implements those methods.

Q: 指針類型跟非指針類型的方法是否符合某介面的條件會分開來看 >> 使用時要注意

雖然從物件呼叫方法的時候可以不管是否為指針, go 會處理
但若變數使用介面當作類型, 這時候使用的是指針或者非指針就有差異
如果符合介面定義的方法, 他的接收者都是指針類型, 在非指針類型沒有符合該介面的方法時, 指定時就會報錯

Note: There is an error in the example code on line 22.
Vertex (the value type) doesn’t implement Abser because the Abs method is defined only on *Vertex (the pointer type).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import (
"fmt"
"math"
)

type Abser interface {
Abs() float64
}

func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}

a = f // a MyFloat implements Abser
a = &v // a *Vertex implements Abser

// In the following line, v is a Vertex (not *Vertex)
// and does NOT implement Abser.
a = v

fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}

type Vertex struct {
X, Y float64
}

func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

Interface are implemented implicitly

A type implements an interface by implementing its methods. There is no explicit declaration of intent, no “implements” keyword.

Implicit interfaces decouple the definition of an interface from its implementation, which could then appear in any package without prearrangement.

Interface values

Under the hood, interface values can be thought of as a tuple of a value and a concrete type:

1
(value, type)

method on an interface value executes the method of the same name on its underlying type.

Interface values with nil underlying values

If the concrete value inside the interface itself is nil, the method will be called with a nil receiver.

In some languages this would trigger a null pointer exception, but in Go it is common to write methods that gracefully handle being called with a nil receiver (as with the method M in this example.)

Note that an interface value that holds a nil concrete value is itself non-nil.

Nil interface values

A nil interface value holds neither value nor concrete type.

Calling a method on a nil interface is a run-time error because there is no type inside the interface tuple to indicate which concrete method to call.

1
2
3
4
5
6
7
8
9
10
11
12
13
type I interface {
M()
}

func main() {
var i I
describe(i)
i.M() // error
}

func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}

The empty interface > go 語言的 any type

The interface type that specifies zero methods is known as the empty interface:

1
interface{}

An empty interface may hold values of any type. (Every type implements at least zero methods.)
Empty interfaces are used by code that handles values of unknown type.
For example, fmt.Print takes any number of arguments of type interface{}.

Type assertions

A type assertion provides access to an interface value’s underlying concrete value.

1
t := i.(T)

This statement asserts that the interface value i holds the concrete type T and assigns the underlying T value to the variable t.

If i does not hold a T, the statement will trigger a panic.

To test whether an interface value holds a specific type, a type assertion can return two values: the underlying value and a boolean value that reports whether the assertion succeeded.

1
t, ok := i.(T)

If i holds a T, then t will be the underlying value and ok will be true.
If not, ok will be false and t will be the zero value of type T, and no panic occurs.

Note the similarity between this syntax and that of reading from a map.

Type switches

A type switch is a construct that permits several type assertions in series.

A type switch is like a regular switch statement, but the cases in a type switch specify types (not values), and those values are compared against the type of the value held by the given interface value.

1
2
3
4
5
6
7
8
switch v := i.(type) {
case T:
// here v has type T
case S:
// here v has type S
default:
// no match; here v has the same type as i
}

The declaration in a type switch has the same syntax as a type assertion i.(T), but the specific type T is replaced with the keyword type.

This switch statement tests whether the interface value i holds a value of type T or S.
In each of the T and S cases, the variable v will be of type T or S respectively and hold the value held by i.
In the default case (where there is no match), the variable v is of the same interface type and value as i.

fmt.Stringers Interface

One of the most ubiquitous interfaces is Stringer defined by the fmt package.

1
2
3
type Stringer interface {
String() string
}

A Stringer is a type that can describe itself as a string.
The fmt package (and many others) look for this interface to print values.

Errors

Go programs express error state with error values.

The error type is a built-in interface similar to fmt.Stringer:

1
2
3
type error interface {
Error() string
}

(As with fmt.Stringer, the fmt package looks for the error interface when printing values.)

Functions often return an error value, and calling code should handle errors by testing whether the error equals nil.

1
2
3
4
5
6
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
return
}
fmt.Println("Converted integer:", i)

A nil error denotes success; a non-nil error denotes failure.

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 example code creates a strings.Reader and consumes its output 8 bytes at a time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
}
}
}

Images

Package image defines the Image interface:

1
2
3
4
5
6
7
package image

type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}

Note: the Rectangle return value of the Bounds method is actually an image.
Rectangle, as the declaration is inside package image.
(See the documentation for all the details.)

The color.Color and color.Model types are also interfaces, but we’ll ignore that by using the predefined implementations color.RGBA and color.RGBAModel. These interfaces and types are specified by the image/color package