Что можно класть в context.Value, а что нельзя

Вопрос звучит невинно: context.WithValue — удобный способ протащить что угодно через вызовы. На практике ответ «что угодно» — это провал на собесе уровня Senior.

Value в контексте предназначен для метаданных запроса, которые пересекают границы API и идут сквозь процессы насквозь: trace-id, span, request-id, данные аутентификации, locale. Это то, что нужно логированию, трейсингу и middleware, но не является частью бизнес-логики функции.

Если через контекст передают репозиторий, конфиг или флаг фичи — это сигнал, что аргумент прячут вместо того, чтобы объявить его явно в сигнатуре. Явный параметр виден в автодополнении, типизирован и не ломается молча при рефакторинге. context.Value — нет: ключ обычно построен на интерфейсе{} до 1.18 или на собственном типе-строке, и компилятор не проверит, что значение вообще туда положили.

type ctxKey string

const requestIDKey ctxKey = "request_id"

func WithRequestID(ctx context.Context, id string) context.Context { return context.WithValue(ctx, requestIDKey, id) }

func RequestID(ctx context.Context) (string, bool) { id, ok := ctx.Value(requestIDKey).(string) return id, ok }

Следом спрашивают: почему ключ — не строка, а отдельный тип. Если два пакета используют одну и ту же строку в качестве ключа, они молча перезатирают значения друг друга — коллизия ключей в одной мапе контекста. Собственный тип ctxKey делает ключи из разных пакетов несравнимыми даже при одинаковом текстовом значении, и коллизия становится невозможной на уровне типов.

Context.Value — не замена DI и не способ передать обязательный параметр. Если без значения функция не может корректно отработать — оно должно быть явным аргументом, а не спрятанным в контексте.

Тренажёр: 1900+ вопросов и задач, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки