んだ日記

ndaDayoの技術日記です

『実用 Go言語』を読み始めた。2章定義型〜3章構造体

今日も今日とて、『実用 Go言語』の読書録を書いていきます。 前回は1章の記録を書きましたが、今回は2章定義型〜3章構造体まで書いていきます。

定義型

まずは、例から。

こんなふうに型を定義することができる。

type Opcode byte

type Definition struct {
    Name          string
    OperandWidths []int
}

メソッドとレシーバ

定義した型に対して、メソッドを持たせることもできる。 レシーバとは、構造体のフィールドやその他のデータ型にアクセスする方法を提供するメソッドの引数。

func (s HTTPStatus) String()s HTTPStatusのこと。sをレシーバ変数と呼ぶ。

package extend

type HTTPStatus int

const (
    StatusOK           HTTPStatus = 200
    StatusUnauthorized HTTPStatus = 401
)

func (s HTTPStatus) String() string {
    switch s {
    case StatusOK:
        return "OK"
    case StatusUnauthorized:
        return "Unauthorized"
    default:
        return fmt.Sprintf("HTTPStatus(%d)", s)
    }
}

列挙型への型定義

また、列挙型のレシーバにロジックを実装することもいける。

type Season int

const (
    Peak Season = iota + 1
    Normal
    Off
)

func (s Season) Price(price int) int {
    if s == Peak {
        return price + 200
    }

    return price
}

一応、テストも書いてみましょう

func TestPrice(t *testing.T) {
    tests := []struct {
        season Season
        got    int
        want   int
    }{
        {Peak, 1000, 1200},
        {Normal, 1000, 1000},
        {Off, 1000, 1000},
    }

    for _, tt := range tests {
        got := tt.season.Price(tt.got)

        if got != tt.want {
            t.Errorf("Price(%v, %v) = %v; want %v", tt.season, tt.got, got, tt.want)
        }
    }
}

構造体

続いて、構造体。

Goにはクラスがない。代わりに?というのが正しいかは知らないが、構造体を提供している。

type Lexer struct {
    input        string
    position     int  
    readPosition int  
    ch           byte
}

func New(input string) *Lexer {
    l := &Lexer{input: input}
    l.readChar()
    return l
}

特徴

フィールドの読み書きを禁止してない。

PHPであれば、メンバ変数はプライベートにして、メソッド経由で読み書きを行ったりするが、Goの場合は読み書き可能。

インスタンス

構造体をインスタンス化する方法は3種類提供されている。

① new
b1 := new(Book)

② var 変数宣言
var b2 Book

③ 複合リテラル

b3 := &Book{
     Title: "親孝行プレイ",
     Author : "みうらじゅん"
}

値を入れないで生成したフィールドは、ゼロ値で初期化される。

構造体のメソッド、レシーバ

レシーバには、値レシーバーとポインタレシーバの2種類がある。

type Struct struct {
    v int
}

// 値レシーバー
func (s Struct) SetValue(v int) {
    s.v = v
}
// ポインタレシーバ
func (s *Struct) SetValuePointer(v int) {
    s.v = v
}

テストコードで挙動を確認してみましょう。

func TestSetValue(t *testing.T) {
    s := Struct{v: 0}

    s.SetValue(100000)

    if s.v != 0 {
        t.Errorf("SetValue failed, got: %d, want: 0", s.v)
    }
}

func TestSetValuePointer(t *testing.T) {
    s := Struct{v: 0}

    s.SetValuePointer(10)

    if s.v != 10 {
        t.Errorf("SetValuePointer failed, got: %d, want: 0", s.v)
    }
}

値レシーバーとポインタレシーバで、挙動が違うことがわかりますね。

実装方法

構造体を実装する際には、

  • ポインターか、値か
  • イミュータブルか、ミュータブルか
  • ゼロ値の保証をどうするか?

という判断のポイントがある。

困ったらまずは、「ポインタで」「ミュータブルで」「ファクトリー関数で動作」という選択肢が、一番問題が少ないそうだ。

まとめ

以上、2章定義型〜3章構造体についてざっと気になった箇所について、まとめていきました。構造体にタグを埋め込む、ジェネリクスあたりは、雰囲気だけ理解して飛ばしました(必要に迫られたら復讐する

実装の選択肢とか継承がないなどなど、興味深く読めました。

次はインターフェースです。興奮してきますね

僕から以上。あったかくして寝ろよ

github.com