Go

Go でのポインターの扱い

More than 1 year has passed since last update.

今日は Go 言語でのポインターの扱いについて整理してみた。久しくポインターを扱う言語に触れていなかったので、ふわっと理解しているつもりだったが、色々理解できていなかったので、まとめてみる。

基本的なポインタ

Go での基本的なポインタの扱いは、C とそんなに変わらない。唯一違うのは、ポインタ演算ができないところだ。(プログラムを破壊する可能性があるからだろうか)

package main

import "fmt"

func main() {
    str := "ushio"
    p := &str

    fmt.Println(str, "  // str")
    fmt.Println(&str, "  // &str")
    fmt.Println(p, "  // p")
    fmt.Println(*p, "  // *p")
}

はっきり言って文法も C と変わらない。実行結果はきっとあなたの予想通り

ushio   // str
0xc42006c1a0   // &str
0xc42006c1a0   // p
ushio   // *p

理解したいターゲット

実はこれを書いているのも、オープンソースへの貢献のための一歩で、次のコードをちゃんと理解したいと思ったから。

// DataFactory is the DataFactory Response
type DataFactory struct {
    autorest.Response      `json:"-"`
    ID                     *string             `json:"id,omitempty"`
    Name                   *string             `json:"name,omitempty"`
    Type                   *string             `json:"type,omitempty"`
    Location               *string             `json:"location,omitempty"`
    Tags                   *map[string]*string `json:"tags,omitempty"`
    *DataFactoryProperties `json:"properties,omitempty"`
}

// DataFactoryProperties is the Property of DataFactory
type DataFactoryProperties struct {
    DataFactoryID     *string           `json:"datafactoryId,omitempty"`
    ProvisioningState ProvisioningState `json:"provisioningState,omitempty"`
    Error             *string           `json:"error,omitempty"`
    ErrorMessage      *string           `json:"errorMessage,omitempty"`
}

何となくでよければ正直動くものはかけるが、結局それは遅さを招くので、理解しておきたい。

構造体の参照渡しと、値渡し

関数やメソッドに対してのパラメータは、参照渡しという方法と、値渡しという方法がある。GO で普通に書くと「値渡し」になる。つまり、変数の値をコピーして渡す(値渡し)と、参照渡し、つまりポインタを渡す方法だ。コピーする内容が多ければ、値渡しだと時間がかかってしまう。参照渡しだとパラメータが大きな構造体出会っても、アドレスだけなので、一瞬で終わる。

さて、構造体をまるでクラスのように書くテクニックがよく使われるが、大抵は上記のターゲットもそうであるように、ポインタ渡しになっている。それには理由がある。次の例を見てみよう。

こんな構造体を作る。

// Person is a person with name and age
type Person struct {
    Name   string  // Person's first name
    Age    int     // Person's age
}

そして、これに対応するメソッドを用意する。一つは値渡し、一つは、ポインタになっている。内容は、Ageの内容をインクリメントすることだ。


// Increment increments person's age
func (p Person) Increment() {
    p.Age++
}

// IncrementWithReference increment person's age
func (p *Person) IncrementWithReference() {
    p.Age++
}

これらのメソッドを次のように使ってみると、実行結果が異なる。

func main() {

    ushio := Person{
        Name: "tsuyoshi",
        Age:  46,
    }

    ushio.Increment()
    fmt.Println("Value: ", ushio.Age, " // 47と思いきや違う")
    ushio.IncrementWithReference()
    fmt.Println("Pointer: ", ushio.Age, " // 実際に47になった")

実行結果

Value:  46  // 47と思いきや違う
Pointer:  47  // 実際に47になった

値渡しの方は、Age をインクリメントしているにもかかわらず、構造体の値が変化していない。これらの関数は次のように展開される

func Increment(p Person) {
   p.Age++
}

fun IncrementWithReference(p *Person) {
   p.Age++
}

つまり第一引数として、構造体の引数が渡っているのと同じである。だから、値渡しにしてしまうと、コピーが渡されるので、更新がうまくいかない。だから、値渡しにしていいケースは、Immutable なケースなどが考えられるが、それ以外には、基本的にはポインタ渡しの方がイメージにあう。

ポインタと値渡しパラメータに対するシンタックスシュガー

Go はポインタの操作ができない。また、しょっちゅうポインタの値を操作することになる。面倒なので、シンタックスシュガーで、本来ポインターであっても、p.Age++ のように、値渡しのようなイメージで書くことが可能になっている。(だから、たまに、どっちだかわからなくなっていた。なるほど。)

先ほどのゴールに対してまだ理解できていないポイントがある。それは、構造体の フィールド がポインタになっていることである。何故わざわざこうなっているのだろう?

構造体の フィールド が ポインタである理由

ゴールの構造体と比べて違うところは何かというと、構造体を初期化した時の、初期化の挙動による。構造体を初期化すると、値を与えないと、デフォルト値が採用される。

// Person is a person with name and age
type Person struct {
    Name   string  // Person's first name
    Age    int     // Person's age
    Status *string // Person's status
}

先ほどの構造体に、ポインタのフィードを足してみる。そして、次のようなコードを書いて、初期化のデフォルト値の違いを見てみる。

    aPerson := new(Person) // No parameter

    fmt.Println("Name :", aPerson.Name)
    fmt.Println("Age :", aPerson.Age)
    fmt.Println("Status :", aPerson.Status)

結果は

Name : 
Age : 0
Status : <nil>

初期化したら、型ごとのデフォルト値で埋められる。ちなみに、元々のゴールの構造体の使い道は、REST API から帰ってきた値をを保存するためのものである。JSON から変換されてくる。この構造体の必須チェックを行うときに、値渡しだと、から文字が、初期値によるものなのか、から文字が渡されたのかがわからなくなってしまう。ちなみに、参照渡しにすると、値の参照が多少面倒になる。例えば、*string のフィールドがあるときに、&"tsuyoshi" などと書いても、参照渡しはできない。たぶんこんなメソッドを書くなり、一旦変数に代入して、アドレスをもらうなりしないといけない。面倒だ。

func toAddress(str string) *string {
    return &str
}

ゴールの構造体で参照渡しになっているのは、JSONからパースするので、nil か から文字かを明確に区別したかったのだと思う。そして、構造体のフィールドの更新はない(REST-API から受け取った値を格納する責務なので)だから、今回はポインタを選択しているのだと思う。

ネストした構造体

では最後のテクニック

// Person is a person with name and age
type Person struct {
    Name   string  // Person's first name
    Age    int     // Person's age
    Status *string // Person's status
}

を使って


// Employee is an employee
type Employee struct {
    Person
    ID int // Employee's employee_id
}

こんな感じで書くと、継承っぽいイメージで使えるようになる。具体例を見てみよう。初期化時はPersonを意識しているが、使うところは、まるで継承したようなイメージになっている。


    emp := Employee{
        Person: Person{Name: "tsuyoshi",
            Age:    46,
            Status: toAddress("Happy"),
        },
        ID: 2014,
    }

    fmt.Println("Name: ", emp.Name)
    fmt.Println("Age: ", emp.Age)
    fmt.Println("Status: ", *emp.Status, "(", emp.Status, ")")
    fmt.Println("Id: ", emp.ID)

実行結果

Name:  tsuyoshi
Age:  46
Status:  Happy ( 0xc42000e350 )
Id:  2014

うん。しっかりまるで継承しているかのようだ。これで大体ポインターの部分はつかめたきがする。他にも押さえておくべき文法がありましたら、ぜひコメントお願いいたします。

リソース

今回はたくさん参照しましたので、リストを書いておきます。