🐔

Go エアプが選ぶ!Go の出来そうで出来ないコト 3 選

2025/01/09に公開

はじめに

Go エアプなのでよくワナにハマる。と言う訳で(?)比較的最近オレがハマったワナを 3 つほど挙げるので、みんなでアホかコイツwwww と笑ってクレメンス[1]

マップの値へのポインタは取れない

割と有名らしい。例を見て欲しい。

m := map[string]string{
    "キー": "値",
}

p := &m["キー"] // -> エラー!

やると⇓なエラーが出る。

invalid operation: cannot take address of m["キー"] (map index expression of type string)

スライスなら取れるのに…

s := []string{
    "値",
}

p := &s[0] // -> エラーじゃない!

マップの値へのポインタなんて取らんやろ、と思うかもしれないが、メソッドがポインタレシーバの場合に直接呼べないのが地味にめんどい。

type S struct {
    msg string
}

func (p *S) Hello() {
    fmt.Println(p.msg)
}

func main() {
    m := map[string]S{
        "日本語": {"ちわ!"},
    }
    m["日本語"].Hello() // -> エラー!
}

⇓なエラーが出る。

cannot call pointer method Hello on S

あと、フィールドを直接更新できないのも地味にめんどい。

type S struct {
    msg string
}

func (p *S) Hello() {
    fmt.Println(p.msg)
}

func main() {
    m := map[string]S{
        "日本語": {"ちわ!"},
    }
    m["日本語"].msg = "うっす!" // -> エラー!
}

⇓なエラーが出る。

cannot assign to struct field m["日本語"].msg in map

エラーにしている理由は定かではないが、個人的な思い込みではマップがハッシュテーブルで実装されているので要素の追加削除で要素の場所が変わり得るからじゃないかと思っている。

要素の場所が変わり得ると言う意味ではスライスの要素追加(append)でも発生するのにポインタが取れるので、マップの場合は何かもっと致命的な問題があるのかもしれない[2]

型アサーションで埋め込み構造体は取れない

説明が下手過ぎるのでコードを見てくれ。

type B struct {
    msg string
}

type D1 struct {
    B
}

type D2 struct {
    B
}

func main() {
    s := []any{
        D1{B{"さいなら!"}},
        D2{B{"ごきげんよう!"}},
    }

    for _, v := range s {
        b := v.(B)  // -> パニック!
        fmt.Println(b.msg)
    }
}

D1 だって D2 だって B が埋め込まれてるんだから B に型アサーション出来てもいいんじゃないの、と思ったけどそういうもんじゃないようだ。

埋め込み構造体が満たすインタフェースへの型アサーションは出来るのに…

type B struct {
    msg string
}

type D1 struct {
    B
}

type D2 struct {
    B
}

func (b B) GetMsg() string {
    return b.msg
}

type Get interface {
    GetMsg() string
}

func main() {
    s := []any{
        D1{B{"さいなら!"}},
        D2{B{"ごきげんよう!"}},
    }

    for _, v := range s {
        b := v.(Get)  // -> パニックにならない!
        fmt.Println(b.GetMsg())
    }
}

やろうと思えば出来るんだとは思うが、よく考えると(よく考えなくても)そもそも型アサーションってのは対象オブジェクトを変換したりはしないので[3]、これが出来ちゃうと別のワナになっちゃうのかな、知らんけど。

関数型の変数に違う型の関数は代入できない

何言ってんだコイツとか思うかもしれないが、何も全く違う型の関数を代入しようってんじゃない。これもうまく説明できないのでまぁコードを見てくれ。

type Greeting interface {
    Say()
}

type S struct{}

func (S) Say() {
    fmt.Println("何見とんじゃゴルァ!")
}

func New() Greeting {
    return S{}
}

func main() {
    var f func() any
    f = New // -> エラー!
    f()
}

Greeting を返す関数」を「any を返す関数」型の変数には代入できない。

やると⇓なエラーが出る。

cannot use New (value of type func() Greeting) as func() any value in assignment

Greetingany に代入することはできるのに…

type Greeting interface {
    Say()
}

type S struct{}

func (S) Say() {
    fmt.Println("何見とんじゃゴルァ!")
}

func New() Greeting {
    return S{}
}

func main() {
    var v1 Greeting = S{}
    var v2 any = v1 // -> エラーじゃない!
    fmt.Printf("%#v\n", v2)
}

Greetingany でもあるので、「Greeting を返す関数」を「any を返す関数」とみなす事ができるとは思うんだが、Go はそうは考えてくれない[4]

引数の場合も同様[5]で、関数型変数の引数型が any の場合に Greeting を引数としてとる関数を代入するみたいなのも NG だ[6]

まぁ 実装上そういう扱いには出来ない、あるいは頑張れば出来るけどそこまでしてやるもんでもないと言う判断なんだろう、知らんけど[7]

それっぽい事をやりたければ関数リテラルで囲めばいい。

type Greeting interface {
    Say()
}

type S struct{}

func (S) Say() {
    fmt.Println("何見とんじゃゴルァ!")
}

func New() Greeting {
    return S{}
}

func main() {
    var f func() any
    f = func() any { return New() } // -> エラーじゃない!
    f()
}

関係ないが Go では関数リテラルがめっさカジュアルに使われてるような気がする。defer のタイミングを調整するためとか Functional Option Pattern とか…

いや、そもそも最近(?)は Go に限らずそういう傾向なのかも知れない…

番外編:for range でポインタを取れない

for range ループで要素へのポインタを使いたいと思ったことはないだろうか?

type S struct {
    msg string
}

func main() {
    s := []S{
        S{"ちわ"},
        S{"やぁ"},
    }
    for _, *v := range s { // <- そんなことは出来ない!
        v.msg += "!"
    }
}

やりたいことは分かってもらえただろうか?もちろん *vv にすればエラーにはならないがそれじゃ全く意味がない。

この場合はこう書く必要がある。

type S struct {
    msg string
}

func main() {
    s := []S{
        S{"ちわ"},
        S{"やぁ"},
    }
    for i := range s { // <- これなら出来るが…
        s[i].msg += "!"
    }
}

あるいはこう。

type S struct {
    msg string
}

func main() {
    s := []S{
        S{"ちわ"},
        S{"やぁ"},
    }
    for i := range s { // <- これなら出来るが…
        v := &s[i]
        v.msg += "!"
    }
}

いやまぁ出来るんだからいいっちゃあいいんだが、これぐらいなら出来ても良くない?

まぁ Go 哲学的にはちょいと書けばいいんなら書いとけや、て感じな気がするのでこれが出来るようになる日は来なそう…

おわりに

と言う訳で、オレが比較的最近やらかしたアホなミスを晒した。笑う門には福来る。これを読んだことで皆さんのストレスが少しでも解消されれば幸いである。

それでは、良い Go ライフを!

脚注
  1. インターネット老人会所属。 ↩︎

  2. もし正確な理由を知っていたら教えて欲しい。 ↩︎

  3. 実物を取り出すか別のインタフェース型にするかの二択だよね? ↩︎

  4. Java なんかはそう思ってくれる。 ↩︎

  5. 型の関係は逆だが。 ↩︎

  6. これも Java なんかは許してくれる。 ↩︎

  7. 出来るけど違うワナになるのであえて禁止している可能性もあるな… ↩︎

Discussion