Map[string]interface{} in Go: a user's manual
bitfieldconsulting.com
bitfieldconsulting.com
An empty interface should be effectively useless. What could it possibly do!? This pattern is much more like a dynamic container with a type tag.
https://go.googlesource.com/proposal/+/master/design/15292/2...
Generics do not obviate the need for interface{} or the described use case of map[string]interface{}
interface{} is like Java or C# object. It packages a type and a value in a single value that can be queried at runtime to extract the underlying value in a type safe matter.
When people use map[string]interface{] it's because they don't know the type of value at compile time.
Go maps are actually not generic in the same way they are in other languages, there is just a single map struct with type descriptors used to do some compiler magic: https://dave.cheney.net/2018/05/29/how-the-go-runtime-implem...
For generics what you want is not interface{}, which says nothing, but a type T that has meaningful constraints.
You mean runtime generic container without the benefits of compile-time type checking, opening the developer to runtime type errors purely for ideological reasons...
It's really hard to take people who complain about Go seriously when they're pretty obviously just zealots who don't know much about the language besides "types good" and "Go bad". My stance: types good in moderation; Go a useful tradeoff of engineering attributes.
No it isn't, Go has other flaws stemming from the exact same root cause and it has nothing to do with programming...
There are no ideological reasons.
https://github.com/golang/go/commit/dd64f86e0874804d0ec5b713... "Generics may well come at some point."
https://golang.org/doc/faq#generics "A language proposal implementing a form of generic types has been accepted for inclusion in the language. If all goes well it will be available in the Go 1.18 release."
One of the language designers explaining the motivation for adding parametric polymorphism to Go: https://www.youtube.com/watch?v=RIvL2ONhFBI&t=1064s
Explaining the benefits of generics: https://www.reddit.com/r/golang/comments/jditu9/what_do_gene...
From the Featherweight Go paper: https://arxiv.org/pdf/2005.11710.pdf "Recently, the Go team mooted a design to extend Go with generics, and Rob Pike wrote Wadler to ask: Would you be interested in helping us get polymorphism right (and/or figuring out what “right” means) for some future version of Go?"
> The generic dilemma is this: do you want slow programmers, slow compilers and bloated binaries, or slow execution times?
> I would be happy to learn about implementations that somehow manage to avoid all three of these bad outcomes, especially if there were good written descriptions (papers, blog posts, etc.) about what was hard and why it's a good approach. I'd also be interested to see good written descriptions of trying one of those approaches and what went wrong (or right).
- Russ Cox (2009): https://research.swtch.com/generic
Which one did Rust choose?
To be fair, there are also language-implementor-laziness reasons.
Interfaces in go are simply saying "this thing satifies these properties". interface{} is an interface that anything can satisfy.
func doSomeRequest(...) {
type loose map[string]interface{}
payload, err := json.Marshal(loose{
"fooBar": true,
"nested": loose{
"subfield": bazArg,
},
})
// ...
}
I should really probably write some blog listing various tricks like this I'm using, but I still can't make myself do it...Also, if you're making a set, use `map[string]struct{}` instead of `map[string]interface{}` as `struct{}` takes 0 bytes whereas `interface{}` takes 8.
if the serialized structure doesn't indicate its "Type" somewhere/somehow then you would have to resort to map[string]interface{}
type Foo interface {
Foo() int
}
`interface{}` is simply a shortform for an interface with no methods. // Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
https://github.com/openjdk/jdk/blob/master/src/java.base/sha...A thread-safe implementation is available with `ConcurrentHashMap.newKeySet()`, which uses a similar approach:
public static <K> KeySetView<K,Boolean> newKeySet() {
return new KeySetView<K,Boolean>
(new ConcurrentHashMap<K,Boolean>(), Boolean.TRUE);
}
https://github.com/openjdk/jdk/blob/master/src/java.base/sha...But without generics it must be implemented again for each entry type ...
Kubernetes uses a code generator to automate this, but it's a bit kludge. [3]
[1]: https://pkg.go.dev/k8s.io/apimachinery/pkg/util/sets#Empty
[2]: https://dave.cheney.net/2014/03/25/the-empty-struct
[3]: https://github.com/kubernetes/code-generator/blob/master/cmd...
However sets tend to provide support for… set operations. Which currently can't be done generically in go except through interface{} or specialised, neither of which is a great option.
What “type safety” could you possibly hope for from a system you don’t control?
I’m not even sure where to start from that. Responses provided to an HTTP request are one of the most obvious places where enforcing structure and type safety is clearly a win.
You receive some bytes and you do stuff with them. Any other expectation is a farce.
Is a property missing after serialization? Can you even serialize it? Maybe a different status code returns a different mime type! OK, whatever. That’s not always an error, nor is it remotely “exceptional.”
try:
val = input['foo']['bar']
except KeyError, TypeError:
raise HttpErrors.BadRequestif 'bar' in input['foo'].keys(): val = input['foo']['bar']
Generally, exceptions cause code to run slower and I feel like in any other language is considered bad practices.
I never understood why Python promotes EAFP vs LBYL. Both performance and accuracy wise, checking before doing is safer and faster performance.
Python exceptions are only slower on the exception. They are almost exactly the same speed on success.
LBYL forces you to look for all the ways in which you might leap. EAFP lets you handle even unexpected conditions. The classic use case is checking if a condition exists, then doing the thing, except the condition changed between check and run. So you gotta catch anyways.
I've been moving away from this actually and towards a rust-like Result[T, Exception]. I'm using the package called "result". For fastapi api_request -> handler -> some_func_chain -> return response_to_client patterns, if something barfs, you normally get a 422 and useless "Internal server error". It's far simpler imho to map over some Results and then pack the error(s) into a more useful response.
Also testing is just a lot simpler.
Also, in Go, using panic/recover is considered unidiomatic. You can use it like an exception system, but it's not what you're supposed to do.
The problem in Go isn't with interface{} but that the simple type system can't handle common use cases so you end up needing it use interface{} much more often that you would need to in other languages.
Basically interface{} is useful when you truly need it. But it is often used to workaround deficiencies in the type system.