We’re looking at different types for context keys.
So far, we’ve looked at empty structs (struct{})
, and found it to be less than ideal.
Today, let’s consider integer context keys.
This seems handy, right?
type contextKey int
const (
KeyUserID int = iota
KeyTransactionID
KeyMessageID
.
.
.
KeyFoo
)
Let’s see how it stacks up to our 5 criteria:
-
Must be comparable.
✅ No problem!
-
Use minimal membory.
✅
int
doesn’t have as small a memory footprint asstruct{}
’s zero bytes, but it’s still pretty small. One could potentially useint8
if this is really important, though it’s likely not worth it. -
Should be readable.
Here’s where numeric keys can suffer. While this convention is fairly readable in code, it can make for a pain when debugging.
fmt.Printf("failed to read context key %s", KeyMessageID) // prints: failed to read context key 3
You could somewhat mitigate this problem by adding a
String()
method on the custom type:func (k contextKey) String() string { switch k { case KeyUserID: return "KeyUserID" /* ... */ } }
Meh. That improves the situation, but at what cost? And it only works for places where a
String()
method is even considered. Not perfect, by any means. -
Easy to declare multiple keys per package.
✅ Super easy!
-
Nice if it works with
const
✅ Definitely!
So in conclusion… using an int
(or other numeric type) for your context keys can work, functionally, but it suffers for debugability. Not the biggest drawback in the world, but if we have another alternative that checks all the boxes, that’s probably better… so let’s keep looking.