Ответ
context.WithValue
используется для передачи данных, связанных с конкретным запросом (request-scoped data), вниз по стеку вызовов, не передавая их явно в качестве аргументов каждой функции.
Основная идея — пробросить сквозные метаданные, такие как ID запроса, данные аутентификации пользователя или информацию для трассировки.
Правила хорошего тона (Best Practices):
Используйте только для сквозных данных.
context
предназначен для данных, которые могут понадобиться в любой точке обработки запроса, а не для передачи обязательных параметров функции.- Хорошо:
context.WithValue(ctx, "request_id", "xyz-123")
- Плохо:
context.WithValue(ctx, "user_id", 123)
(еслиuser_id
— это основной параметр, с которым работает функция, его нужно передавать явно).
- Хорошо:
Используйте кастомные типы для ключей. Чтобы избежать конфликтов ключей между разными пакетами, никогда не используйте встроенные типы (например,
string
). Вместо этого объявляйте свой неэкспортируемый тип.Значения в контексте должны быть неизменяемыми (immutable), чтобы избежать гонок данных.
Пример правильного использования:
package main
import (
"context"
"fmt"
)
// 1. Объявляем кастомный тип для ключа (неэкспортируемый)
type key int
// 2. Объявляем переменную-ключ
const requestIDKey key = 0
func main() {
// Создаем базовый контекст
ctx := context.Background()
// 3. Добавляем значение в контекст с помощью нашего ключа
ctx = context.WithValue(ctx, requestIDKey, "abc-123-xyz")
// Вызываем функцию, которая использует контекст
processRequest(ctx)
}
func processRequest(ctx context.Context) {
// 4. Извлекаем значение, безопасно проверяя тип
if requestID, ok := ctx.Value(requestIDKey).(string); ok {
fmt.Println("Processing request with ID:", requestID) // "Processing request with ID: abc-123-xyz"
} else {
fmt.Println("Request ID not found")
}
}
Таким образом, context.WithValue
— это мощный инструмент для передачи метаданных, но его следует использовать с осторожностью, чтобы не сделать код запутанным и неявным.