質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.47%
Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

Q&A

1回答

1160閲覧

Go言語 Interfaceについて

2_34_koki

総合スコア67

Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

0グッド

0クリップ

投稿2020/11/19 03:53

Goの勉強中でInterfaceの部分でわからないところがあったので質問させていただきました.

package main type XYInterface interface { Ten() Five() } type XY struct { x int y int } func (xy XY) Ten() { xy.x = 10 xy.y = 10 } func (xy *XY) Five() { xy.x = 5 xy.y = 5 } func main() { var i XYInterface xy := XY{1, 2} i = xy //ここでエラーが出る }

上記のコードでixyを入れようとするとエラーが発生します.しかしxy&XY{1,2}の形が定義してあげるとエラーが発生しません.

xy := XY{1,2} xy.Ten() xy.Five() //=> (&xy).Five() xy := &XY{1,2} xy.Ten() //=> (*xy).Tex() xy.Five()

ここで良くわからないのが今まで上記のようにGo言語が勝手に解釈してくれ利便性を量っていると思うで
最初のコードでも内部で下記のように解釈を行いコンパイルエラーが発生しないと思ったのですがどうでしょうか?

var i XYInterface xy := XY{1, 2} i = xy i.Tex() (&i).Five()

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nobonobo

2020/11/19 04:17

エラーメッセージを示して下さい。
guest

回答1

0

フィールドやメソッドの解決の時だけ気の利いた参照型か非参照型かに関わらず対象にアクセスが可能になってはいますが、それはその場の情報だけでコード生成できるから便利に自動で振り分けされます。

しかし、「構造体型」と「構造体へのポインタ型」はあくまで別の型なので
型互換チェックでは単純に「そのインターフェース型のメソッド一覧」と「対象の型のメソッド一覧」との比較検証のみを行います。わざわざ複数の型との比較は行いません。

なぜそうなのかの境目はおそらくですが、コンパイル後のメモリ効率と融通効かせるメリットのトレードオフです。前者の自動参照はそれをするかしないかでメモリコストほぼ変わらないのですが、後者の場合、2つの型情報をマージした型情報を別途残す必要があります。

追記

やはりエラーメッセージを示して欲しかったところ。

エラーメッセージは

cannot use xy (type XY) as type XYInterface in assignment: XY does not implement XYInterface (Five method has pointer receiver)

前述の回答は質問者の意図とは少しずれていたようです。

  • 「構造体へのポインタ型」であれば「XYInterface」に代入でき、意図した操作が可能
  • 「構造体型」の場合「XYInterface」に代入できず上記のエラーとなる

という状況の説明が欲しいということですね。ポインタ型は「*」による参照をとるだけで「構造体型」との互換が取れるのに対し、「構造体型」の場合、構造体型のメソッドレシーバーはメソッドが呼ばれる際、構造体のコピーを伴い特定のメモリアドレスに存在する保証がありません。なので「&」によるポインタに変換できません。
ポインタを得るには必ず変数への束縛が必要になります。

質問の後半に書かれた「XYInterface型へのポインタ」は「インターフェース型に入れられた値へのポインタ」と誤解しないでください。あくまで「XYInterfaceというインターフェース型へのポインタ」です。

go

1var i XYInterface 2 xy := XY{1, 2} 3 i = xy 4 i.Ten() 5 (&i).Five()

Go言語では「インターフェース型へのポインタ」はインターフェースではなくなりますのでメソッドを呼ぶことはできなくなります。通常、Go言語でインターフェース型へのポインタを使う場面は引数で渡したインターフェース型に結果を入れるときだけです(引数で結果受け取り)。

まとめると、

  • 「インターフェース型」は「値型とポインタ型」とは別の概念です(双方を格納できる特殊型)
  • インターフェース型に入れたポインタをデリファレンスするには一旦ポインタ型にタイプアサーションが事前に必要
  • インターフェース型に入れた値のポインタを得るには一旦値型にタイプアサーションかつ変数への束縛が事前に必要
  • 「インターフェース型へのポインタ」はインターフェース型として機能しない
  • 値型をポインタ型に変換するには事前に変数への束縛が必要
  • なので値型から直接ポインタ型レシーバのメソッドを呼ぶことはできない
  • 逆にポインタ型から値型レシーバのメソッドは呼ぶことができる(低コストでポインタを値に変換できるから)

この辺りのルールはコードを書く側にとって統一感がないように見えますが、これらを気の利いたコード生成などで隠さないのがGo言語の特徴のひとつです。(書き手の好みで書き方に揺れが起こりにくい)

投稿2020/11/19 05:52

編集2020/11/24 00:45
nobonobo

総合スコア3367

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nobonobo

2020/11/24 00:52

もう少し実践的な話をすると、ポインタ型レシーバが必要になった時点で値型レシーバを残す理由はありません。実行コストの面もふくめてポインタ型レシーバに統一すべきです。 値型レシーバへの統一が有効な用途は主に実行コストを妥協して「イミュータブルな値の保持」なのでユーザーもそれを期待します。混在してしまうとイミュータブルなのかミュータブルなのか混乱を招きます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.47%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問