notebook

知ったこと、気になったこと、気づいたことを書き残す。

Goの' := 'の妙

tr;dr

  • := では再代入は原則できない

  • := の右辺が返す型が多値の場合は再代入できる(例外あり)

:= について

暗黙的な定義ってやつ。
型を書くのが面倒なことが多いのでよく使ってしまいますよね。
この記事を書くにあたって調べてみたけど、あくまで変数の定義の糖衣構文であって、代入の式ではないらしい。

これはOK

var a int = 1 // 変数の定義 + 代入
a = 2 // 再代入

var b := int型を返す関数()  // 変数の定義 + 代入
b = 2 // 再代入

これはNG

var a int = 1 // 変数の定義 + 代入
a := int型を返す関数() // コンパイルエラー

b := int型を返す関数()  // 変数の定義 + 代入
b := 2 // コンパイルエラー

:= を使った再代入はできないことが分かります。

関数が多値を返す場合は定義済みの変数に再代入できる

多値とは以下のように、複数の型の値を返せるやつです。

func multi() (int, error) {
    return 1, nil
}

a, err := multi()

タプルっぽいけど型では無いのでタプルでは無いっぽい。
エラー処理とかでよく使いますよね。

多値を返す関数が右辺の場合

以下はコンパイルエラーにはなりません。

func multi() (int, error) {
    return 1, nil
}

var a int = 0
a, err := multi() // 再代入できる!

変数a は代入済みなのに:=を使って再代入ができることが分かります。

例外

以下のようなパターンはコンパイルエラーとなります。

左辺を同じ変数のペアにして再度:=を使う場合

func multi() (int, error) {
    return 1, nil
}

a, err := multi()
a, err := multi() // コンパイルエラー (no new variables on left side of :=)

新しい変数を宣言する必要があるようです。
以下ならコンパイルエラーは起きません。

func multi() (int, error) {
    return 1, nil
}

a, err := multi()
b, err := multi()

左辺の変数のどちらかを新しい変数にすると再代入できます。(もちろん変数errの方を新しい変数にしても問題ありません)

左辺で_を使って変数に代入する場合

goですごいと思ったのは_ が特殊な意味を持っているという点です。
多値を返す関数を右辺にした場合、左辺のいずれかの変数を_という名前で定義しておくと、その変数_ は使わなくてもコンパイルエラーとならなくなります。(goでは定義済みの変数が未使用だとコンパイルエラーとなります。)

この_:= を同時に使った場合はコンパイルエラーとなります。

func multi() (int, error) {
    return 1, nil
}

var a int = 0
a, _ := multi() // コンパイルエラー (no new variables on left side of :=)

新しい変数の定義が必要

上記ふたつの例外で起こるコンパイルエラーですが、ともにno new variables on left side of := というエラーメッセージが出ます。
読んで字の如く、「:=の左辺には新しい変数が必要」っていうことが分かります。

つまり、:=を使って定義済みの変数に再代入する際には、新しい変数とペアにした上で右辺の多値を返す関数の返り値を代入する必要がある。ということが分かります。

まとめ

:= を使って再代入っぽいことはできます。
ただ、変数への再代入自体が昨今は微妙と考えられている(と思っている)のであまり多用せず、関数ごとに新しい変数を宣言するのが良いのでは無いかと思います。
go難しいよ。go