5 ms·
I think only #1 really counts as a gotchya. #3 is literally just "spend at least 2 seconds knowing what a slice is" (which you should do! there's only like 4 da
by dilap 9y ago
I think only #1 really counts as a gotchya. #3 is literally just "spend at least 2 seconds knowing what a slice is" (which you should do! there's only like 4 data structures in Go built in, so get to know 'em all). #2 is similar.
My favorite gotchya is that assigning a nil pointer to an interface will not give you a nil interface.
Once you know what's going on, it totally makes sense, but usually you don't start out with that deep an understanding of how interfaces work, and it violates natural-looking assumptions about algebraic equality, which makes for very easy counter-intuitive behavior.
Example:
package main
import "fmt"
func main() {
var x *ErrorX = nil
var err error = x
if err == nil {
fmt.Println("nope, this won't happen ")
} else {
fmt.Println("here's my non-nil error:", x)
}
}
type ErrorX struct{}
func (x *ErrorX) Error() string {
return "hi i'm ErrorX"
}
https://play.golang.org/p/BB1b2_HDb6I https://play.golang.org/p/BB1b2_HDb6I
- Matthias247 9y agoI agree on all of these. #1 would even count as gotcha when it would the loop would yield a reference instead of the a copy, since lots of code might expect a copy. I think in some other languages (e.g. C#) there was also some confusion and change about that. There are of course also the usual gotchas when capturing things in a closure (is it by value or by reference?). But this is not Go specific. You explained the main Go-specific Gotcha in my opinion: The dual-nullability of interface types, and the fact that non-null interface objects might contain null pointers to structs that implement the interface.
- Dylan16807 9y ago> I think in some other languages (e.g. C#) there was also some confusion and change about that. Nah. They wanted references, they got references. The issues were an oversight related to scope.
- catnaroek 9y ago> Once you know what's going on, it totally makes sense Anything “makes sense” from an operational point of view, so long as you internalize the rules. But does Go's handling of `nil` denote anything you ever actually want to express, regardless of language? > it violates natural-looking assumptions about algebraic equality No, Go's handling of `nil` doesn't violate anything about equality: everything (besides floats, but you can't fault Go for those) is equal to itself and to nothing else. But the syntax (i.e. using a single unadorned token `nil`) does a very poor job of reflecting the language's semantics (the existence fo multiple `nil` values, all different from one another).
- dilap 9y agoI'm sure there are uses for it; in this case, since making nil pointers not work would require extra rules, it doesn't seem worth doing. A suggestion I saw on reddit was to use a different word for "nil" for interfaces ("unset" or something), which would make the gotchya have less bite. And I wasn't trying to say nil-to-interface violated anything real, just that it violated natural-to-make-but-actually-untrue assumptions (which is I guess pretty much the definition of a gotchya): If we have a = nil b = a it's natural (but incorrect) to assume that we have b == nil Basically, I agree with you that the syntax is being poorly suggestive of the semantics. I like the "unset" suggestion because then we'd have: a = nil b = a which wouldn't falsely suggest b == unset Too late now, though.
- lenkite 9y agoCovered in the FAQ, but really I keep forgetting all the time. https://golang.org/doc/faq#nil_error https://golang.org/doc/faq#nil_error Under the covers, interfaces are implemented as two elements, a type and a value. The value, called the interface's dynamic value, is an arbitrary concrete value and the type is that of the value. For the int value 3, an interface value contains, schematically, (int, 3). An interface value is nil only if the inner value and type are both unset, (nil, nil). In particular, a nil interface will always hold a nil type. If we store a nil pointer of type * int inside an interface value, the inner type will be * int regardless of the value of the pointer: (* int, nil). Such an interface value will therefore be non-nil even when the pointer inside is nil.
- tapirl 9y agoThe design for #1 is good and consistent. There are not reference values in Go. All assignments in Go are value copies. BTW, in fact, the ", _" in the author's code can be omitted. for idx := range zoo { zoo[idx].legs = 999 }
- dilap 9y agoHypothetically, if the language defined for i, x := range xs to have x repeatedly be a new reference to each of xs members, it seems like we'd avoid 2 gotchyas (capture of x by a closure inside the for loop not doing what we want, and #1 in the original article). I think the downside would be the concept of references doesn't exist anywhere else in Go (so the "for ... range" loop would have to be its own thing, rather than explainable as a transformation to simpler code) & maybe also implementation concerns I don't know about. But if we were willing to pay the complexity cost it would make the language nicer, IMO. (I'm not saying we should be willing to pay the complexity cost though -- a lot of Go's charm is how much the language leaves out.)
- Merovius 9y ago> Hypothetically, if the language defined for i, x := range xs to have x repeatedly be a new reference to each of xs members, it seems like we'd avoid 2 gotchyas I'm not convinced. I believe what you'd end up with is that someone will write a "Common Gotchas in Go" article about how, if you think you are operating on a copy in the loop, you are actually operating on a reference. Really, it seems very non-obvious to me, why one would be a less surprising behavior than the other. (The fact that the loop variables are not scoped to the loop body - i.e. closures will share the references - is another issue and that I would pretty unambiguously call a gotcha that should be fixed…)
- dilap 9y agoYou think so? It just feels like I basically never want it to be a copy, but you do frequently want to modify the element you're iterating over. It actually pushes you to design things in ways you might otherwise not. E.g., instead of []X you'll have []*X just so it'll be easier to modify from inside a loop.
- stcredzero 9y agoI think only #1 really counts as a gotchya. #3 is literally just "spend at least 2 seconds knowing what a slice is" Most programming language gotchas are literally, "just spend {small time increment} learning about {thing}." Those things build up and get lost/misplaced. Programming "nickels and dimes you to death." Design is about the emergent interaction of myriad details. Programming language design is exactly that as well.
- dilap 9y agoi'd say a "gotchya" is something in a language where there's a subtlety that confounds what you would reasonably expect to happen, after obtaining a casual famiarity with the language. the slice examples doesn't qualify because it's integral to the fundamental idea of what a slice is. they only way this could confound your expectation is if you'd literally spent zero time learning what a go slice is. (so i guess it is a gotchya if you see "slice", know python slices, and assume the concepts are the same and don't bother to investigate any further.) by the way, i agree with you that "Design is about the emergent interaction of myriad details"; by that criteria go is very well-designed...