🧑‍⚖️

[Go]interfaceを引数にとるときはポインタ渡しする必要はない

2023/11/15に公開

TL;DR

ポインタ渡ししなくても参照渡しされる。

- func Delete(item *model.ItemInterface) error
+ func Delete(item model.ItemInterface) error

先に挙動を確認

▼やったこと
interfaceを引数に取る関数 func Delete(item model.ItemInterface) error
に対して、
interfaceを実装した変数を渡して呼び出す Delete(c, concleteItem)
ここで、Delete関数内でのitemの挙動をいくつか確認する。

▼結果
(1)Delete関数の中でitemの値を更新すると、呼び出し元のconcleteItemの値も更新される

  • ここから参照渡しされていることが分かる

(2)一方で、itemとconcleteItemは違うポインタを示す。また、*item のようにして値にアクセスすることはできない。

  • これらはポインタ渡しした場合の挙動とは異なる。

値渡しした場合ともポインタ渡しした場合とも挙動が異なる。それはなぜかを説明する。

Interface型の引数には何が入っているのか?

Interface values are represented as a two-word pair giving a pointer to information about the type stored in the interface and a pointer to the associated data.
https://research.swtch.com/interfaces

すなわち、interfaceを引数に取る関数の中で、引数は以下の値を持つ値となっている。
(1) 値へのポインタ
(2) 値の型(=実装の型)

これで、上記の挙動(1)(2)の説明が付くことになる。
よって、ポインタ渡しをしなくても参照渡し出来るので、ポインタ渡しをする必要がない。

補足: 型アサーションについて

上記のinterface型の引数(という言い方が正しいのか分からないが)の仕様は、型アサーションにも関わる。

以下のような典型的な型アサーションコードを考える。

func Delete(item model.ItemInterface) error {
    switch v := item.(type) {
    case ConcleteItem:
        ...
    case ConcleteItem2:
        ...
    default:
        ...
    }
}

このコードが何故動作するのか(どうやってitemの実装型を知るのか)というと、何のことはなく、item 変数がその実装の型情報をそのまま持っているからである。

Discussion