Ответ
context.WithValue
предназначен для передачи данных, относящихся к конкретному запросу (request-scoped data), по цепочке вызовов. Это идеальный способ пробросить сквозную информацию, такую как ID запроса, данные трассировки, или информацию о пользователе, без необходимости явно передавать их в каждом вызове функции.
Основной принцип:
context.WithValue
возвращает копию родительского контекста с прикрепленной к нему парой ключ-значение.
Пример с использованием лучшей практики (ключ собственного типа):
// Для избежания коллизий ключей, создаем собственный тип для ключа.
// Пустая структура не занимает памяти.
type key int
const userIDKey key = 0
func main() {
// Сохраняем userID в контексте
ctx := context.WithValue(context.Background(), userIDKey, 12345)
// ... где-то глубоко в цепочке вызовов
processRequest(ctx)
}
func processRequest(ctx context.Context) {
// Извлекаем значение. Важно безопасно приводить тип.
userID, ok := ctx.Value(userIDKey).(int)
if !ok {
log.Println("userID not found in context")
return
}
fmt.Printf("Processing request for user ID: %dn", userID)
}
Нюансы и лучшие практики:
- Используйте кастомный тип для ключей. Никогда не используйте встроенные типы (например,
string
) в качестве ключей. Это предотвращает случайные коллизии имен ключей между разными пакетами. - Не используйте контекст для передачи обязательных параметров. Если функция для своей работы требует какой-то параметр, он должен быть явным аргументом функции. Контекст предназначен для опциональных, сквозных данных.
- Значения в контексте статичны. Контекст иммутабелен. Если вы хотите изменить значение, вы создаете новый дочерний контекст с помощью
context.WithValue
. - Проверяйте тип значения. Метод
Value()
возвращаетinterface{}
. Всегда используйте проверку типа (value, ok := ctx.Value(key).(Type)
) для безопасного извлечения данных. - Помните о производительности. Каждый вызов
WithValue
приводит к аллокации памяти. В высоконагруженных системах не стоит злоупотреблять им в циклах.