2015年9月27日日曜日

Golang の receiver の種類について

Go にはクラスがありませんが、struct type に method を設定することで同じようなことができます。method とは receiver を持つ function のことです。

receiver には2つの種類があり、1つは value, そして pointer です。
package main

import (
    "fmt"
)

type document struct {
    title  string
    author string
}

func (d document) display() {
    fmt.Printf("Title: %s, Author: %s\n",
        d.title,
        d.author,
    )
}

func (d *document) setTitle(title string) {
    d.title = title
}

func main() {
    d1 := &document{"A project report", "John"}
    d1.display()

    d2 := document{"A sales report", "Paul"}
    d2.setTitle("A business report")
    d2.display()
}
上記の場合、display() は value receiver, setTitle() は pointer receiver を持ちます。

ここで document の instance が value / pointer に関わらず、2つの method が使えているように見えるのは、Go が method の receiver に合うようにしてくれるからです。

例えば d1.display() は (*d1).display() のように呼び出してくれます。
また、d2.setTitle() は (&d2).setTitle() といった具合です。

しかし、interface を使用する場合には注意が必要です。
package main

import (
    "fmt"
)

type displayer interface {
    display()
}

type document struct {
    title  string
    author string
}

func (d *document) display() {
    fmt.Printf("Title: %s, Author: %s\n",
        d.title,
        d.author,
    )
}

func displayDocument(d displayer) {
    d.display()
}

func main() {
    d := document{"A project report", "John"}
    displayDocument(d)
}
$ go build main.go
./main.go:29: cannot use d (type document) as type displayer in argument to displayDocument:
        document does not implement displayer (display method has pointer receiver)
この場合、コンパイルがエラーになってしまいます。

d := document{"A project report", "John"}
display() が pointer receiver なのに、d が value instance だからです。


Go のドキュメントには以下のようにあります。
The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T

どの type T の method set も receiver type T で宣言された method で構成されます。 pointer type *T に対応する method set は *T もしくは T で宣言された method set です。
分かりづらいですが、value の method set は value receiver のみという制限があるんですね。pointer の method set には value receiver, pointer receiver どちらも使用できます。

d := &document{"A project report", "John"}
このように修正すると、コンパイルが通るようになります。

なぜ、このような制限があるのかというと、アドレスが常に取得できるわけではないからです。
package main

import (
        "fmt"
)

type price int

func (p *price) appendYen() string {
        return fmt.Sprintf("\u00A5%d", *p)
}

func main() {
        fmt.Println(price(1000).appendYen())
}

$ go build main.go
./main.go:14: cannot call pointer method on price(1000)
./main.go:14: cannot take the address of price(1000)

0 件のコメント:

コメントを投稿