Что можно класть в 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 — что спрашивают на самом деле
В этом посте были ссылки, но мы их удалили по правилам Сетки