Для чего используется `context.WithValue`? Расскажите о нюансах и лучших практиках его применения.

Ответ

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)
}

Нюансы и лучшие практики:

  1. Используйте кастомный тип для ключей. Никогда не используйте встроенные типы (например, string) в качестве ключей. Это предотвращает случайные коллизии имен ключей между разными пакетами.
  2. Не используйте контекст для передачи обязательных параметров. Если функция для своей работы требует какой-то параметр, он должен быть явным аргументом функции. Контекст предназначен для опциональных, сквозных данных.
  3. Значения в контексте статичны. Контекст иммутабелен. Если вы хотите изменить значение, вы создаете новый дочерний контекст с помощью context.WithValue.
  4. Проверяйте тип значения. Метод Value() возвращает interface{}. Всегда используйте проверку типа (value, ok := ctx.Value(key).(Type)) для безопасного извлечения данных.
  5. Помните о производительности. Каждый вызов WithValue приводит к аллокации памяти. В высоконагруженных системах не стоит злоупотреблять им в циклах.