Ответ
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приводит к аллокации памяти. В высоконагруженных системах не стоит злоупотреблять им в циклах.
Ответ 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)
}
А теперь, блядь, нюансы, без которых можно влететь:
- Ключи — только свои типы. Никаких
stringилиintв лоб! Иначе какой-нибудь левый пакет может случайно перетереть твои данные, и будет пиздец. Создавай свой тип, хотьtype myKey string, и спи спокойно. - Не пихай в контекст обязательные параметры. Если функция без какого-то значения — просто труп, передавай его явно, аргументом. Контекст — для опциональной, сквозной хуйни, вроде трейс-ид или локали.
- Контекст — неизменяемая хуйня. Раз положил — не перепишешь. Хочешь новое значение? Создавай новый контекст через
WithValue. Старый останется как был. - Всегда проверяй тип при извлечении.
Value()возвращаетinterface{}, а это, блядь, как чёрный ящик. Используй проверку с двумя возвращаемыми значениями, иначе в рантайме можешь получить сюрприз. - Не злоупотребляй в циклах. Каждый вызов
WithValue— аллокация. Если ты в горячем цикле на каждый чих создаёшь новый контекст с парой ключей, потом будешь удивляться, куда память делась.