GoのnoCopyマーカー:go vetが安全でない構造体コピーを検出する仕組み
GoのsyncパッケージのnoCopyマーカーはコンパイラのルールではなく、go vetのcopylocksチェッカーへの巧妙なフックです。その仕組みと、独自の型での使い方を解説します。

syncパッケージのsync.Mutex、sync.Once、sync.Mapなどの構造体には、すべてnoCopy型のフィールドが含まれています。このマーカーは、LockとUnlockという2つの空メソッドを持つ空の構造体です。Goコンパイラに特別なルールを追加するわけではありません。sync.Mapを使用後にコピーしてもgo buildは通ります。警告はgo vet、具体的にはそのcopylocksチェッカーから出ます。
チェッカーのルールはシンプルです。ポインタがsync.Lockerを実装しているが、値自体は実装していない型を探します。noCopyの場合、ポインタレシーバメソッドにより*noCopyがsync.Lockerを実装し、値型は実装しません。チェッカーは構造体フィールドを再帰的に検査するため、sync.Map内のnoCopyを見つけ、assignment copies lock value to b: sync.Map contains sync.noCopyと報告します。
sync.Mapがすでにsync.Mutexを含んでいるのに、なぜ明示的なマーカーが必要なのでしょうか?理由は3つあります。警告が明確になる、内部実装に依存しない、誤検知を修正できる、です。type LocalMutex sync.Mutexのような型はLock/Unlockメソッドを失うため、基になる構造体にnoCopyがなければチェッカーは見逃してしまいます。
独自のnoCopy型を自分のパッケージで定義できます。パターンをコピーするだけです:
type noCopy struct{}
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
type Session struct {
_ noCopy
id string
closed bool
}go testはデフォルトでcopylocksを実行しないことに注意してください。これらのコピーを検出するには、go vet ./...またはgo test -vet=copylocks ./...を実行する必要があります。
警告を無視すると何が壊れるのでしょうか?場合によります。コピーされたsync.WaitGroupは、カウンタがコピーされDone()がコピーのみをデクリメントするため、Wait()が永遠に戻らない可能性があります。ミューテックスをコピーすると、データ競合やデッドロックが発生する可能性があります。
noCopyマーカーは、Russ Coxのパターンに基づいて、2016年にAliaksandr Valialkin(VictoriaMetrics CTO)によって追加されました。これは巧妙な静的解析フックであり、実行時の安全ネットではありません。
noCopyマーカーはGoコンパイラに特別なルールを追加しません。警告はgo vetのcopylocksチェッカーから出ます。これは、ポインタがsync.Lockerを実装するが値が実装しない型を探します。
ディスカッション
0 件のコメント
最初のコメントを投稿しましょう。