Deep Engineering

ЗАМЕР

bench/gostruct/example.sh

Скрипт, которым получены числа в статье, и запись прогона. Файл читается на сборке из репозитория — это тот самый код, который запускали, а не его копия.

Цитируется в статье
/ru/interview/golang/structs
Как запустить
go run bench/gostruct/internals.go
bash bench/gostruct/compile.sh
go run bench/gostruct/sets.go
GOEXPERIMENT=noswissmap go run bench/gostruct/sets.go
/usr/local/go1.25.1/bin/go run bench/gostruct/sets.go
go run bench/gostruct/practice.go
bash bench/gostruct/example.sh

Запись прогона

Замеры для урока «Структуры и методы»

Файл Что делает
internals.go восемь наблюдений без единого замера времени: нулевое значение без конструктора, копия при присваивании, передаче и range, поверхностность копии, сравнение == и структура ключом карты, размер и выравнивание при двух порядках полей, пустая структура, метод как функция с получателем и определённый тип без методов базового, анонимная структура; после аудита 14.10.2026 — раздел 9 (slices.Clone/maps.Clone поверхностны, массив указателей копирует указатели, рекурсивный Clone) и раздел 10 (поле any: сравнимо при компиляции, паника при выполнении)
compile.sh собирает cases/* (или проверяет их go vet и golangci-lint) и печатает ответ инструмента: == и ключ карты у структуры со срезом, метод на чужом типе, смешанный и позиционный литерал, копия структуры с sync.Mutex, анализатор fieldalignment
cases/*/ подопытные программы к compile.sh, у каждой свой go.mod; большинство намеренно не собирается
sets.go память карты на миллион строковых ключей со значениями struct{}, bool и int64; размер слота «ключ–значение»
example.sh, cases/example/ полный короткий пример урока (go run . в чистом каталоге с go.mod)
practice.go источник ответов двух практических задач урока
go run bench/gostruct/internals.go
bash bench/gostruct/compile.sh
go run bench/gostruct/sets.go
GOEXPERIMENT=noswissmap go run bench/gostruct/sets.go
/usr/local/go1.25.1/bin/go run bench/gostruct/sets.go
go run bench/gostruct/practice.go
bash bench/gostruct/example.sh

Записи прогонов — в runs/, go1.24.7 linux/amd64, 2 ядра: internals.txt, compile.txt, sets.txt, sets-noswissmap.txt (та же go1.24.7 с прежней реализацией карт), example.txt, practice.txt; на go1.25.1 — internals-go1.25.txt, compile-go1.25.txt, example-go1.25.txt, sets-go1.25.txt: отличаются от go1.24.7 только строкой версии. Сверено 14.10.2026; Go 1.26 и 1.27 не измерялись. golangci-lint в compile.sh — версии 2.5.0, собранный go1.25.1.

Что получилось

Нулевое значение может быть рабочим — если тип так написан. var mu sync.Mutex сразу берётся Lock, var buf bytes.Buffer сразу пишется. Свой тип Stats, который создаёт карту при первой записи, тоже работает без конструктора, а Registry, который ждёт карту от конструктора, падает: assignment to entry in nil map.

Присваивание копирует структуру целиком, но копия поверхностная. После b := a и правок в b у a не изменились строка и массив [3]int, а изменились элемент среза, значение в карте и значение под указателем: unsafe.Sizeof(a) = 80 — копируются заголовки, а не данные за ними. range по срезу структур отдаёт копию: v.X = 0 не меняет pts[0].X. Копию структуры с sync.Mutex компилятор пропускает, а go vet ловит в трёх местах.

== у структуры — пока сравнимы все поля. Point{1, 2} == Point{1, 2} — true, Point работает ключом карты. Структура с полем []string не сравнивается (struct containing []string cannot be compared) и не может быть ключом (invalid map key type Team), а за интерфейсом сравнение собирается и паникует: comparing uncomparable type main.Team; ключом map[any]int — тоже паника, с другим текстом: hash of unhashable type main.Team. Указатели сравниваются адресами: две структуры с указателями на равные числа не равны.

Порядок полей меняет размер. struct{ a bool; b int64; c bool } — 24 байта, смещения 0, 8, 16; struct{ b int64; a bool; c bool } — 16, смещения 0, 8, 9. Полезных байт в обоих 10. fieldalignment: struct of size 24 could be 16 — в обычный go vet он не входит, в cases/fieldalign/.golangci.yml он включён явно.

Пустая структура весит 0 — кроме как в конце другой структуры. struct{ z struct{}; n int64 } — 8 байт, struct{ n int64; z struct{} } — 16: за пустым полем в конце компилятор добавляет байт (cmd/compile/internal/types/size.go, строки 517–523 в go1.24.7), и выравнивание делает из него восемь.

Поэтому map[string]struct{} на go1.24 весит столько же, сколько map[string]bool. Миллион строковых ключей: 56 МБ, 56 МБ и 56 МБ у int64, кратность bool к struct{} — 1.00. Слот карты — структура «ключ, значение», и struct{ key string; elem struct{} } весит те же 24 байта, что с bool и int64. На go1.25.1 — те же 56 МБ. С прежней реализацией карт (GOEXPERIMENT=noswissmap) — 40, 42 и 58 МБ: там ключи и значения корзины лежат раздельно.

Метод — функция с получателем первым аргументом. Celsius.Fahrenheit(100) даёт то же, что Celsius(100).Fahrenheit(), а тип выражения — func(main.Celsius) float64. Метод на time.Duration и на int объявить нельзя: cannot define new methods on non-local type. Новый тип MyDuration с тем же базовым int64 методов time.Duration не получает: печатается 1500000000, а не 1.5s.

Литерал: имена полей или позиции. Смешивать нельзя (mixture of field:value and value elements). Позиционный литерал чужого типа собирается, но go vet предупреждает (struct literal uses unkeyed fields), а после того как в тип добавили поле, перестаёт собираться: too few values in struct literal of type geo.Point.

Clone тоже поверхностный (раздел 9). После slices.Clone у [][]int правка c[0][0] видна в исходном (s[0][0] = 99), после maps.Clone у map[string][]int — тоже; у []int и map[string]int — нет. b := a для [2]*int копирует указатели: *a[0] = 99. Метод Doc.Clone, клонирующий каждый внутренний срез, даёт независимую копию: doc.Grid[0][0] = 1.

Поле-интерфейс (раздел 10). type Key struct{ X any } сравним при компиляции; Key{[]int{1}} == Key{[]int{1}} — паника comparing uncomparable type []int, вставка ключом карты — hash of unhashable type []int.

Скрипт

21 строк
#!/usr/bin/env bash
#
# Полный короткий пример урока: файл cases/example/main.go со своим go.mod
# (go 1.24), который можно скопировать в чистый каталог и запустить
# командой go run . — без зависимостей от остальных файлов bench/gostruct.
#
# ЗАЧЕМ. Добавлен после внешнего аудита 14.10.2026: остальные программы
# каталога — стенды наблюдений, их вывод не заменяет запускаемого примера.
#
# ЗАПУСК:
#
#	bash bench/gostruct/example.sh
set -u

GO=${GO:-go}
cd "$(dirname "$0")/cases/example"

echo "$($GO env GOVERSION) $($GO env GOOS)/$($GO env GOARCH) | полный пример: go run ."
echo
$GO run .