コンテンツにスキップ

Go/主要な標準インターフェース

出典: フリー教科書『ウィキブックス(Wikibooks)』
< Go

Go言語には、Stringerインターフェイスのように明示的に宣言されなくても、重要な役割を果たすインターフェイスが多数存在します。これらのインターフェイスは、Goの標準ライブラリやサードパーティライブラリで広く使用されており、Goの柔軟性と強力な機能を支えています。以下に、そのようなインターフェイスのいくつかを紹介し、詳細に解説します。

1. error インターフェイス

[編集]

errorインターフェイスは、Goにおけるエラーハンドリングの中心的な役割を果たします。このインターフェイスは以下のように定義されています。

type error interface {
    Error() string
}

このインターフェイスを実装する型は、Error()メソッドを提供することで、エラーメッセージを文字列として返すことができます。errorインターフェイスは、関数がエラーを返す際に広く使用されます。

package main

import "fmt"

func divide(a, b int) (quotient int, err error) {
	if b == 0 {
		err = fmt.Errorf("division by zero")
	} else {
		quotient = a / b
	}
	return
}

func main() {
	a, b := 10, 2
	if result, err := divide(a, b); err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Result:", result)
	}

	// 0 で割るケース
	a, b = 10, 0
	if result, err := divide(a, b); err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Result:", result)
	}
}

2. io.Reader インターフェイス

[編集]

io.Readerインターフェイスは、データを読み取るための標準的な方法を提供します。このインターフェイスは以下のように定義されています。

type Reader interface {
    Read(p []byte) (n int, err error)
}

Readメソッドは、指定されたバイトスライスにデータを読み込み、読み取られたバイト数とエラーを返します。このインターフェイスは、ファイル、ネットワーク接続、メモリバッファなど、さまざまなデータソースに対して使用されます。

func readAll(r io.Reader) ([]byte, error) {
    var buf bytes.Buffer
    _, err := buf.ReadFrom(r)
    if err != nil {
        return nil, err
    }
    return buf.Bytes(), nil
}

3. io.Writer インターフェイス

[編集]

io.Writerインターフェイスは、データを書き込むための標準的な方法を提供します。このインターフェイスは以下のように定義されています。

type Writer interface {
    Write(p []byte) (n int, err error)
}

Writeメソッドは、指定されたバイトスライスをデータストリームに書き込み、書き込まれたバイト数とエラーを返します。このインターフェイスは、ファイル、ネットワーク接続、メモリバッファなど、さまざまなデータの宛先に対して使用されます。

func writeAll(w io.Writer, data []byte) error {
    _, err := w.Write(data)
    return err
}

4. fmt.Stringer インターフェイス

[編集]

fmt.Stringerインターフェイスは、オブジェクトを文字列として表現するためのメソッドを提供します。このインターフェイスは以下のように定義されています。

type Stringer interface {
    String() string
}

このインターフェイスを実装する型は、String()メソッドを提供することで、オブジェクトの文字列表現を返すことができます。fmtパッケージの関数(例: fmt.Println)は、このインターフェイスを実装する型を自動的に文字列に変換します。

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%s (%d years)", p.Name, p.Age)
}

func main() {
    p := Person{Name: "Alice", Age: 30}
    fmt.Println(p) // Output: Alice (30 years)
}

5. sort.Interface インターフェイス

[編集]

sort.Interfaceインターフェイスは、コレクションをソートするためのメソッドを提供します。このインターフェイスは以下のように定義されています。

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

このインターフェイスを実装する型は、Len()Less()Swap()メソッドを提供することで、コレクションのソートを可能にします。sortパッケージは、このインターフェイスを利用して、スライスや他のコレクションをソートします。

type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

func main() {
    people := []Person{
        {"Bob", 31},
        {"Alice", 30},
        {"Charlie", 32},
    }
    sort.Sort(ByAge(people))
    fmt.Println(people) // Output: [{Alice 30} {Bob 31} {Charlie 32}]
}

6. http.Handler インターフェイス

[編集]

http.Handlerインターフェイスは、HTTPリクエストを処理するためのメソッドを提供します。このインターフェイスは以下のように定義されています。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

このインターフェイスを実装する型は、ServeHTTPメソッドを提供することで、HTTPリクエストを処理できます。httpパッケージは、このインターフェイスを利用して、HTTPサーバーを構築します。

type MyHandler struct{}

func (h MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    handler := MyHandler{}
    http.ListenAndServe(":8080", handler)
}

7. context.Context インターフェイス

[編集]

context.Contextインターフェイスは、ゴルーチン間でキャンセルシグナルやデッドライン、その他のリクエストスコープの値を伝播させるためのメソッドを提供します。このインターフェイスは以下のように定義されています。

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

このインターフェイスは、長時間実行される操作や、複数のゴルーチン間での協調的なキャンセルを管理するために使用されます。

func longRunningOperation(ctx context.Context) {
    select {
    case <-time.After(5 * time.Second):
        fmt.Println("Operation completed")
    case <-ctx.Done():
        fmt.Println("Operation canceled")
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    go longRunningOperation(ctx)
    time.Sleep(3 * time.Second)
}

8. json.Marshalerjson.Unmarshaler インターフェイス

[編集]

json.Marshalerjson.Unmarshalerインターフェイスは、JSONへのエンコードとデコードをカスタマイズするためのメソッドを提供します。

type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

type Unmarshaler interface {
    UnmarshalJSON([]byte) error
}

これらのインターフェイスを実装する型は、JSONとの相互変換をカスタマイズできます。

type CustomDate struct {
    time.Time
}

func (cd CustomDate) MarshalJSON() ([]byte, error) {
    return []byte(fmt.Sprintf(<code>"%s"</code>, cd.Format("2006-01-02"))), nil
}

func (cd *CustomDate) UnmarshalJSON(data []byte) error {
    t, err := time.Parse(<code>"2006-01-02"</code>, string(data))
    if err != nil {
        return err
    }
    cd.Time = t
    return nil
}

9. fmt.Formatter インターフェイス

[編集]

fmt.Formatterインターフェイスは、fmtパッケージでのフォーマット出力をカスタマイズするためのメソッドを提供します。

type Formatter interface {
    Format(f State, verb rune)
}

このインターフェイスを実装する型は、fmt.Printffmt.Sprintfなどの関数で使用される際に、独自のフォーマットを提供できます。

type MyType struct {
    Value int
}

func (m MyType) Format(f fmt.State, verb rune) {
    switch verb {
    case 'v':
        fmt.Fprintf(f, "MyType{Value: %d}", m.Value)
    default:
        fmt.Fprintf(f, "MyType(%d)", m.Value)
    }
}

func main() {
    m := MyType{Value: 42}
    fmt.Printf("%v\n", m) // Output: MyType{Value: 42}
}

10. encoding.TextMarshalerencoding.TextUnmarshaler インターフェイス

[編集]

encoding.TextMarshalerencoding.TextUnmarshalerインターフェイスは、テキスト形式でのエンコードとデコードをカスタマイズするためのメソッドを提供します。

type TextMarshaler interface {
    MarshalText() ([]byte, error)
}

type TextUnmarshaler interface {
    UnmarshalText([]byte) error
}

これらのインターフェイスを実装する型は、テキスト形式との相互変換をカスタマイズできます。

type CustomBool bool

func (cb CustomBool) MarshalText() ([]byte, error) {
    if cb {
        return []byte("yes"), nil
    }
    return []byte("no"), nil
}

func (cb *CustomBool) UnmarshalText(data []byte) error {
    *cb = string(data) == "yes"
    return nil
}
インターフェースの命名規則
Go言語のインターフェース名は、語尾に -er を付けることが一般的ですが、必ずしもすべてのインターフェースがこの規則に従っているわけではありません。語尾が -er でないインターフェースも存在し、それらは特定の役割やコンテキストを反映するために、異なる命名規則が採用されています。
語尾が -er のインターフェース
語尾に -er を付ける命名規則は、インターフェースが何らかの「行為者」や「機能提供者」であることを示すために使われます。例えば、Reader は「読み取るもの」、Writer は「書き込むもの」、Stringer は「文字列化するもの」という意味を持ちます。この命名規則は、インターフェースの役割を直感的に理解しやすくするために広く採用されています。
  • Reader (io.Reader)
  • Writer (io.Writer)
  • Stringer (fmt.Stringer)
  • Formatter (fmt.Formatter)
語尾が -er でないインターフェース
一方で、語尾が -er でないインターフェースも存在します。これらのインターフェースは、特定の概念や抽象化を表すために、より具体的な名前が付けられることがあります。以下にその例を挙げます。
error
error インターフェースは、Go言語におけるエラーハンドリングの中心的な役割を果たします。このインターフェースは、Error() string という単一のメソッドを持ちますが、その名前は -er で終わりません。これは、error が「エラー」という具体的な概念を表すため、特別な命名が採用されたと考えられます。
context.Context
context.Context は、ゴルーチン間でのキャンセルシグナルやデッドライン、値の伝播を管理するためのインターフェースです。このインターフェース名は、Context という具体的な概念を表しており、-er で終わりません。これは、コンテキストが「行為者」ではなく、「状態や環境を表すもの」であるためです。
sort.Interface
sort.Interface は、コレクションをソートするためのインターフェースです。このインターフェース名は、Interface という一般的な用語を使用していますが、-er で終わりません。これは、sort パッケージ内で「ソート可能なもの」を抽象化するためのインターフェースであることを明確にするためです。
http.Handler
http.Handler は、HTTPリクエストを処理するためのインターフェースです。このインターフェース名は、Handler という言葉を使用していますが、語尾が -er ではありません。これは、Handler が「処理するもの」という意味を持つため、-er を省略した形で命名されています。
json.Marshaler と json.Unmarshaler
json.Marshalerjson.Unmarshaler は、JSONのエンコードとデコードをカスタマイズするためのインターフェースです。これらの名前は、MarshalerUnmarshaler という形で、語尾に -er が付いていますが、元の動詞が MarshalUnmarshal であるため、少し特殊な形になっています。

Go言語のインターフェース名は、その役割や機能を明確に反映するように設計されています。語尾に -er を付けることは、インターフェースが何らかの「行為者」や「機能提供者」であることを示すための一般的な規則ですが、特定の概念や抽象化を表す場合には、この規則から逸脱することがあります。

例えば、error は「エラー」という具体的な概念を表すため、-er を付けずに単純な名前が採用されています。同様に、context.Context は「コンテキスト」という状態や環境を表すため、-er を付けずに具体的な名前が使われています。

Go言語のインターフェース名は、その役割や機能を明確に反映するように設計されています。語尾に -er を付けることが一般的ですが、特定の概念や抽象化を表す場合には、この規則から逸脱することがあります。これにより、インターフェース名はその役割を直感的に理解できるようになり、コードの可読性と保守性が向上します。

まとめ

[編集]

Go言語には、Stringerインターフェイスのように明示的に宣言されなくても、重要な役割を果たすインターフェイスが多数存在します。これらのインターフェイスは、Goの標準ライブラリやサードパーティライブラリで広く使用されており、Goの柔軟性と強力な機能を支えています。これらのインターフェイスを理解し、適切に活用することで、より効率的で保守性の高いコードを書くことができます。