Hash tables in Go and advantage of self-hosted compilers
23 points by runxiyu
23 points by runxiyu
Using empty structs also hurts readability.
Does it? Sure, struct{} is a little longer than bool and has more symbols in it, but it conveys the fact that there is no information stored here apart from the key being present, whereas with bool you have to rely on a comment, or domain knowledge, to infer that the bools do not actually mean anything. In other words, map[K]struct{} cannot be anything else than set of K, whereas map[K]bool can be: the struct{} version is self-documenting. How is that less readable?
Without disagreeing with you on the technical aspects, I personally find Go’s interface{} and struct{} idioms difficult to parse and unintuitive - it gives me the ick.
I think the reason for this is that they expose implementation rather than intent. The change from interface{} to “any” was a godsend for me, and if struct{} became (say) “none” then I would absolutely use it.
I think one reason for this is that, in both cases, it’s not clear to me if these choices are intentional, or just placeholders. Do we think there will be members in the struct{} one day? Or is it intentionally left blank? So, although it is idiomatic and perhaps I’m being irrational, nevertheless it niggles at me every time.
I know not everyone thinks the way I do about this, but I prefer my code to be explicit about intention, and struct{} feels both arbitrary and ugly to me.
One can always define such oneself, just as some previously did for 'any'.
type none = struct{}
What do you think of the fact that many languages use () for this? struct {} is a struct with zero fields, while () is a tuple with zero elements.
Well, the equivalent of the unit value () in Rust is struct{}{} in Go, and since we’re talking about readability: I rest my case, your honour.
In Rust, () is both the unit value and the unit type: in type position it means "the type of the empty tuple", in an expression it means "the value of the empty tuple". Go distinguishes these two: struct{} always means "the type of the empty struct", struct{}{} always means "the value of the empty struct" (and follows the universal syntax of "type + braces = value"). It's not obvious to me why this is worse. I think both are fine.
Do we think there will be members in the struct{} one day? Or is it intentionally left blank?
This is a good point: while struct{} is more precise than bool in the sense of "what does the type mean now", it is not precise in how it is intended to evolve as the software around it changes. A nominally distinct unit type would carry more information here.
However, can we not say the same about bool? Surely the fact that an additional, unused value is available means that that value is intended to be used at some point? With... some meaning? Is the bool intended to be a flag, or is it just a 1-bit integer that can be extended to a larger int when necessary?
I think we can say that while (absent comments, which can get out of date) struct{} and bool both have ambiguity in their intended direction of extension, bool is actually even more ambiguous than struct{} is, because even its current meaning is already ambiguous. So I hold my point that I don't get why struct{} is less readable than bool.
When I use a map with a bool value as a set, I name it accordingly.
For example, it wouldn’t be users but userExists. Then if userExists[user] reads well.
I’m going to wrap up my contribution by simply pointing out that readability is subjective. Perhaps a better way to describe how I feel about this is that I find interface{}, struct{} and of course struct{}{} aesthetically unsatisfying.
To be clear, I agree with you in general about the use of bools. It’s probably good practice to use struct{}. But next time this arises I might declare a type alias as someone else suggested. Just to stop it hurting my eyes.
So would this be improved by simply swapping the order of the fields in the struct? Yielding:
type slot struct {
elem typ.Elem
key typ.Key
}
Apparently so!
package main
import (
"fmt"
"reflect"
)
type slota struct {
key int
elem struct{}
}
type slotb struct {
elem struct{}
key int
}
func main() {
var a slota
var b slotb
fmt.Printf("slota: %d\n", reflect.TypeOf(a).Size())
fmt.Printf("slotb: %d\n", reflect.TypeOf(b).Size())
}
> go run tt.go 08:13:42
slota: 16
slotb: 8
My understanding is that Rust doesn't do this, and adding a ZST to a struct doesn't change the type. So I wonder what the implementation difference is.
Go does not allow pointers at the end of allocations, so when a ZST is the last field of a structure it needs something to be present after the ZST in order to keep that pointer in-bounds: https://github.com/golang/go/issues/58483#issuecomment-1427182579
In Rust, pointers to the end of an allocated object are valid.