Практика: Go 1.26 (Go)
Вопросы для практики — Go 1.26 — из наборов курса «Go», доступных без аккаунта: по несколько из каждой части с ответом и объяснением, а также тест по этим наборам.
начать практику
Откроет карту курса «Go»: одно нажатие, без регистрации.
Посмотрите ответ, чтобы проверить себя. Эти вопросы написаны для wspell; за ними нет экзамена третьей стороны. Тест работает без аккаунта.
коротко о курсе
- экзамены
- Go 1.26
- вопросы
- 4 743 вопроса
- карточки
- 2 314 карточек
- без аккаунта
- 30 из 384 наборов
- части
- 10 частей
- примеры вопросов
- 30 примеров вопросов
Go 1.26
10 частей; без аккаунта — 30 наборов · 398 вопросов · 166 карточек. На странице 30 примеров вопросов с ответами — по несколько из каждой части.
Тренировать вопросы: Go 1.26 →
Язык
7 наборов · 92 вопроса · 38 карточек
из набора Заголовок среза: len, cap и базовый массив
An API keeps three records and returns only the first two to an untrusted caller: records := []string{"public", "public2", "SECRET"} shown := records[:2] The caller receives shown. Which statement is true?- It is safe: returning a sub-slice copies the elements into a fresh array
- The caller can recover "SECRET" with shown[:cap(shown)], because reslicing is bounded by capacity
- It is safe: a reslice can never reach past the length it was given
- The caller panics if it reslices past the length, so the data is protected
показать ответ
ответ B. The caller can recover "SECRET" with shown[:cap(shown)], because reslicing is bounded by capacity
Третья запись НЕ защищена. У
shownдлина 2, но ёмкость 3, а выражение среза ограничено ёмкостью, поэтомуshown[:cap(shown)]печатает[public public2 SECRET]— проверено запуском. Выражение среза ничего не копирует и не очищает, так что вызывающая сторона держит указатель в тот же массив. Решение — полная форма срезаrecords[:2:2], которая ограничивает ёмкость до 2, и тогда ни пересрезание, ни append не увидят и не перезапишут третий элемент; другое исправление —slices.Clone(records[:2]), который вдобавок разрывает совместное владение при записи. Именно ради этого и существует трёхиндексная форма, и это в равной мере касается []byte, возвращаемых парсерами.официальная документация
из набора map: семантика, comma-ok, delete и неупорядоченный обход
m := map[float64]string{} m[math.NaN()] = "a" m[math.NaN()] = "b" fmt.Println(len(m), m[math.NaN()] == "") What is printed?- 2 true
- It panics: NaN is not a valid map key
- 2 false
- 1 false — the second assignment overwrites the first
показать ответ
ответ A. 2 true
Печатается
2 true. float64 — сравнимый тип, поэтому NaN является вполне законным ключом и паники не будет, но NaN — единственное значение, не равное самому себе, поэтому каждое присваивание не находит существующего ключа и создаёт НОВУЮ запись, а найти её потом невозможно. В map оказывается две записи, до которых можно добраться только обходом (или удалить через clear);m[math.NaN()]возвращает нулевое значение — пустую строку. Вывод обобщается: избегайте ключей с плавающей точкой везде, где возможен NaN, включая структуры-ключи с полем float, и не считайте «значение находится обратно» свойством любого сравнимого типа ключа.официальная документация
из набора Интерфейсы: наборы методов, реализация и пустой интерфейс
type T struct{} func (*T) String() string { return "T" } func main() { var s1 fmt.Stringer var a1 any = s1 var p *T var s2 fmt.Stringer = p var a2 any = s2 fmt.Println(a1 == nil, a2 == nil) } What does this print?- true false
- true true
- false false
- false true
показать ответ
ответ A. true false
Программа печатает
true false. s1 — nil-интерфейс, без динамического типа и значения, а присваивание интерфейса интерфейсу копирует его пару (тип, значение), поэтому a1 тоже не получает динамического типа и равен nil. С s2 иначе: ему присвоили nil типа *T, так что его пара — (*T, nil); копирование в a2 сохраняет динамический тип *T, а интерфейс равен nil, только если у него НЕТ динамического типа. FAQ Go формулирует правило: «Интерфейсное значение равно nil, только если и V, и T не установлены».true trueсчитает, что nil-указатель внутри делает nil всё значение; варианты с a1, равным false, предполагают, что проход через fmt.Stringer оставляет тип, но у nil-интерфейса нет типа, который можно оставить.официальная документация
Дженерики
2 набора · 15 вопросов · 21 карточка
из набора Объявление параметров типа у функций
func Largest[T int | float64 | string](vals ...T) T { m := vals[0] for _, v := range vals[1:] { if v > m { m = v } } return m } func main() { fmt.Println(Largest(3, 9, 4), Largest("ant", "zebra")) } What does this program print?- It does not compile: > is not defined for a type parameter whose type set includes string.
- 9 ant
- 9 zebra
- It does not compile: a variadic parameter may not have a type-parameter element type.
показать ответ
ответ C. 9 zebra
Программа печатает
9 zebra. Вариативный параметр внутри функции - это просто[]T, поэтому ничто не мешает T быть типовым параметром; вывод типов берёт тип из вариативных аргументов: int для первого вызова (нетипизированные константы получают тип по умолчанию) и string для второго. Оператор>доступен, потому что КАЖДЫЙ тип во множествеint | float64 | stringупорядочен, а строки сравниваются лексикографически побайтно, так что "zebra" > "ant". Если бы в объединение входил тип без порядка - структура или срез, - сравнение было бы отвергнуто. Стандартная запись такого ограничения в библиотеке - cmp.Ordered.официальная документация
из набора Объявление параметров типа у функций
func F[T any, U T](a T, b U) { fmt.Println(a, b) } func main() { F[int, int](1, 2) } In Go 1.26, why is this package rejected?- Because U is never used anywhere in the body of the function F.
- Because a type parameter may not be used as another parameter's constraint.
- Because a constraint has to be an interface literal, not a named interface.
- Because F is instantiated with two identical type arguments.
показать ответ
ответ B. Because a type parameter may not be used as another parameter's constraint.
go1.26.4 сообщает:
cannot use a type parameter as constraint. Ограничение - это интерфейс, задающий множество типов, а типовой параметр - лишь заполнитель, а не интерфейс; в примерах самой спецификацииinterface{ P }помечен как недопустимый именно по этой причине. Что типовой параметр МОЖЕТ, так это входить в ограничение другого параметра в качестве типового аргумента или составной части:[S ~[]E, E any]и[P Constraint[int]]законны и являются стандартным способом связать два параметра. Инстанциация двумя одинаковыми типовыми аргументами обычна; неиспользуемый типовой параметр законен; а ограничением может быть и именованный интерфейс, например cmp.Ordered, ничуть не хуже литерала.официальная документация
из набора Объявление параметров типа у функций
func Describe[P any](v P) string { type L P var l L return fmt.Sprintf("%T %T", v, l) } func main() { fmt.Println(Describe(1)) } In Go 1.26, what does the compiler report for this package?- It fails to build, because a type may not be declared inside a generic function.
- Nothing: it prints `int int`, since L is defined as P and P is int here.
- Nothing: it prints `int main.L`, since L is a distinct defined type.
- It fails to build, because a type parameter may not be the right-hand side of a type declaration.
показать ответ
ответ D. It fails to build, because a type parameter may not be the right-hand side of a type declaration.
go1.26.4 сообщает:
cannot use a type parameter as RHS in type declaration, и спецификация формулирует то же ограничение примеромfunc f[P any]() { type L P } // illegal. Определённому типу нужен единственный фиксированный базовый тип в момент объявления, а типовой параметр - заполнитель, означающий разный тип для каждой инстанциации, так что базовому типу L просто нечем быть. Локальные объявления типов внутри обобщённой функции в остальном законны -type L []intтам компилируется, - поэтому широкий ответ «типы внутри обобщённой функции запрещены» неверен. Поскольку пакет не компилируется, ни один из вариантов с печатью произойти не может.официальная документация
Конкурентность
4 набора · 52 вопроса · 16 карточек
из набора Горутины: инструкция go, вычисление аргументов и время жизни
A method with a value receiver is started as a goroutine, and the caller then mutates the struct. type counter struct{ n int } func (c counter) report(done chan<- struct{}) { fmt.Println("n =", c.n) close(done) } func main() { done := make(chan struct{}) c := counter{n: 1} go c.report(done) c.n = 99 <-done } What does this program print in Go 1.26, and is the result guaranteed?- n = 99 on some runs, and the race detector flags c.n
- n = 1 or n = 99, depending on which goroutine runs first
- n = 99 on every run, because report starts after the write
- n = 1 on every run, and the race detector stays silent
показать ответ
ответ D. n = 1 on every run, and the race detector stays silent
Проверено на go1.26.4:
n = 1в 10 из 10 запусков, а сборка с-raceничего не сообщает (3 запуска).Оператор go вычисляет значение функции
c.reportв вызывающей горутине, и спецификация (раздел Method values) говорит о таком значении: «The expression x is evaluated and saved during the evaluation of the method value; the saved copy is then used as the receiver in any calls, which may be executed later». При получателе-значении сохранённая копия — это копия всей структуры, снятая, покаc.nещё равно 1. Горутина читает только эту копию, так что общего доступа к памяти нет вовсе.Вариант «n = 99 on every run, because report starts after the write» неверен: момент старта горутины не важен, потому что она никогда не читает
cиз main.Варианты «n = 1 or n = 99, depending on which goroutine runs first» и «n = 99 on some runs, and the race detector flags c.n» предполагают, что горутина читает переменную; она читает копию. Они стали бы верными только при получателе-указателе,
func (c *counter) report(...): тогда сохраняется лишь указатель, и чтение гоняется сc.n = 99.официальная документация
из набора Небуферизованные каналы и синхронная передача
A monitoring line reports the queue depth while a sender is parked on an unbuffered channel. func main() { ch := make(chan int) go func() { ch <- 7 }() time.Sleep(50 * time.Millisecond) fmt.Println(len(ch), cap(ch)) fmt.Println(<-ch) } What does this print? The two output lines are shown separated by a slash.- 1 0 / 7
- 1 1 / 7
- 0 1 / 7
- 0 0 / 7
показать ответ
ответ D. 0 0 / 7
Проверено на go1.26.4:
0 0, затем7, 10 из 10 запусков, детектор гонок молчит.Спецификация (Length and capacity) определяет
lenдля канала как «number of elements queued in channel buffer», аcap— как «channel buffer capacity». У небуферизованного канала буфера нет, поэтому оба значения равны 0 — независимо от того, ждёт ли сейчас отправитель. Значение ждущего отправителя не стоит в очереди канала; оно передаётся напрямую в момент приёма, поэтому вторая строка —7.Результат даже не зависит от паузы: и до прихода отправителя ответ был бы
0 0.Варианты «1 1 / 7» и «1 0 / 7» считают припаркованного отправителя элементом очереди; это не так.
Вариант «0 1 / 7» считает небуферизованный канал каналом ёмкости один; у
make(chan int)ёмкость нулевая.официальная документация
из набора select: псевдослучайный выбор, default и вечная блокировка
A reviewer asks whether these two receives differ: ch := make(chan int, 2) ch <- 5 ch <- 6 select { case v := <-ch: fmt.Println("select got", v) } v := <-ch fmt.Println("plain got", v) In Go 1.26, what is the difference?- The select form panics if the channel is closed, while the plain receive returns the zero value
- The select form is non-blocking, while the plain receive blocks until a value arrives
- The select form re-evaluates ch on every execution, so it must not be used with a nil channel
- There is none in behaviour — a single-case select with no default blocks exactly like a plain receive
показать ответ
ответ D. There is none in behaviour — a single-case select with no default blocks exactly like a plain receive
Select с одной веткой и без
defaultимеет ровно ту же семантику, что и сама эта операция: он блокируется, пока ветка не сможет выполниться. Компилятор даже сводит его к обычной операции, так что ни накладных расходов, ни разницы в поведении нет — фрагмент печатаетselect got 5иplain got 6. Неблокирующим select делает толькоdefault, и именно это заблуждение здесь и проверяется. Ни одна из форм не паникует на закрытом канале: приём из закрытого канала всегда законен и даёт нулевое значение с ok == false. А то, что select вычисляет операнды-каналы при каждом выполнении, верно и для обычного приёма; на nil-канале обе формы просто блокируются навсегда.официальная документация
Ошибки
3 набора · 33 вопроса · 18 карточек
из набора Интерфейс error, значения ошибок и их создание
type result struct { n int err error } func main() { var r result fmt.Println(r.err == nil, r.n, r.err) } What is printed?- true 0, then a nil-pointer panic when r.err is printed.
- It does not compile: a struct field of interface type needs an explicit initialiser.
- false 0 <nil>
- true 0 <nil>
показать ответ
ответ D. true 0 <nil>
Печатается
true 0 <nil>. Нулевое значение любого интерфейсного типа, включая error, - это nil, поэтому только что объявленная структура сразу означает «ошибки нет» без каких-либо действий с вашей стороны; благодаря этомуvar r resultи каналы таких структур пригодны к использованию немедленно. Печатать nil-ошибку безопасно: fmt проверяет интерфейс до того, как что-то вызывать, поэтому разыменовывать нечего и паники не будет. Инициализатор не нужен - нулевые значения в Go всегда определены, - а nil в поле и есть то состояние, которое код затем проверяет черезr.err == nil.официальная документация
из набора Обёртывание через %w, несколько %w и Unwrap
var ErrClosed = errors.New("connection closed") func main() { a := fmt.Errorf("send: %w", ErrClosed) b := fmt.Errorf("send: %v", ErrClosed) fmt.Println(a.Error() == b.Error()) fmt.Println(errors.Is(a, ErrClosed), errors.Is(b, ErrClosed)) fmt.Println(errors.Unwrap(a) == ErrClosed, errors.Unwrap(b)) } What is printed (output lines separated by `/`)?- true / true false / true <nil>
- false / true false / true <nil>
- true / true true / true <nil>
- true / true false / false <nil>
показать ответ
ответ A. true / true false / true <nil>
Печатается
true,true false,true <nil>. Оба сообщения побайтово одинаковы - %w форматирует операнд точно так же, как %v, - поэтому по тексту невозможно понять, какую из ошибок вызывающий сможет разобрать. Разница структурная: %w запоминает операнд, поэтому у возвращённой ошибки есть метод Unwrap, отдающий ErrClosed обратно, и errors.Is его находит. %v просто подставляет текст, создавая обычную ошибку, которая ничего не оборачивает, поэтому errors.Is даёт false, а errors.Unwrap возвращает nil. Это самый значимый выбор одного символа во всей обработке ошибок в Go, и в логах он невидим: случайный %v проявляется как «errors.Is перестала совпадать», а не как ошибка форматирования.официальная документация
из набора Интерфейс error, значения ошибок и их создание
type tempErr struct{ retryIn int } func (e tempErr) Error() string { return fmt.Sprintf("retry in %ds", e.retryIn) } func main() { e := tempErr{retryIn: 3} fmt.Printf("%v|%s|%d\n", e, e, e.retryIn) } What is printed?- retry in 3s|%!s(main.tempErr)|3
- {3}|{3}|3
- retry in 3s|retry in 3s|3
- {3}|retry in 3s|3
показать ответ
ответ C. retry in 3s|retry in 3s|3
Печатается
retry in 3s|retry in 3s|3. fmt отдаёт приоритет интерфейсу error, а не форматированию структуры по умолчанию: если операнд реализует error, fmt вызывает Error() и форматирует полученную строку по глаголу - и это относится и к %v, и к %s, поэтому оба поля вывода - результат метода, а не{3}. Третьему глаголу передано e.retryIn, обычный int, поэтому печатается 3 - метод там ни при чём. Обратите внимание: у метода приёмник-значение, поэтому error реализует само значение e, а не только &e; с приёмником-указателемfmt.Printf("%v", e)напечатал бы{3}, так как tempErr перестал бы быть error.официальная документация
Стандартная библиотека
5 наборов · 79 вопросов · 32 карточки
Тренировать: Стандартная библиотека
из набора fmt — глаголы, флаги, ширина и точность
func main() { n := 255 fmt.Printf("%b %o %#o %#x %#X\n", n, n, n, n, n) } What does this print?- 11111111 377 0377 0xff 0XFF
- 11111111 0377 377 0xff 0xFF
- 255 377 0o377 0xff 0XFF
- 11111111 377 0o377 ff FF
показать ответ
ответ A. 11111111 377 0377 0xff 0XFF
Печатается
11111111 377 0377 0xff 0XFF. %b - это основание 2 без префикса, %o - основание 8 без префикса, а флаг «решётка» добавляет тот префикс, который использовал бы исходник Go для данного глагола:0для %#o (старый восьмеричный префикс, а не0o),0xдля %#x и0Xдля %#X. Регистр буквыxв префиксе следует за регистром глагола, как и регистр шестнадцатеричных цифр, поэтому %#X даёт0XFF. В варианте0377 377восьмеричные формы с префиксом и без переставлены местами, а вариант, начинающийся с255, печатает десятичное там, где запрошен %b. Префикс0oв Go существует с версии 1.13, и fmt умеет его выводить - но глаголом %O, а не %#o.официальная документация
из набора strings — поиск, разбиение, обрезка, замена и регистр
func main() { fmt.Println(strings.ToUpper("straße"), strings.ToLower("HÉLLO"), strings.EqualFold("Go", "GO")) } In Go 1.26, what does this print?- STRASSE héllo true
- STRASSE HÉLLO false
- STRAßE héllo false
- STRAßE héllo true
показать ответ
ответ D. STRAßE héllo true
Печатается
STRAßE héllo true. Функции ToUpper и ToLower применяют ПРОСТОЕ отображение регистра Unicode, руна за руной, поэтому руна, у которой нет однорунной формы верхнего регистра, остаётся как есть: ß остаётся ß, ведь её настоящий верхний регистр - двухсимвольное SS, а это особое правило данная функция намеренно не реализует (его реализует golang.org/x/text/cases). У букв с диакритикой отображения взаимно однозначные, поэтому É переходит в é. Функция EqualFold сравнивает по простому сворачиванию регистра Unicode - это правильный способ спросить «одно ли это слово без учёта регистра»: для Go и GO она даёт true, поэтому все ответы сfalseотпадают. Заметьте, это не то же самое, что сравнивать ToLower обеих строк, хотя здесь результаты совпадают. Ответы соSTRASSEожидают полного, зависящего от языка отображения регистра, которое даёт только x/text.официальная документация
из набора encoding/json — маршалинг, теги структур, omitempty и omitzero
type User struct { Name string email string Age int `json:"age"` } func main() { b, err := json.Marshal(User{Name: "ann", email: "a@b.c", Age: 30}) fmt.Println(string(b), err) } What is printed?- {"Name":"ann","email":"a@b.c","age":30} <nil>
- {"Name":"ann","age":30} and a non-nil error
- {"Name":"ann","age":30} <nil>
- {"name":"ann","age":30} <nil>, since an untagged field is lower-cased to match JSON convention.
показать ответ
ответ C. {"Name":"ann","age":30} <nil>
Печатается
{"Name":"ann","age":30} <nil>. Маршалятся только экспортированные поля, а неэкспортированное пропускается совершенно молча - ни ошибки, ни предупреждения, ни намёка в выводе на то, что значение потеряно. Это самая частая неожиданность в этом пакете: поле, переименованное в нижний регистр, или структура, скопированная из другого пакета с урезанными заглавными буквами, просто перестаёт сериализоваться. Обратите также внимание, что экспортированное поле без тега сохраняет своё имя Go в точности, вместе с заглавной буквой, поэтому Name выводится как "Name", а не "name"; приведение ключа к нижнему регистру - это работа тега у поля Age.официальная документация
Рантайм
2 набора · 18 вопросов · 21 карточка
из набора Планировщик GMP: G, M, P, очереди выполнения и кража работы
Two goroutines are started one immediately after the other, with no synchronisation between them. What is guaranteed about the order in which their first statements run?
- Nothing is guaranteed; only operations joined by a synchronisation edge are ordered.
- They run in creation order unless one of them blocks, at which point the other overtakes it.
- The goroutine created first runs first, because it is enqueued first.
- With GOMAXPROCS set to 1 the creation order is honoured, and only above 1 does it become unpredictable.
показать ответ
ответ A. Nothing is guaranteed; only operations joined by a synchronisation edge are ordered.
Модель памяти Go упорядочивает события только через синхронизацию - операцию с каналом, мьютекс, WaitGroup, атомарную операцию с подходящим порядком. Оператор go гарантирует лишь то, что новая горутина стартует после самого оператора go, и ничего не говорит о её положении относительно других горутин. Локальные очереди планировщика, кража работы и вытеснение свободно перетасовывают порядок, поэтому ответы, обещающие порядок создания, опираются на случайность реализации, меняющуюся от запуска к запуску. Установка GOMAXPROCS в 1 ничего в гарантии не меняет: один P всё равно чередует горутины в произвольных точках.
официальная документация
из набора Планировщик GMP: G, M, P, очереди выполнения и кража работы
A program running with GOMAXPROCS set to 1 has ten goroutines blocked receiving from an empty channel while main keeps computing. What is true of the single P?
- It is idle, so the runtime reports a deadlock as soon as all ten goroutines are parked.
- It is unusable, so the runtime spawns ten extra threads, one per blocked goroutine.
- It is held by the first blocked goroutine until that goroutine is woken.
- It is free to run main; a goroutine blocked on a channel does not occupy a P.
показать ответ
ответ D. It is free to run main; a goroutine blocked on a channel does not occupy a P.
Блокировка на канале паркует горутину: её снимают с P и помещают в очередь ожидания канала, а P тут же берёт следующую готовую горутину - здесь это main. Именно поэтому один P обслуживает тысячи ждущих горутин. Припаркованная горутина ничего не удерживает, и поток под неё не создаётся: потоки создаются для горутин, заблокированных в системных или cgo-вызовах, а не для припаркованных на структурах рантайма. Детектор взаимоблокировок срабатывает, только когда спят все горутины, а main продолжает работать, так что сообщать не о чем.
официальная документация
из набора Планировщик GMP: G, M, P, очереди выполнения и кража работы
The runtime/metrics documentation says /sched/goroutines/running:goroutines is always less than or equal to /sched/gomaxprocs:threads. What does that bound express?
- Only one goroutine per P may exist, so the two numbers are effectively the same.
- The scheduler caps live goroutines at GOMAXPROCS and queues the rest until a slot frees.
- A goroutine must hold a P to execute, and GOMAXPROCS is the number of Ps.
- Goroutines blocked in system calls are counted as running, which is why the bound is not an equality.
показать ответ
ответ C. A goroutine must hold a P to execute, and GOMAXPROCS is the number of Ps.
Для выполнения Go-кода нужен P, а число P в точности равно GOMAXPROCS, поэтому количество горутин в состоянии running не может его превысить - в этом и состоит смысл ограничения. Число живых горутин не ограничено вовсе: их могут быть миллионы, и их считает соседняя метрика /sched/goroutines:goroutines. Не удерживает P и одна горутина на всё время жизни: P выполняет по одной горутине и постоянно переключается. Горутины, заблокированные в системном вызове, - это противоположность running, у них своя метрика /sched/goroutines/not-in-go:goroutines, так что их зачисление в running нарушило бы ограничение, а не объяснило его.
официальная документация
Производительность
1 набор · 18 вопросов
Тренировать: Производительность
из набора Escape-анализ: что убегает в кучу и почему
type I interface{ M() int } type S struct{ n int } func (s S) M() int { return s.n } func viaIface(i I) int { return i.M() } func call() int { var i I = S{5} return viaIface(i) } The analysis says leaking param: i for viaIface. What does it say about the S value in call?- moved to heap: i, because the interface variable holds a pointer to the value.
- Nothing, because escape analysis skips values whose type is an interface.
- S escapes to heap, because assigning to an interface variable always boxes.
- S does not escape, because the call is devirtualized and inlined away.
показать ответ
ответ D. S does not escape, because the call is devirtualized and inlined away.
С подробным флагом go1.26.4 печатает
inlining call to viaIface, затемdevirtualizing i.M to S, затемinlining call to S.Mи, наконец,S{...} does not escape. Анализ убегания выполняется ПОСЛЕ встраивания, поэтому к моменту, когда он доходит до call, интерфейсного вызова уже нет: компилятор доказал, что динамический тип - S, заменил динамическую диспетчеризацию прямым вызовом и встроил его. Строкаleaking param: iописывает viaIface, скомпилированную отдельно, для тех вызывающих, которых компилятор не видит насквозь, - на эту точку вызова она не распространяется. Так что «всегда упаковывает» - как раз то заблуждение, которое этот пример опровергает;moved to heapназвало бы переменную, адрес которой взят, а этого здесь не происходит; интерфейсы же анализируются наравне со всем остальным.официальная документация
из набора Escape-анализ: что убегает в кучу и почему
type Row struct { ID int Name string } var sink []Row func BenchmarkStructs(b *testing.B) { b.ReportAllocs() for b.Loop() { rows := make([]Row, 0, 100) for i := 0; i < 100; i++ { rows = append(rows, Row{ID: i}) } sink = rows } } The same benchmark written with a []*Row, &Row{ID: i} and a []*Row sink reports 101 allocs/op. What does the version shown report?- 101 allocs/op as well, since each Row is a separate object either way.
- 100 allocs/op, one per append, because the result slice is preallocated.
- 0 allocs/op, because make with a constant capacity is always stack allocated.
- 1 alloc/op, because the hundred Rows live inside one backing array.
показать ответ
ответ D. 1 alloc/op, because the hundred Rows live inside one backing array.
Измерено на go1.26.4: версия со значениями даёт 1 alloc/op, версия с указателями - 101 alloc/op, и оба числа воспроизводятся от запуска к запуску. Хранение Row ПО ЗНАЧЕНИЮ размещает все сто элементов внутри единственного подлежащего массива среза, поэтому выделяется ровно один объект - сам массив, который убегает, поскольку присваивается переменной уровня пакета. Хранение *Row выделяет массив плюс по объекту на элемент, отсюда 1 + 100. append не выделяет память на каждый вызов: ёмкость зарезервирована заранее, поэтому перевыделения нет ни в одной из версий. И постоянная ёмкость сама по себе не делает всё бесплатным, потому что срез убегает: тот же цикл с локальным результатом даёт 0 allocs/op. В этом и состоит весь аргумент в пользу раскладки «структуры по значению» на горячем пути: меньше объектов для выделения и меньше указателей, которые придётся обходить сборщику. Приёмник здесь намеренно типизирован: с приёмником типа any сохранение среза вдобавок упаковывало бы его трёхсловный заголовок, и две версии показали бы 2 и 102.
официальная документация
из набора Escape-анализ: что убегает в кучу и почему
func tidy() int { x := 1 defer func() { x = 2 }() return x } What does the compiler report about the deferred func literal and about x?- The literal escapes to heap, and x is moved to heap alongside it.
- The literal does not escape, and no line is printed for x at all.
- The literal does not escape, and x is still moved to heap because it is assigned.
- Nothing is printed: defer bodies are excluded from escape analysis entirely.
показать ответ
ответ B. The literal does not escape, and no line is printed for x at all.
go1.26.4 печатает
./d.go:3:8: func literal does not escapeи ничего не печатает про x. Open-coded defer выполняется до разрушения кадра, поэтому его замыкание может жить в этом кадре; компилятор размещает отложенный литерал в куче лишь тогда, когда defer стоит в цикле или по другой причине не может быть open-coded. Поскольку литерал остаётся на стеке, там же может остаться и переменная, в которую он пишет: присваивание через замыкание само по себе не повод переносить переменную - повод лишь присваивание через замыкание, которое ПЕРЕЖИВАЕТ кадр. Тела defer, разумеется, анализируются: именно оттуда и берётся строкаdoes not escape. (Функция при этом возвращает 1, так как возвращаемое значение копируется до выполнения отложенного вызова.)официальная документация
Тестирование
2 набора · 37 вопросов
из набора Основы testing.T: Error или Fatal, Helper, Cleanup, TempDir, Setenv
A test starts a helper goroutine that has to report a problem back to the test. Which testing.T method is documented as safe to call from that goroutine?
- t.SkipNow
- t.Errorf
- t.Parallel
- t.FailNow
показать ответ
ответ B. t.Errorf
Пакет testing делит свои методы надвое. FailNow, Fatal, Fatalf, SkipNow, Skip, Skipf и Parallel должны вызываться только из горутины, выполняющей тестовую функцию, потому что каждый из них завершает тест, останавливая именно эту горутину. Методы отчёта - варианты Log и Error - "могут вызываться одновременно из нескольких горутин", поэтому Errorf и есть правильный способ для вспомогательной горутины сообщить о проблеме; тест затем падает, когда завершится. Проверено на go1.26.4: t.Errorf из горутины записывается в лог, тестовая функция доходит до своего естественного конца, и тест отмечается как FAIL. Идиоматическая форма - вызвать Errorf (или отправить проблему обратно по каналу) и вернуться из горутины.
официальная документация
из набора Табличные тесты и подтесты с t.Run
A package has one test function, TestParse, with four subtests. A developer runs `go test -run 'TestParse/unicode' -v`. What does go test -v report?
- TestParse itself and the matching subtest, because a parent has to run for its subtests to be reached.
- Only the subtest whose name matches; the parent is not shown in the output because it was not itself selected.
- All four subtests, because the second element of the pattern only orders them.
- Nothing, because -run matches whole test functions and a subtest is not one.
показать ответ
ответ A. TestParse itself and the matching subtest, because a parent has to run for its subtests to be reached.
Аргумент -run разбивается по неэкранированным слэшам на последовательность регулярных выражений, и каждый элемент должен совпасть с соответствующей частью идентификатора теста, поэтому первый элемент выбирает родителя, а второй фильтрует его подтесты. Документация добавляет, что "возможные родители совпадений тоже запускаются", и это необходимость, а не любезность: подтест существует лишь потому, что тело родителя вызывает t.Run, значит родитель обязан выполниться, чтобы подтест был обнаружен. Проверено на go1.26.4: в подробном выводе виден запуск родителя и только совпавший подтест под ним, а родитель получает собственную строку PASS или FAIL. Несовпавшие подтесты отфильтровываются, а подтесты вполне адресуемы - для этого и существует синтаксис со слэшем.
официальная документация
из набора Основы testing.T: Error или Fatal, Helper, Cleanup, TempDir, Setenv
A test calls t.TempDir(), stores the path in a package-level variable and writes a file there. When is that directory removed in Go 1.26?
- When the whole test binary exits, so that a later test in the same package can still read the files left in it.
- When the test and all its subtests complete, so a cleanup registered after the TempDir call still sees it.
- Only if the test passes; a failing test's directory is kept behind for inspection.
- As soon as the test function returns, before any of the test's subtests are resumed.
показать ответ
ответ B. When the test and all its subtests complete, so a cleanup registered after the TempDir call still sees it.
Каталог "автоматически удаляется, когда завершаются тест и все его подтесты" - в тот же момент, когда срабатывают функции Cleanup, - а поскольку само удаление в TempDir регистрируется как уборщик в момент вызова TempDir, любой уборщик, зарегистрированный после этого вызова, выполняется раньше и ещё видит каталог. Проверено на go1.26.4 сохранением пути: подтест каталог видел, собственный уборщик родителя видел, а более поздний тест того же пакета уже не нашёл. Значит, он не доживает до конца бинарника и не исчезает в момент возврата из функции - что важно для параллельных подтестов, продолжающих работу после завершения тела родителя. Результат теста при этом вообще не учитывается: сохранение каталога упавшего теста - удобство, которого стандартная библиотека не предлагает, и тесту, которому это нужно, придётся создать свой каталог самому.
официальная документация
Инструменты
1 набор · 18 вопросов
из набора Модули: директивы go.mod и что означает каждая
What does the `// indirect` comment on a require line in go.mod mean?
- No package or test in the main module imports that module directly; it is needed to build something else.
- The module is needed only by the main module's own tests, never by any of its non-test code.
- The requirement is informational: minimal version selection skips indirect lines when it computes the build list.
- The go command wrote the line itself and will drop it again on the next build.
показать ответ
ответ A. No package or test in the main module imports that module directly; it is needed to build something else.
// indirect- это пометка, которую поддерживает команда go: она означает, что ни один пакет или тест главного модуля не импортирует пакет из этого модуля напрямую. Версия при этом остаётся настоящим требованием и полностью участвует в minimal version selection: комментарий ничего не меняет в выборе, он лишь фиксирует, почему строка здесь. Тестовые зависимости собственных пакетов главного модуля считаются прямыми, так что пометка не про тесты. И строка не является временной: go mod tidy сохраняет косвенное требование, пока оно нужно какой-либо зависимости, и удаляет его лишь тогда, когда модуль больше не нужен сборке.официальная документация
из набора Модули: директивы go.mod и что означает каждая
Go 1.25 added the ignore directive. A module's go.mod contains `ignore ./assets`, and ./assets holds a buildable Go package. What does the directive do?
- It keeps that package out of the module zip that gets published to the proxy.
- It makes the package unimportable: an explicit import of it no longer resolves.
- The directory is skipped when patterns such as ./... expand, but an explicit import of a package there still builds.
- go build skips the directory, but go test and go vet still walk into it and build its packages when given the ./... pattern.
показать ответ
ответ C. The directory is skipped when patterns such as ./... expand, but an explicit import of a package there still builds.
ignore говорит команде go не заходить в каталог при раскрытии шаблонов, и именно это делает директиву полезной для деревьев данных, фикстур или скопированного в репозиторий (vendored) материала не на Go, который иначе замедлял бы каждый ./... или не загружался бы вовсе. Проверено на go1.26.4: без директивы
go list ./...печатает и корневой пакет, и пакет в ./assets; сignore ./assets— только корневой, при этом main-пакет, прямо импортирующий example.com/ign/assets, по-прежнему компилируется и работает. То есть фильтруется раскрытие шаблонов, а не импортируемость. Это и не фильтр упаковки: zip модуля строится из содержимого репозитория, и ignore на него не влияет. Действует директива одинаково на все команды, раскрывающие шаблоны, включая go test.официальная документация
из набора Модули: директивы go.mod и что означает каждая
In Go 1.26 a module's go.mod carries the line `go 1.21.0`, the installed toolchain is go1.26.4, and one source file loops with `for i := range 3`. What does go build do?
- It builds, and go vet separately reports the construct through its stdversion analyzer.
- It fails to compile, because ranging over an integer needs go1.22 and the go line fixes the language version for the module's files.
- It builds: the go line only affects module resolution, and the installed compiler is 1.26.
- It fails, because the go command refuses to build any module whose go line is older than the running toolchain until go.mod is raised to match.
показать ответ
ответ B. It fails to compile, because ranging over an integer needs go1.22 and the go line fixes the language version for the module's files.
Директива go задаёт версию языка модуля, а не только настройку модульной системы: компилятор запускается с -lang, взятым из неё, поэтому файл в этом модуле может использовать только возможности вплоть до Go 1.21. go1.26.4 сообщает
cannot range over 3 (untyped int constant): requires go1.22 or later (-lang was set to go1.21; check go.mod)и завершается с кодом 1. Наличие установленного тулчейна 1.26 ничего не меняет - в этом и смысл строки go: модуль можно собирать более новыми тулчейнами, не получая молча новую семантику языка. Анализатор stdversion в vet сообщает о слишком новых символах *стандартной библиотеки* для действующей версии языка, и не он останавливает эту сборку, а компилятор. Более старая строка go - это нормальная ситуация, а не ошибка: новый тулчейн спокойно собирает старые модули.официальная документация
Идиомы
3 набора · 36 вопросов · 20 карточек
из набора Именование: пакеты, переменные, получатели, интерфейсы и геттеры
A type keeps its state in an unexported field named owner and needs an exported accessor for it plus a way to change it. Following Effective Go's Getters section, what should the two methods be called?
- Owner and WithOwner
- GetOwner and PutOwner
- Owner and SetOwner
- GetOwner and SetOwner
показать ответ
ответ C. Owner and SetOwner
Effective Go говорит об этом прямо: "If you have a field called owner (lower case, unexported), the getter method should be called Owner (upper case, exported), not GetOwner. The use of upper-case names for export provides the hook to discriminate the field from the method. A setter function, if needed, will likely be called SetOwner." То есть геттер теряет приставку Get, а сеттер сохраняет Set. Вариант GetOwner и SetOwner - это распространённое заблуждение, перенесённое из Java-подобных bean-классов: приставка Get здесь как раз то, что Effective Go запрещает, хотя Set указан верно. Вариант GetOwner и PutOwner неверен дважды: Put в Go не является словом для сеттера. Вариант Owner и WithOwner называет геттер правильно, но WithX - это соглашение для функциональных опций, то есть для построения значения, а не для его изменения; Effective Go называет сеттер SetOwner.
официальная документация
из набора Пословицы Go — что на самом деле значит каждая
Which of these lines is NOT on the list of Go proverbs at go-proverbs.github.io, as fetched on 2026-09-25?
- Errors are values.
- A little copying is better than a little dependency.
- Accept interfaces, return structs.
- Clear is better than clever.
показать ответ
ответ C. Accept interfaces, return structs.
Страница, загруженная 2026-09-25, перечисляет 19 пословиц "from @robpike.io's inspiring talk at Gopherfest SV 2015" и добавляет, что "the list may be updated when he next gives the talk". Clear is better than clever, Errors are values и A little copying is better than a little dependency в списке есть. Accept interfaces, return structs - максима сообщества, а не одна из пословиц; ближайшая официальная рекомендация - раздел Interfaces в Code Review Comments: "The implementing package should return concrete (usually pointer or struct) types".
официальная документация
из набора Именование: пакеты, переменные, получатели, интерфейсы и геттеры
A package in Go 1.26 is declared as package my_pkg and exports a constant named MAX_LENGTH. What do go build, go vet and gofmt each do with that code?
- go build rejects the package clause, because a package name must be a single lower-case word
- All three accept it: both names break house convention, and neither is a toolchain error
- go build accepts it, but go vet reports both names as convention violations
- gofmt rewrites MAX_LENGTH to MaxLength and leaves the package name alone
показать ответ
ответ B. All three accept it: both names break house convention, and neither is a toolchain error
Проверено на go1.26.4: пакет собирается, go vet завершается с кодом 0 без замечаний, gofmt -l не печатает ничего. В предложении package допустим любой идентификатор, а идентификатор может содержать подчёркивания и заглавные буквы, поэтому ни одно из имён не является некорректным. Соглашения при этом реальны: Effective Go говорит, что пакетам дают "lower case, single-word names; there should be no need for underscores or mixedCaps", а Code Review Comments говорит, что неэкспортируемая константа - это maxLength, "not MaxLength or MAX_LENGTH", - но это соглашения, которые поддерживаются код-ревью и сторонними линтерами, а не самим дистрибутивом Go. Ожидание, что их поймает vet, и есть распространённое заблуждение: vet проверяет ловушки корректности, а не стиль, и в комплекте Go нет линтера стиля. gofmt меняет раскладку кода и никогда ничего не переименовывает.
официальная документация
как это работает
В каждой части — несколько вопросов, выбранных в постоянном порядке из наборов, для которых не нужен аккаунт, поэтому страница читается одинаково при каждом визите. Посмотрите ответ — там верный вариант и объяснение.
Тренировка части запускает тест по наборам, целиком посвящённым этой части (до 12 наборов); тренировка всего экзамена берёт до 12 случайных наборов, в том числе тех, где части смешаны, и при каждом запуске выбирает их заново. В обоих случаях — только наборы, для которых не нужен аккаунт. Каждый ответ проверяется сразу, а ошибки возвращаются. Бесплатный аккаунт открывает остальные наборы и позволяет пройти пробный экзамен.
частые вопросы
Go 1.26: откуда эти вопросы?
30 примеров вопросов на этой странице и 398 вопросов, из которых они взяты, написаны для wspell. Ни одна сторона не проводит за ними экзамен, а wspell независим от любых организаторов экзаменов.
Go 1.26: нужен ли аккаунт для практики?
Чтобы начать — нет. Тест на странице «Go 1.26» работает без аккаунта — 30 из 384 наборов. Ссылка тренировки начинает курс за вас тем же нажатием. Бесплатный аккаунт сохраняет прогресс, открывает остальные наборы и позволяет пройти пробный экзамен.
Названия экзаменов и товарные знаки на этой странице принадлежат организациям, которые владеют этими экзаменами и проводят их. wspell — независимая платформа, не связанная ни с одной из этих организаций и не одобренная ими.