Goクイズ Advent Calendar 2020/12/16(wed)
この記事は、Goクイズ Advent Calendar 2020の16日目の記事です。
問題
package main
import "fmt"
type T struct {
x int
}
func (t T) Get() int {
return t.x
}
func (t T) Set(x int) T {
t.x = x
return t
}
func (t *T) Add(x int) *T {
t.x += x
return t
}
func main() {
var t T
fmt.Println(t.Set(1).Add(1).Get())
}
選択肢
build(compile) error
panic
-
1
と表示される -
2
と表示される
考えてみましょう!
_____ _
| ____|_ __ (_) ___ _ _
| _| | '_ \ | |/ _ \| | | |
| |___| | | || | (_) | |_| |
|_____|_| |_|/ |\___/ \__, |
|__/ _ |___/
___ ___ | |_ _(_)_ __ __ _
/ __|/ _ \| \ \ / / | '_ \ / _` |
\__ \ (_) | |\ V /| | | | | (_| |
|___/\___/|_| \_/ |_|_| |_|\__, |
____ |___/ _
/ ___| ___ __ _ _ _(_)___| |
| | _ / _ \ _____ / _` | | | | |_ / |
| |_| | (_) |_____| (_| | |_| | |/ /|_|
\____|\___/ \__, |\__,_|_/___(_)
__ _ _ __ _____ |_|____ _ __ (_)___
/ _` | '_ \/ __\ \ /\ / / _ \ '__| | / __|
| (_| | | | \__ \\ V V / __/ | | \__ \
\__,_|_| |_|___/ \_/\_/ \___|_| |_|___/
__ _| |_ | |__ ___ | |_| |_ ___ _ __ ___
/ _` | __| | '_ \ / _ \| __| __/ _ \| '_ ` _ \
| (_| | |_ | |_) | (_) | |_| || (_) | | | | | |
\__,_|\__| |_.__/ \___/ \__|\__\___/|_| |_| |_| _ _
___ / _| | |_| |__ (_)___ __ _ _ __| |_(_) ___| | ___| |
/ _ \| |_ | __| '_ \| / __| / _` | '__| __| |/ __| |/ _ \ |
| (_) | _| | |_| | | | \__ \ | (_| | | | |_| | (__| | __/_|
\___/|_| \__|_| |_|_|___/ \__,_|_| \__|_|\___|_|\___(_)
解説
A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m():
- メソッド呼び出し
x.m()
は、xの型のメソッドセットがm
を含んでおり、かつ与えた引数リストがm
のパラメータリストに代入可能であるとき、有効です。 - もし
x
がアドレス化可能で、かつ&x
のメソッドセットがm
を含むならば、x.m()
は&x.m()
の省略表現となります。
今回の問題を再掲します。
type T struct {
x int
}
func (t T) Get() int {
return t.x
}
func (t T) Set(x int) T {
t.x = x
return t
}
func (t *T) Add(x int) *T {
t.x += x
return t
}
func main() {
var t T
fmt.Println(t.Set(1).Add(1).Get())
}
この中には以下の3つのメソッド呼び出しがあります。
t.Set(1)
t.Set(1).Add(1)
t.Set(1).Add(1).Get()
1つめのt.Set(1)
はtの型がTであってTのメソッドセットにSet
が含まれるため問題ありません。引数の代入可能性ももちろん満たしています。
2つめのt.Set(1).Add(1)
をみると、t.Set(1)
はT
型なのでポインターレシーバーメソッドのAdd
をメソッドセットに含みません。それではt.Set(1)
をアドレス化した&t.Set(1)
の省略表現とみなせるでしょうか?そのためにはt.Set(1)
がアドレス化可能でなければなりません。そこでアドレス化可能性についてみてみましょう。
Addressability より:
For an operand x of type T, the address operation &x generates a pointer of type *T to x. The operand must be addressable, that is, either a variable, pointer indirection, or slice indexing operation; or a field selector of an addressable struct operand; or an array indexing operation of an addressable array. As an exception to the addressability requirement, x may also be a (possibly parenthesized) composite literal.
これによると、addressableな場合は次の5つです。
- 変数である
- pointer indirection
*p
である - スライスに対するインデックス演算の結果である
- アドレス化可能なstructに対するフィールドセレクターである
- アドレス化可能な配列に対するインデックス演算の結果である
t.Set(1)
という式は上記のいずれにも当てはまりませんから、アドレス化可能ではなく、t.Set(1).Add(1)
というメソッド呼び出しはillegalです。したがって、本問のコードはコンパイルできず、次のようなエラーメッセージを出力します。
./prog.go:22:22: cannot call pointer method on t.Set(1)
./prog.go:22:22: cannot take the address of t.Set(1)
解説の流れと同じことをエラーメッセージに書いてくれています。
なお、Addrssabilityについて仕様書から引用した段落の最後の1文は、composite literalがaddressbleではないことを前提としています。この"As an exception"とは、「address operator&
が適用可能なのはaddressableなoperandのみであるという原則」に対する例外を意味しており、「composite literalはaddressableではないけれども&
が適用可能である」という意味です。
これは本問のメソッドを使って次のようなコードを書くと分かります。playgroundのリンクをつけたので確認してみてください。
T{}.Add(1) // compile error. T{}がaddressableではないから。
解説サマリー
- Goのメソッド宣言には、value receiverとpointer receiverという2つの宣言の仕方がある。
- 記述の簡便性のため、
value.PointerReceiverMethod()
は(&value).PointerReceiverMethod()
と解釈される。 - ただし、この記法が有効であるためには、
value
がアドレス化可能(addressable)でなければならない。アドレス化可能な場合は5つある。 - なお、
composite literals
は、&
をつけることができるがアドレス化可能ではない。
正解
(1)の、compile(build) error
が正解です。
Discussion