【Goテクニック】DoNotCopy、DoNotCompare、DoNotImplement

![Go-Logo_Blue.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705474/e0df2a13-e969-ab33-f025-93bc5c906005.png)


**この記事では、Go言語の[google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf)パッケージを利用しているときに見かけた`DoNotCopy`、`DoNotCompare`、`DoNotImplement`の３つのテクニックについて紹介します。**

➡ [DoNotCopy](#donotcopy)：構造体のシャローコピーを防止
➡ [DoNotCompare](#donotcompare)：構造体の比較を防止
➡ [DoNotImplement](#donotimplement)：インターフェースの実装を制限
➡ (おまけ)[NoUnkeyedLiterals](#おまけ-nounkeyedliterals)：構造体のインスタンス生成時にキー指定を強制

# DoNotCopy

`DoNotCopy`は以下のような実装となっています。
([protobuf-go/internal/pragma/pragma.go](https://github.com/protocolbuffers/protobuf-go/blob/master/internal/pragma/pragma.go#L29))

```go
// DoNotCopy can be embedded in a struct to help prevent shallow copies.
// This does not rely on a Go language feature, but rather a special case
// within the vet checker.
//
// See https://golang.org/issues/8005.
type DoNotCopy [0]sync.Mutex
```

DoNotCopyのコメントには以下の通り記載されています。

> DoNotCopy は、構造体に埋め込むことでシャローコピーの防止に役立てることができます。これは Go 言語の機能に依存するのではなく、vet チェッカー内の特別なケースによるものです。

つまり、`DoNotCopy`はある構造体のシャローコピーを防ぐために利用するもののようです。
ただしこれはGoの仕様ではなくGo vetによるチェックに引っかかるだけなので、ビルドと実行は可能な点に注意が必要です。

次のようなコードで検証してみると、Go vetの警告が出ますがプログラムは実行できることが確認できます。
➡[Try on Go Playground](https://go.dev/play/p/GWZy_eHhehY)

```go:DoNotCopyの検証コード
type DoNotCopy [0]sync.Mutex

type Foo struct {
	DoNotCopy
	Bar string
}

func main() {
	f := Foo{
		Bar: "Hello!",
	}
	ff := f
	fmt.Printf("%#v\n", ff)
}
```

```bash:実行結果
# [play]
./prog.go:21:8: assignment copies lock value to ff: play.Foo contains sync.Mutex
./prog.go:22:22: call of fmt.Printf copies lock value: play.Foo contains sync.Mutex

Go vet failed.

main.Foo{DoNotCopy:main.DoNotCopy{}, Bar:"Hello!"}

Program exited.
```

また、このチェックは[sync.Locker](https://pkg.go.dev/sync#Locker)のインターフェースを実装しているか否かが判定基準なので、次のような構造体を利用することでも可能となります。
このテクニックは、[syncパッケージ](https://github.com/golang/go/blob/ed07b321aef7632f956ce991dd10fdd7e1abd827/src/sync/cond.go#L111-L122)などの標準パッケージ内でも利用されているようです。

```go
type DoNotCopy struct{}

func (*DoNotCopy) Lock()   {}
func (*DoNotCopy) Unlock() {}
```

# DoNotCompare

`DoNotCompare`は以下のように実装されています。
([protobuf-go/internal/pragma/pragma.go](https://github.com/protocolbuffers/protobuf-go/blob/master/internal/pragma/pragma.go#L22))

```go
// DoNotCompare can be embedded in a struct to prevent comparability.
type DoNotCompare [0]func()
```


DoNotCompareのコメントには以下の通り記載されています。

> DoNotCompare は、構造体に埋め込むことで比較を防ぐことができます。

つまり、DoNotCompareを利用すると構造体の比較を防ぐことができます。
以下のような構造体を比較するコードで検証してみると、実際にビルドに失敗することが確認できます。
➡ [Try on Go Playground](https://go.dev/play/p/cAf8c2dv83H)

```go:DoNotCompareの検証コード
type DoNotCompare [0]func()

type Foo struct {
	DoNotCompare
	Bar string
}

func main() {
	f := Foo{
		Bar: "Hello!",
	}
	ff := Foo{
		Bar: "World!",
	}
	fmt.Println("f==ff", f == ff)
}
```

```go:実行結果
./prog.go:23:23: invalid operation: f == ff (struct containing DoNotCompare cannot be compared)

Go build failed.
```

これは、Goの言語仕様として下に挙げた２点があるためです（参考：[spec#Comparison operators](https://go.dev/ref/spec#Comparison_operators)）。

1. `Struct types are comparable if all their field types are comparable.`
1. `Slice, map, and function types are not comparable.`

関数は比較不可であり、それを要素に持つスライスもまた比較不可となります。さらに、比較不可な型をフィールドに持つ構造体もまた比較不可となります。
[DoNotCopy](#donotcopy)とは異なり、これは言語仕様に依存したテクニックであるためビルドにも失敗します。

なお、この言語仕様に基づくと以下のような定義でも同様の機能が実現できます。
ただし、見た目のシンプルさとメモリ効率を考慮すると`type DoNotCompare [0]func()`を利用するのが良いように思われます。

```go
type DoNotCompare func()
type DoNotCompare [0]func()

type DoNotCompare map[string]struct{}
type DoNotCompare [0]map[string]struct{}

type DoNotCompare []struct{}
type DoNotCompare [0][]struct{}
```

# DoNotImplement

`DoNotImplement`は以下のよう実装されています。
([protobuf-go/internal/pragma/pragma.go](https://github.com/protocolbuffers/protobuf-go/blob/master/internal/pragma/pragma.go#L19))

```go
// DoNotImplement can be embedded in an interface to prevent trivial
// implementations of the interface.
//
// This is useful to prevent unauthorized implementations of an interface
// so that it can be extended in the future for any protobuf language changes.
type DoNotImplement interface{ ProtoInternal(DoNotImplement) }
```

DoNotImplementのコメントには以下のように記載されています。

> DoNotImplement は、インターフェースに埋め込むことで、そのインターフェースの安易な実装を防ぐことができます。これは、許可されていないインターフェースの実装を防ぐために役立ち、将来的な protobuf の言語変更にも対応できるようにするためのものです。

この説明だけでは具体的な使い方のイメージがつかないので、実際に利用されている箇所を確認してみます。
DoNotImplementは、Protobufのパッケージ内において、例えば[FileImportsインターフェース](https://github.com/protocolbuffers/protobuf-go/blob/b98563540c0a4edb38526bcd6e6c97f9fac1f453/reflect/protoreflect/type.go#L147-L154)などで利用されています。

```go:FileImportsのインターフェース定義
type doNotImplement pragma.DoNotImplement

type FileImports interface {
	Len() int
	Get(i int) FileImport
	doNotImplement
}
```

では、「インターフェースの安易な実装」とはどういう意味か、上記の`FileImports`インターフェースを実装する構造体を定義してみます。
次のような構造体`Foo`を定義して、FileImportsインターフェースを実装することを試みましたが、この実装は機能しません。なぜなら`protoreflect.DoNotImplement`インターフェースが[internalパッケージ](https://go.dev/doc/go1.4#internalpackages)に定義されているからです。加えて`DoNotImplement`インターフェースは自分自身を循環参照しているため、Foo構造体が知らない間に`FileImports`インターフェースを実装していた！なんてことになる可能性は限りなく低くなります。

```go:Foo構造体にFileImportsインターフェースを実装してみる
type Foo struct{}

func (f *Foo) Len() int                                  { return 0 }
func (f *Foo) Get(i int) protoreflect.FileImport         { return protoreflect.FileImport{} }
func (f *Foo) ProtoInternal(protoreflect.DoNotImplement) {}
```

では、パッケージの利用者側で`FileImports`インターフェースを実装したい場合はどうするか？
これは簡単で、`FileImports`をそのまま構造体に埋め込めこみます。コメントにある「許可されていないインターフェースの実装」というのは「意図しないインターフェースの実装」ととらえてよいかなと思います。
このような仕組みはなかなか自分では思いつけないですね...。ｽｺﾞｲ

```go:Foo構造体にFileImportsインターフェースを実装
type Foo struct {
	protoreflect.FileImports
}

func (f *Foo) Len() int                          { return 0 }
func (f *Foo) Get(i int) protoreflect.FileImport { return protoreflect.FileImport{} }
```

# (おまけ) NoUnkeyedLiterals

ここまでに `DoNot***` を利用したテクニックを見てきましたが、protobufの[pragmaパッケージ](https://github.com/protocolbuffers/protobuf-go/blob/master/internal/pragma/pragma.go)にはもう一つ参考になるテクニックがあります。
それが`NoUnkeyedLiterals`です。

`NoUnkeyedLiterals`の実装は以下のようになっています。
([protobuf-go/internal/pragma/pragma.go](https://github.com/protocolbuffers/protobuf-go/blob/bdcc7adc94f2005be83ac21fda8c5eb4f5890400/internal/pragma/pragma.go#L12))

```go
// NoUnkeyedLiterals can be embedded in a struct to prevent unkeyed literals.
type NoUnkeyedLiterals struct{}
```

ただの構造体ですね。
コメントには以下の通り記載されています。

> NoUnkeyedLiteralsは、キーを指定しないリテラルを防ぐために構造体に埋め込むことができます。

Goでは構造体のインスタンスを生成する際にキー名を指定せずにフィールドに値を設定できますが、NoUnkeyedLiteralsを埋め込むことでそれを防ぐことができるということです。
そもそもキー名を指定せずに構造体のインスタンスを生成することは、構造体の変更に弱くなるため推奨されていません。とくにライブラリの開発者の目線では、キー名を指定せずにインスタンスを生成することは将来の変更を考えてもやめてもらいたいことの一つです。

以下のような構造体`Foo`と`Bar`で検証してみます。
NoUnkeyedLiteralsを埋め込んだ`Bar`は、キー名、つまり`name`や`age`を指定せずにインスタンスを生成することはできません。
➡[Try on Go Playground](https://go.dev/play/p/bowzmDOG2d0)


```go:NoUnkeyedLiteralsの検証コード
type NoUnkeyedLiterals struct{}

type Foo struct {
	name string
	age  int
}

type Bar struct {
	NoUnkeyedLiterals
	name string
	age  int
}

func main() {
	fmt.Println(&Foo{"alice", 20}) // OK
	fmt.Println(&Bar{"bob", 20})   // NG
}
```

```go:実行結果
./prog.go:22:19: cannot use "bob" (untyped string constant) as NoUnkeyedLiterals value in struct literal
./prog.go:22:26: cannot use 20 (untyped int constant) as string value in struct literal
./prog.go:22:28: too few values in struct literal of type Bar
```

# まとめ

[google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf)の中を読むことで、言語本来の仕様やリンタのチェック仕様をうまく活用するころで構造体やインターフェースに制約を加えていることがわかりました。こういう仕組みはなかなか自分では思いつけないので、他人のコードを読んで学ぶことの大切さをあらためて実感しました。

# 参考資料

- [protocolbuffers/protobuf-go](https://github.com/protocolbuffers/protobuf-go/blob/master/internal/pragma/pragma.go)
- [The Go Programming Language Specification - Language version go1.23 (June 13, 2024)](https://go.dev/ref/spec)
- [Goの構造体のコピーを防止する方法](https://shogo82148.github.io/blog/2018/05/16/macopy-is-struct/)
- [How to force compiler error if struct shallow copy?](https://stackoverflow.com/questions/68183168/how-to-force-compiler-error-if-struct-shallow-copy)