Для чего используется `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 приводит к аллокации памяти. В высоконагруженных системах не стоит злоупотреблять им в циклах.

Ответ 18+ 🔞

О, смотри-ка, какая тема подъехала — context.WithValue! Это ж, блядь, как тайная записка, которую ты передаёшь по цепочке вызовов, чтобы не орать на всю квартиру про какой-нибудь ID запроса или юзера. Удобная штука, если не превращать её в помойку, куда скидывают всё подряд.

Суть простая, как три копейки: Берёшь родительский контекст и делаешь его копию, но с прицепом — парой «ключ-значение». Всё, теперь эту штуку можно тащить вглубь вызовов, и любой, у кого есть ключ, сможет достать значение. Красота, да?

Вот как это выглядит, когда делают по уму (с кастомным ключом, а не как попало):

// Чтобы ключи не пересекались случайно с другими библиотеками, 
// объявляем свой собственный тип. Пустая структура — идеально, памяти ноль.
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 не найден в контексте, ёпта")
        return
    }
    fmt.Printf("Обрабатываем запрос для юзера с ID: %dn", userID)
}

А теперь, блядь, нюансы, без которых можно влететь:

  1. Ключи — только свои типы. Никаких string или int в лоб! Иначе какой-нибудь левый пакет может случайно перетереть твои данные, и будет пиздец. Создавай свой тип, хоть type myKey string, и спи спокойно.
  2. Не пихай в контекст обязательные параметры. Если функция без какого-то значения — просто труп, передавай его явно, аргументом. Контекст — для опциональной, сквозной хуйни, вроде трейс-ид или локали.
  3. Контекст — неизменяемая хуйня. Раз положил — не перепишешь. Хочешь новое значение? Создавай новый контекст через WithValue. Старый останется как был.
  4. Всегда проверяй тип при извлечении. Value() возвращает interface{}, а это, блядь, как чёрный ящик. Используй проверку с двумя возвращаемыми значениями, иначе в рантайме можешь получить сюрприз.
  5. Не злоупотребляй в циклах. Каждый вызов WithValue — аллокация. Если ты в горячем цикле на каждый чих создаёшь новый контекст с парой ключей, потом будешь удивляться, куда память делась.