Goの比較可能性（comparable）

## はじめに

どうも[ナレッジワーク](https://tenn.in/kw)の[tenntenn](https://tenn.in)です。

さっき、次のような記事を書いたんですが、そもそも比較可能性について、あまり知られてなさそうということでまとめることにしました。

https://qiita.com/tenntenn/items/b33e799567a94928a663

## 比較可能性（comparable)

Goには比較可能な型と比較できない型があります。たとえば、`int`型や`string`型は比較できますが、スライスや関数は比較ができません。

[言語仕様](https://go.dev/ref/spec#Comparison_operators)には、真偽値、整数値、浮動小数点値、複素数値、文字列、ポインタ、チャネル、インタフェース、構造体（条件あり）、配列（条件あり）が比較可能としています。

比較可能の場合、`==`演算子および`!=`演算子で比較できます。順序付け可能な場合は、さらに`<`、`<=`、`>`、`>=`演算子が使用できます。`int`型などの数値型や文字列型がこれにあたります。

比較可能な型は、マップのキーに仕様できます。意外かもしれませんが、構造体や配列も次のようにマップのキーとして使用できます（一部を除く）。

```go
type Point struct {
	X int
	Y int
}
m1 := map[Point]int{{X: 100, Y: 200}: 20}
fmt.Println(m1)

m2 := map[[2]int]int{{100, 200}: 30}
fmt.Println(m2)
```
[The Go Playgroundで動かす](https://go.dev/play/p/mZYETzwN-te)

入れ子になったコンポジットリテラルでは要素の型名を省略できるため便利ですね。キーが2つあるような場合に使えます。

## 比較できない型

比較できない型は、スライス、マップ、関数（メソッド値やメソッド式も含む）です。これらの型は、`nil`との比較しかできません。

```go
var s []int
var m map[any]any
var f func()
fmt.Println(s == nil, m == nil, f == nil) // OK
fmt.Println(s == s, m == m, f == f)       // NG
```
[The Go Playgroundで動かす](https://go.dev/play/p/dqh9JSc8FYR)

なお、この他にも後述する一部の構造体と配列も比較できないものがあります。

## 構造体と配列の比較

構造体と配列は基本的には比較可能な型です。しかし、それらを構成するフィールドや要素に比較できない型がある場合は、比較できません。

たとえば、次のように関数の配列や構造体のフィールドにスライスがある場合は比較できません。

```go
var x [2]func()
var y struct{ s []int }
fmt.Println(x == x, y == y) // NG
```
[The Go Playgroundで動かす](https://go.dev/play/p/pUqzY8PGWeb)

## インタフェースの比較

インタフェースは比較可能です。しかし、インタフェースは動的型（dynamic type）と動的値（dynamic value）の２つから構成されるため、少し複雑です。

インタフェース値が等しいのは、動的型と動的値がともに等しい場合です。
動的型と動的値とは、たとえば、次のような`any`型の変数`x`があった場合、`int`型と`100`です。

```go
var x any
var n int = 100
x = n
// 動的型:int、動的値：200
fmt.Printf("動的型:%[1]T 動的値:%[1]v\n", x)
```
[The Go Playgroundで動かす](https://go.dev/play/p/s1De0eMwZnZ)

動的型は`fmt.Printf`関数の`%T`verbsで表示できます。ちなみに、`[1]`は1番目の引数を表します。同じ引数について違う書式を使用して表示したい場合に用いると便利です。

次のような`any`型の変数`x`と変数`y`を比較すると`false`になります。動的型は`int`型で同じですが、動的値が違うためです。

```go
var x, y any = 100, 200
fmt.Println(x == y)
```
[The Go Playgroundで動かす](https://go.dev/play/p/_yXWnsEzfsE)

次のように動的型が違う場合も`false`になります。

```go
var x, y any = int(100), int32(100)
fmt.Println(x == y)
```
[The Go Playgroundで動かす](https://go.dev/play/p/mnJmeRexcm1)

動的型が同じであっても、比較できない型の場合はパニックが発生します。

```go
var x, y any = []int{100}, []int{100}
fmt.Println(x == y)
```
[The Go Playgroundで動かす](https://go.dev/play/p/EVFwkXRaXoU)

もちろん、動的値が同じ（ように感じる場合）であってもパニックが発生します。

```go
ns := []int{100}
var x, y any = ns, ns
fmt.Println(x == y)
```
[The Go Playgroundで動かす](https://go.dev/play/p/yn8PpZ5EhAd)

比較する２つのインタフェース値の動的型のうち片方または両方が比較できない型であっても、同じ型でなければパニックにはならず、`false`となります。

```go
var x, y any = []int{100}, []int32{100}
fmt.Println(x == y)
```
[The Go Playgroundで動かす](https://go.dev/play/p/7Q4Q5VRnzmR)

表にまとめると次のようになります。

| xの動的型がcomparable | yの動的型がcomparable | 動的型が同じ | 動的値が同じ | x == y |
|:-:|:-:|:-:|:-:|:-:|
| ◯  | ◯  | ◯  | ◯  | `true` |
| ◯  | ◯  | ◯  | ☓  | `false`|
| ◯  | ◯  | ☓  | ◯  | `false`|
| ◯  | ◯  | ☓  | ☓  | `false`|
| ◯  | ☓  | ◯  | ◯  | panic |
| ◯  | ☓  | ◯  | ☓  | panic |
| ◯  | ☓  | ☓  | ◯  | `false`|
| ◯  | ☓  | ☓  | ☓  | `false`|
| ☓  | ◯  | ◯  | ◯  | panic |
| ☓  | ◯  | ◯  | ☓  | panic |
| ☓  | ◯  | ☓  | ◯  | `false`|
| ☓  | ◯  | ☓  | ☓  | `false`|
| ☓  | ☓  | ◯  | ◯  | panic |
| ☓  | ☓  | ◯  | ☓  | panic |
| ☓  | ☓  | ☓  | ◯  | `false`|
| ☓  | ☓  | ☓  | ☓  | `false`|

ざっくり、まとめると以下のようになります。

* 動的型が違う場合は必ず`false`
* 動的型が同じでどちらかの動的型が比較不可能な場合はパニック
* 動的型が同じでcomparableでかつ動的値が同じ場合は`true`
* 動的型が同じでcomparableでかつ動的値が違う場合は`false`

## おわりに

比較と一言でいってもなかなかの奥深さですね。