Hacker News new | ask | show | jobs
by sluongng 513 days ago
Let me try to take the other side:

`ctx.Value` is an `any -> any` kv store that does not come with any documentation, type checking for which key and value should be available. It's quick and dirty, but in a large code base, it can be quite tricky to check if you are passing too many values down the chain, or too little, and handle the failure cases.

What if you just use a custom struct with all the fields you may need to be defined inside? Then at least all the field types are properly defined and documented. You can also use multiple custom "context" structs in different call paths, or even compose them if there are overlapping fields.

4 comments

Because you should wrapp that in a type safe function. You should not use the context.GetValue() directly but use your own function, the context is just a transport mechanism.
If it is just a transport mechanism, why use context at all ant not a typed struct?
Because dozens of in between layers don't need to know the type, and should in fact work regardless of the specific type.

Context tells you enough: someone, somewhere may do magic with this if you pass it down the chain.

And in good Go tradition it's explicit about this: functions that don't take a context don't (generally) do that kind of magic.

If anything it mixes two concerns: cancelation and dynamic scoping.

But I'm not sure having two different parameters would be better.

> `ctx.Value` is an `any -> any` kv store that does not come with any documentation, type checking for which key and value should be available

The docs https://pkg.go.dev/context#Context suggest a way to make it type-safe (use an unexported key type and provide getter/setter). Seems fine to me.

> What if you just use a custom struct with all the fields you may need to be defined inside?

Can't seamlessly cross module boundaries.

> `ctx.Value` is an `any -> any` kv store that does not come with any documentation, type checking for which key and value should be available.

On a similar note, this is also why I highly dislike struct tags. They're string magic that should be used sparingly, yet we've integrated them into data parsing, validation, type definitions and who knows what else just to avoid a bit of verbosity.

Most popular languages support annotations of one type or another, they let you do all that in a type safe way. It's Go that's decided to be different for difference sake, and produced a complete mess.
IMO Go is full of stuff like this where they do something different than most similar languages for questionable gains. `iota` instead of enums, implicit interfaces, full strings in imports (not talking about URLS here but them having string literal syntax), capitalization as visibility control come to mind immediately, and I'm sure there are others I'm forgetting. Not all of these are actively harmful, but for a language that touts "simplicity" as one of its core values, I've always found it odd how many different wheels Go felt the need to reinvent without any obvious benefit over the existing ones.
the second i tried writing go to solve a non-trivial problem the whole language collapsed in on itself. footguns upon footguns hand-waved away with "it's the go way!". i just don't understand. the "the go way" feels more like a mantra that discourages critical thinking about programming language design.
> `ctx.Value` is an `any -> any`

It did not have to be this way, this is a shortcoming of Go itself. Generic interfaces makes things a bit better, but Go designers chose that dumb typing at first place. The std lib is full of interface {} use iteself.

context itself is an after thought, because people were building thread unsafe leaky code on top of http request with no good way to easily scope variables that would scale concurrently.

I remember the web session lib for instance back then, a hack.

ctx.Value is made for each go routine scoped data, that's the whole point.

If it is an antipattern well, it is an antipattern designed by go designers themselves.