公式の A Tour of Go ではじめて Go 言語を触ってみました。
基本編
メソッド編
以下、インターフェースについて学んだことのメモ。
インターフェース
インターフェース型
- インターフェース型は、メソッドのシグネチャの集まりとして定義する
- シグネチャ: 名前, 引数や戻り値の型
- インターフェース型の変数には、インターフェースを実装した型 (= インターフェースで定義したメソッドの集まりを漏れなく定義された型) の値を格納できる
- Go では、インターフェースの実装を明示的に行わない(
imlements
キーワードなし)
- Go では、インターフェースの実装を明示的に行わない(
package main
import (
"fmt"
"math"
)
// Abser インターフェースを宣言
type Abser interface {
// インターフェースはメソッドのシグネチャの集まり
Abs() float64
}
func main() {
var a Abser // a: インターフェース型を持つ変数
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat は Abser を満たす(実装する)
a = &v // a *Vertex は Abser を満たす(実装する)
// In the following line, v is a Vertex (not *Vertex)
// and does NOT implement Abser.
a = v
fmt.Println(a.Abs())
}
// MyFloat 型の宣言
type MyFloat float64
// MyFloat 型に Abs() メソッドを定義
// 暗黙的に MyFloat 型が Abser インターフェースを実装する
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
// Vertex 型の宣言
type Vertex struct {
X, Y float64
}
// *Vertex 型に Abs() メソッドを定義(ポインタレシーバ)
// 暗黙的に *Vertex 型が Abser インターフェースを実装する
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
インターフェースの値
- インターフェースの値は、値と型のタプルのように考えることができる(
(value, type)
) - インターフェースの値のメソッドを呼び出すと、基底の型が持つ同じ名前のメソッドが実行される
import (
"fmt"
"math"
)
// I 型(インターフェース)を宣言
type I interface {
M()
}
// T 型(構造体)を宣言
type T struct {
S string
}
// *T 型にメソッド M() を定義
// 暗黙的に *T 型が I インターフェースを実装
func (t *T) M() {
fmt.Println(t.S)
}
// F 型(float64)を宣言
type F float64
// F 型にメソッド M() を定義
// 暗黙的に F 型が I インターフェースを実装
func (f F) M() {
fmt.Println(f)
}
func main() {
var i I // I型(インターフェース)の変数を宣言
i = &T{"Hello"} // T 型の値へのポインタをインターフェースへ格納
describe(i) // (&{Hello}, *main.T)
i.M() // Hello
i = F(math.Pi) // F 型の値をインターフェースへ格納
describe(i) // (3.141592653589793, main.F)
i.M() // 3.141592653589793
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
nil
をレシーバとするメソッドの呼び出し
- インターフェースに格納されている中身の具体的な値が
nil
の場合、メソッドはnil
をレシーバとして呼び出される- ここで、
nil
を保持するインターフェースの値それ自体は非nil
であることに注意
- ここで、
- いくつかの他の言語では「ぬるぽ」を引き起こすが、Goでは
nil
がレシーバとして呼び出されても正しく動作するメソッドを記述するのが一般的
package main
import "fmt"
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() {
// レシーバが nil であることを想定したメソッドの記述
if t == nil {
fmt.Println("<nil>")
return
}
fmt.Println(t.S)
}
func main() {
var i I
var t *T // <nil>
i = t
describe(i) // (<nil>, *main.T)
i.M() // <nil>
// nil をレシーバとするメソッドの呼び出し
i = &T{"hello"}
describe(i) // (&{hello}, *main.T)
i.M() // hello
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
nil
インターフェースの値
-
nil
インターフェースの値は、値も具体的な型も保持しない - 呼び出す対象のメソッドを示す型が存在しないため、ランタイムエラーとなる
package main
import "fmt"
type I interface {
M()
}
func main() {
var i I // <nil>
describe(i) // (<nil, <nil>)
i.M() // runtime error!!
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
空のインターフェース
- ゼロ個のメソッドを指定されたインターフェース型(
interface{}
) - 任意の型の値を保持できる
- 全ての型は、少なくともゼロ個のメソッドを実装している
- 未知の型の値を扱うコードで使用される
- 例:
fmt.Print
はinterface{}
型の任意の数の引数を受け取る
- 例:
package main
import "fmt"
func main() {
var i interface{}
describe(i) // (<nil>, <nil>)
i = 42
describe(i) // (42, int)
i = "hello"
describe(i) // (hello, string)
}
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}
型アサーション
- 型アサーション:
t, ok := i.(T)
- インターフェースの値
i
の基になる具体的な値を取得する操作 -
i
が具体的な型T
を保持している場合-
t
は基の具体的な値
-
-
i
が具体的な型T
を保持していない場合- panic を引き起こす
- インターフェースの値
- インターフェースの値
i
が、ある具体的な型T
を保持しているかテストするために2つ目の戻り値を利用する:t, ok := i.(T)
-
i
が具体的な型T
を保持している場合-
t
は基の具体的な値 -
ok
はtrue
-
-
i
が具体的な型T
を保持していない場合-
t
はゼロ値 -
ok
はfalse
-
-
package main
import "fmt"
func main() {
var i interface{} = "hello"
s, ok := i.(string)
fmt.Println(s, ok) // hello, true
f, ok := i.(float64)
fmt.Println(f, ok) // 0 false
s := i.(string)
fmt.Println(s) // hello
f = i.(float64) // panic: interface conversion: interface {} is string, not float64
}
型switch
- 複数の型アサーションを連続で使用できる構造
- インターフェースの値が保持する具体的な型による条件分岐
package main
import "fmt"
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
func main() {
do(21) // Twice 21 is 42
do("hello") // "hello" is 5 bytes long
do(true) // I don't know about type bool!
}
インターフェースの例
fmt.Stringer
-
fmt
パッケージにはStringer
インターフェースが定義されている
src/fmt/print.go
// Stringer is implemented by any value that has a String method,
// which defines the ``native'' format for that value.
// The String method is used to print values passed as an operand
// to any format that accepts a string or to an unformatted printer
// such as Print.
type Stringer interface {
String() string
}
- ある型
T
がStringer
インターフェースを実装すると、fmt
でT
の値を文字列として出力する際に、String()
メソッドの戻り値に従うようになる-
fmt
パッケージは、変数を文字列で出力する際にStringer
インターフェースを確認している
-
package main
import "fmt"
type Person struct {
Name string
Age int
}
type MyString string
// fmt.Stringer インターフェースを実装
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
// fmt.Stringer インターフェースを実装
func (m MyString) String() string {
return "🥺"
}
func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
m := MyString("ぴえん")
fmt.Println(a, z, m)
// Arthur Dent (42 years) Zaphod Beeblebrox (9001 years) 🥺
}
error
- Go には組み込みで
error
インターフェースが用意されている
src/builtin/builtin.go
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}
-
error
インターフェースを実装することで、独自エラーを定義できる
package main
import (
"fmt"
"time"
)
type MyError struct {
When time.Time
What string
}
// *MyError 型が error インターフェースを実装
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
func run() error {
return &MyError{
time.Now(),
"it didn't work",
}
}
func main() {
err := run()
fmt.Printf("%T\n", err) // *main.MyError
if err != nil {
fmt.Println(err)
// at 2009-11-10 23:00:00 +0000 UTC m=+0.000000001, it didn't work
}
}
参考: Go でのエラーハンドリングの基本
- Go のプログラムは、エラーの状態を
error
型で表現する- 関数はしばしば
error
型の値を返す - 関数の呼び出し元は、
error
型の値がnil
稼働かを確認することでエラーハンドリングをする-
error
がnil
=> 成功 -
error
が非nil
=> 失敗
-
- 関数はしばしば
package main
import (
"fmt"
"strconv"
)
func main() {
i, err := strconv.Atoi("cat")
fmt.Printf("%T, %T\n", i, err) // int, *strconv.NumError
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
// strconv.Atoi: parsing "cat": invalid syntax
return
}
fmt.Println("Converted integer:", i)
}
src/strconv/atoi.go
// A NumError records a failed conversion.
type NumError struct {
Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat, ParseComplex)
Num string // the input
Err error // the reason the conversion failed (e.g. ErrRange, ErrSyntax, etc.)
}
func (e *NumError) Error() string {
return "strconv." + e.Func + ": " + "parsing " + Quote(e.Num) + ": " + e.Err.Error()
}
io.Readers
-
io
パッケージは、データストリームを読むことを表現するio.Reader
インターフェースを定義している- Go の多くの標準ライブラリは、ファイル、ネットワーク接続、圧縮、暗号化などのために、このインターフェースを実装している
src/io/io.go
// Reader is the interface that wraps the basic Read method.
//
// Read reads up to len(p) bytes into p. It returns the number of bytes
// read (0 <= n <= len(p)) and any error encountered. Even if Read
// returns n < len(p), it may use all of p as scratch space during the call.
// If some data is available but not len(p) bytes, Read conventionally
// returns what is available instead of waiting for more.
//
// When Read encounters an error or end-of-file condition after
// successfully reading n > 0 bytes, it returns the number of
// bytes read. It may return the (non-nil) error from the same call
// or return the error (and n == 0) from a subsequent call.
// An instance of this general case is that a Reader returning
// a non-zero number of bytes at the end of the input stream may
// return either err == EOF or err == nil. The next Read should
// return 0, EOF.
//
// Callers should always process the n > 0 bytes returned before
// considering the error err. Doing so correctly handles I/O errors
// that happen after reading some bytes and also both of the
// allowed EOF behaviors.
//
// Implementations of Read are discouraged from returning a
// zero byte count with a nil error, except when len(p) == 0.
// Callers should treat a return of 0 and nil as indicating that
// nothing happened; in particular it does not indicate EOF.
//
// Implementations must not retain p.
type Reader interface {
Read(p []byte) (n int, err error)
}
-
Read
メソッドは、データを与えられたバイトスライスに入れ、入れたバイトのサイズとエラーの値を返す - ストリームの終端は、
io.EOF
のエラーで返す
package main
import (
"fmt"
"io"
"strings"
)
func main() {
r := strings.NewReader("Hello, Reader!")
fmt.Printf("%T\n", r) // *strings.Reader
b := make([]byte, 8)
// 8 byte 毎に読み出す
for {
n, err := r.Read(b)
// n: b に入れた byte のサイズ
fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
// n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
// n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
// n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
fmt.Printf("b[:n] = %q\n", b[:n])
// b[:n] = "Hello, R"
// b[:n] = "eader!"
// b[:n] = ""
if err == io.EOF {
fmt.Printf("%T\n", err) // *errors.errorString
break
}
}
}
次回