LoginSignup
1
0

More than 1 year has passed since last update.

【A Tour of Go】 インターフェース編

Last updated at Posted at 2022-06-07

公式の A Tour of Go ではじめて Go 言語を触ってみました。

基本編

メソッド編

以下、インターフェースについて学んだことのメモ。

インターフェース

インターフェース型

  • インターフェース型は、メソッドのシグネチャの集まりとして定義する
    • シグネチャ: 名前, 引数や戻り値の型
  • インターフェース型の変数には、インターフェースを実装した型 (= インターフェースで定義したメソッドの集まりを漏れなく定義された型) の値を格納できる
    • Go では、インターフェースの実装を明示的に行わない(imlementsキーワードなし)
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.Printinterface{} 型の任意の数の引数を受け取る
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 は基の具体的な値
      • oktrue
    • i が具体的な型 T を保持していない場合
      • t はゼロ値
      • okfalse
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
}
  • ある型 TStringer インターフェースを実装すると、 fmtT の値を文字列として出力する際に、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 稼働かを確認することでエラーハンドリングをする
      • errornil => 成功
      • 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
		}
	}
}

次回

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0