В пакете sync у структур вроде sync.Mutex, sync.Once и sync.Map есть поле _ noCopy. Это пустая структура с двумя пустыми методами Lock() и Unlock(). Никакой блокировки там нет. Компилятор Go не придаёт этому имени значения: скопировать использованный sync.Map можно, go build пройдёт. Предупреждение выдаёт отдельный инструмент go vet через проверку copylocks: «assignment copies lock value». Он ищет не поле noCopy, а типы, у которых указатель реализует sync.Locker, а само значение — нет. Проверка рекурсивно обходит поля структур. Поэтому noCopy и получил методы Lock и Unlock: указатель *noCopy удовлетворяет sync.Locker, а значение noCopy — нет.
noCopy добавили в стандартную библиотеку в 2016 году. Автор — Aliaksandr Valialkin из VictoriaMetrics, а паттерн предложил Russ Cox. Сначала был только Lock, в 2018 году добавили Unlock, когда copylocks переключился на проверку sync.Locker. Зачем нужен явный маркер, если внутри sync.Map и так есть sync.Mutex? Во-первых, проверка перестаёт зависеть от внутренностей структуры. Во-вторых, сообщение понятнее: «contains sync.noCopy», а не «contains sync.Mutex». В-третьих, маркер ловит ложные отрицания. Например, type LocalMutex sync.Mutex теряет методы Lock и Unlock, но благодаря полю _ noCopy внутри sync.Mutex проверка всё равно находит нарушение. А вот sync.RWMutex не содержит noCopy. Прямое копирование ловится через методы самого RWMutex, а для type LocalRWMutex sync.RWMutex срабатывает вложенное поле w Mutex.
Копировать такие типы не стоит. Классический случай: WaitGroup передали по значению в функцию, Done() меняет копию, а оригинал ждёт вечно. sync.Map, скопированный после первого Store, может дать двум переменным общее хранилище: запись через b видна через a. Если скопировать до первого использования, у каждой переменной будет своё хранилище. Поведение зависит от момента копии.
Свой маркер можно сделать точно так же: пустая структура с методами Lock и Unlock. go test по умолчанию не запускает copylocks, поэтому нужно явно указывать go test -vet=copylocks или go test -vet=all.
Ещё деталь: у noCopy нулевой размер, но если поставить его в конец структуры, Go добавит выравнивание, и размер вырастет. На amd64 и arm64 структура с int64 и trailing noCopy занимает 16 байт вместо 8. В стандартной библиотеке noCopy ставят первым полем, чтобы не раздувать структуру. В atomic.Pointer[T] перед ним ещё идёт нулевое поле _[0]*T.