Глобально уникальный ключ для значения контекста в Golang
Предположим, у вас есть контекст, который передается через множество пакетов. Любой из этих пакетов может помещать туда информацию (например, что-то, связанное с обрабатываемым запросом). Вы хотите убедиться, что ключи не конфликтуют. Если пакет A помещает что-то под ключ K, тогда пакет B не может перезаписать значение под этим ключом.
Соберем несколько фактов:
- Значения контекста хранятся внутри как пустые значения интерфейса (
interface{}
) (исходный код) - Оператор равенства используется для проверки, сохраняет ли контекст значение для желаемого ключа (исходный код).
- Спецификация языка определяет, когда два значения интерфейса равны:
Два значения интерфейса равны, если они имеют одинаковые динамические типы и одинаковые динамические значения или оба имеют значение
nil
.
Исходя из вышеизложенного, если мы поместим ключ динамического типа, специфичный для пакета, устанавливающего этот ключ, все будет в порядке. Таким образом, динамический тип не будет идентичен динамическому типу, установленному любым другим пакетом. Уравнение из метода Value всегда будет ложным. Для этого мы просто будем использовать тип, который не экспортируется из используемого пакета. У нас есть два пакета - a и b:
package main import ( "context" "fmt" "github.com/mlowicki/b" ) func main() { ctx, _ := context.WithCancel(context.Background()) ctx = b.Set(ctx) fmt.Println(b.Get(ctx)) fmt.Println(ctx.Value(1)) } package b import ( "context" ) type key int var id = key(1) func Set(ctx context.Context) context.Context { return context.WithValue(ctx, id, "secret") } func Get(ctx context.Context) (string, bool) { val, ok := ctx.Value(id).(string) return val, ok }
выход:
secret true <nil>
Значение, хранящееся в key(1)
, безопасно, поскольку только код в пакете b может использовать тип b.key.
Тот же механизм не будет работать с картами, поскольку можно перебирать их элементы и извлекать ключи и значения.
Редактировать
Обсуждение на Reddit принесло несколько идей:
const
можно использовать вместоvar
, чтобы запретить изменение значения ключаtype key struct{}
можно использовать, и приведенные выше правила по-прежнему остаются в силе. Тесты с testing.AllocsPerRun показывают такое же количество выделений в Go 1.9, что и при использованииint
. Вероятно, этот подход более эффективен с точки зрения памяти в некоторых старых версиях Go.
Кредиты для TheMerovius и epiris.
Хлопайте 👏, чтобы помочь другим узнать эту историю. Пожалуйста, подпишитесь на меня, если вы хотите получать обновления о новых сообщениях или ускорять работу над будущими историями.