これをやる
型にメソッドを定義
func
と関数名の間にreceiverを定義することで型にメソッドを定義をできる。
メソッドはreceiverを持った関数のことである。
package main import ( "fmt" ) func (test Test) Double() int { return test.X * test.X } type Test struct { X int } func main() { test := Test{2} fmt.Println(test.double()) }
structでない型にも定義できる。
package main import ( "fmt" ) type MyInt int func (val MyInt) Double() MyInt { return val * val } func main() { value := MyInt(2) fmt.Println(value.Double()) }
receiverを持ったメソッドは型が定義されたパッケージ内でのみ定義できる。
Pointer receivers
Pointer receiversと共に定義したメソッドでは、構造体のインスタンス(インスタンスという呼び名でいいのか?)に対して変更が可能。
package main import ( "fmt" ) type Test struct { value int } func (v *Test) mutable() { v.value = v.value * 2 } func (v Test) immutable() { v.value = v.value * 2 } func main() { v := Test{3} v.immutable() // ここでvの値は変更されない fmt.Println(v) v.mutable() // ここでは変更される fmt.Println(v) }
関数に渡した値に破壊的変更をする場合も同じノリ
package main import ( "fmt" ) type Test struct { value int } func mutable(v *Test) { v.value = v.value * 2 } func immutable(v Test) { v.value = v.value * 2 } func main() { v := Test{3} immutable(v) // ここでvの値は変更されない fmt.Println(v) mutable(&v) // ここでは変更される fmt.Println(v) }
package main type Test struct { value int } func mutable(v *Test) { v.value = v.value * 2 print("\n") print(v) // ポインタA } func immutable(v Test) { v.value = v.value * 2 print("\n") print(&v) // ポインタB(コピーなので呼び出し元とポインタが変わる) } func main() { v := Test{3} print("\n") print(&v) // ポインタA immutable(v) // ここでvの値は変更されない mutable(&v) // ここでは変更される }
要するに
- ポインタで渡した場合は同じ参照(なぜならポインタで渡したから)
- 値を直接渡した場合、関数の中で参照できる値は渡した値そのものではなくコピー
という感じ。
先に気になって別の話で書いたがreturnで呼び出し元に戻される値も値そのものではなくコピーになる。 deferの中でmutationな操作をする関数を読んで戻り値を変更しても、戻り値に影響がないのは何故だろう - チョキチョキかにさん
Pointer receiversと共に定義したメソッドはポインタ経由でも値経由でも呼び出せる
package main import "fmt" type Test struct { test int } func (value *Test) double() { value.test = value.test * 2 } func main() { v := Test{1} v.double() fmt.Println(v) // 2 (&v).double() fmt.Println(v) // 4 }
いずれも破壊的な操作が可能。(ポインタ経由での呼び出しは、値経由での呼び出しと解釈されるとのこと)
Pointer receiverを使う理由
- 破壊的変更ができる
- メソッド呼び出し時に構造体をコピーするコストを回避できる
value receiverとpointer receiverを混在させるべきではない。(理由は後ほど出てくるらしい)
インターフェース
インターフェース型はメソッドシグネチャの集合である。
package main import ( "fmt" ) type TestInterface1 interface { print() } type TestInterface2 interface { printDouble() } type Test1 struct{ test int } type Test2 struct{ test int } func (v Test1) print() { fmt.Println(v) } func (v Test2) print() { fmt.Println(v) } func (v Test1) printDouble() { fmt.Println(v) } func main() { var a TestInterface1 var b TestInterface2 a = Test1{1} b = Test1{2} a.print() b.printDouble() b.print() // bの型にprintは定義されてないのでコンパイルエラー var c TestInterface2 c = Test2{1} // Test2にprintDoubleは定義されていないのでコンパイルエラー }
要するに、
- interfaceで定義されたシグネチャと同じシグネチャのメソッドが定義されたstructの値は、そのinterface型に代入できる
- そのinterface型の変数経由で、interfaceに定義したシグネチャと同じメソッドを、struct経由で呼び出せる
と言った感じ。
初期化してない型のメソッドを呼んだ場合の挙動
NullPointerExceptionになるのではなく、receiverがnilになる。
package main type Test struct { value int } type I interface { double() int } func (test *Test) double() int { if test == nil { return 0 } return test.value * 2 } func main() { var a *Test // 値が初期化されていないポインタ a.double() // ここでNullPointerExceptionにならず、double関数のtestがnilになる var b I b = a b.double() // インターフェースの場合も同様 }
型の情報もないinterface(nil interface)のメソッドを呼ぶ場合
セグメンテーション違反で実行時エラーになる。
package main type Test struct { value int } type I interface { double() int } func main() { var b I b.double() // 型情報すらない場合ここでセグメンテーション違反で実行時エラー }
empty interface
メソッドの定義がないインターフェースのこと。あらゆる型の値を代入できる。型のわからない値をハンドリングするために使う。
Type assertions
インターフェースの元の値へのアクセスを提供する。
package main import "fmt" type TestI interface { test() } type TestS struct { value string } func (test TestS) test() { return } func main() { var i TestI = TestS{"test"} s := i.(TestS) fmt.Println(s) s, ok := i.(TestS) fmt.Println(s, ok) // 2値で受けるとinterfaceが特定の型を保持しているかどうかを判定できる }
package main import "fmt" type TestI interface { test() } type TestS struct { value string } func (test TestS) test() { return } func main() { var i TestI = TestS{"test"} f, ok := i.(float64) // impossible type assertion:float64 does not implement TestI (missing test method) fmt.Println(f, ok) }
このケースではエラーになる。Type assertionで指定する型は、対象の変数を宣言した際のインターフェースに対応している必要がある。
package main import "fmt" type TestI interface { test() } type TestS struct { value string } type TestSS struct { value string } func (test TestS) test() { return } func (test TestSS) test() { return } func main() { var i TestI = TestS{"test"} f, ok := i.(TestSS) fmt.Println(f, ok) }
こうするとokにfalseが入ってくる。
Type switches
インターフェースの型による分岐を表現できる。
package main import "fmt" type I interface{} func main() { var a interface{} a = "test" switch t := a.(type) { case string: fmt.Println("string", t) case int: fmt.Println("int", t) } }
a.(type)
はぱっと見型情報を取得するための構文に見えるがType switchの中でないとコンパイルエラーになる。
ここでtにはcaseでマッチした型でtype assertionした場合のaが入ってくる。
package main import "fmt" type I interface{} func acceptString(value string) { fmt.Println(value) } func acceptInt(value int) { fmt.Println(value) } func main() { var a interface{} a = "test" switch t := a.(type) { case string: acceptString(t) // ここではtはstring case int: acceptInt(t) // ここではtはint } }
要するにa.(type)
はswitch用の特定の型に固定しないtype assertionという感じ。
Stringers
typeがString() string
を実装している場合、そのtypeはStringerインターフェースに対応している。(対応という言い方はあっているのだろうか)
JavaでいうとtoStringだけ持ってるインターフェースがObjectクラスから分離してる感じ。
package main import "fmt" type Metalica struct { } func (p Metalica) String() string { return fmt.Sprintf("WOOOOOO YEAHHHHH") } func main() { a := Metalica{} fmt.Println(a) // Metalicaをレシーバーに持ったString()の結果が出力される }
Exercise: Stringers
package main import "fmt" type IPAddr [4]byte func (v IPAddr) String() string { return fmt.Sprintf("%d.%d.%d.%d", v[0], v[1], v[2], v[3]) } func main() { hosts := map[string]IPAddr{ "loopback": {127, 0, 0, 1}, "googleDNS": {8, 8, 8, 8}, } for name, ip := range hosts { fmt.Printf("%v: %v\n", name, ip) } }
なんとなくStringersのレシーバーはポインタにしたい気がするがポインタにするとfmt.PrintfでString()が呼ばれなかった。 インターフェースについて微妙に理解できてない感がある。
Errors
error interfaceというのがいて、それに対応したtypeはErrorとして使える。
type error interface { Error() string }
package main import ( "fmt" ) type MyError struct { msg string } func (v MyError) Error() string { return v.msg } func run() error { return MyError{"あーあ"} } func main() { if err := run(); err != nil { fmt.Println(err) } }
package main import ( "fmt" ) type DivisionByZeroError struct { message string } // errorインターフェースの実装 func (err DivisionByZeroError) Error() string { return err.message } func divide(d int, n int) (int, error) { if n == 0 { return 0, DivisionByZeroError{"lol"} } return d / n, nil // エラーでない場合はerrorにnilを返す } func main() { result, err := divide(3, 0); // 結果をerrを受けて、errがnilかどうかによって関数呼び出しが成功したかどうかを判定する if err != nil { fmt.Println(err) return } fmt.Println(result) }
いわゆるエラーハンドリングについてはこんな感じになるようだ。
Exercise: Errors
package main import ( "fmt" "math" ) type ErrNegativeSqrt float64 func (e ErrNegativeSqrt) Error() string { return fmt.Sprint("cannot Sqrt negative number ", float64(e)) } func Sqrt(x float64) (float64, error) { if x < 0 { return x, ErrNegativeSqrt(x) } z := float64(1) for i := 0; i < 10; i++ { z -= calc(z, x) if math.Abs(x-z*z) < 0.000001 { fmt.Println(i) return z, nil } } return z, nil } func calc(z float64, x float64) float64 { return (z*z - x) / (2 * z) } func main() { fmt.Println(Sqrt(2)) fmt.Println(Sqrt(-2)) }
初期化時のType()とType{}の違いがわかっていない。
Exercise: Readers
package main import "golang.org/x/tour/reader" type MyReader struct{} func (r MyReader) Read(bs []byte) (int, error) { counta := 0 for i, _ := range bs { bs[i] = 'A' counta++ } return counta, nil } func main() { reader.Validate(MyReader{}) }
Exercise: rot13Reader
package main import ( "io" "os" "strings" ) type rot13Reader struct { r io.Reader } func (r *rot13Reader) Read(b []byte) (int, error) { n, e := r.r.Read(b) if e != nil { return 0, e } for i, _ := range b { // fmt.Println(string(b[i])) if b[i] >= 'A' && b[i] <= 'Z' { b[i] = b[i] + 13 if b[i] > 'Z' { b[i] = b[i] - 26 } } if b[i] >= 'a' && b[i] <= 'z' { b[i] = b[i] + 13 if b[i] > 'z' { b[i] = b[i] - 26 } } } return n, nil } func main() { s := strings.NewReader("Lbh penpxrq gur pbqr!") r := rot13Reader{s} io.Copy(os.Stdout, &r) }
Exercise: Images
package main import ( "golang.org/x/tour/pic" "image" "image/color" ) type Image struct { w int h int color uint8 } func (i Image) ColorModel() color.Model { return color.RGBAModel } func (i Image) Bounds() image.Rectangle { return image.Rect(0, 0, i.w, i.h) } // この座標この色的な func (i Image) At(x, y int) color.Color { return color.RGBA{uint8(x), uint8(y), i.color, i.color} } func main() { m := Image{100, 100, 100} pic.ShowImage(m) }