3 ms·
An example of this which I think every gopher hits at least once is the use of an error struct instead of an interface in a return value: func returnsNilEr
by TheDong 6y ago
An example of this which I think every gopher hits at least once is the use of an error struct instead of an interface in a return value:
func returnsNilError() (int, *MyErr) {
return 0, nil
}
// case 1: no other use of error, nil works
{
i, err := returnsNilError()
_ = i
fmt.Printf("err is nil: %v\n", err == nil)
}
// case 2: someone adds a couple lines of code before that call...
{
_, err := os.Open("/tmp/file")
i, err := returnsNilError()
_ = i
fmt.Printf("err is nil: %v\n", err == nil)
}
// Wait, what? Someone adding a line of code before your code changes how your nil check works?
Playground: https://play.golang.org/p/-t1Hq8WMF8l https://play.golang.org/p/-t1Hq8WMF8l
This, I think, is the reason everyone uses "error" in their return values in go, even though using a specific type (like "*os.PathError") would make it much easier to know what possible error conditions a function could have.
- pishpash 6y agoSure, with = (in this case the shorthand :=), what happens before can change how it works. The second := in case 2 should have raised a visual alarm. The fact that := even assigned for err a second time means inference could have happened, like err = (*MyErr)(nil) To catch this, you could have written the nil check like: fmt.Printf("err is nil: %v\n", (*MyErr)(err) == nil) and the compiler will croak. Or, to prevent this, replicate the inference explicitly: fmt.Printf("err is nil: %v\n", err == (*MyErr)(nil))
- jrockway 6y agoI recommend writing `if _, err := ...; err != nil { ... }` where ever possible, to avoid the case above where you overwrite the existing error. It is not possible to always do this; `if val, err := ...; err != nil { } else { use(val) }` is a Bit Much.
- jdsalaro 6y agoCan you elaborate on how and why err==nil changes in your second case? A link to the docs would be helpful. Thanks!
- TheDong 6y agoIt's a combination of two things. First, type inference from "short variable declaration" (see the spec here: https://golang.org/ref/spec#Short_variable_declarations https://golang.org/ref/spec#Short_variable_declarations ) and secondly, nil interfaces vs typed nils stored in an interface type (see this FAQ entry https://golang.org/doc/faq#nil_error https://golang.org/doc/faq#nil_error ). Another way to explain the difference is the following: // case 1 is the same as var i int // inferred type of *MyErr because that's what returnsNilError returns var err *MyErr i, err = 0, (*MyErr)(nil) // err is of type (nil)(*MyErr) // case 2 is the same as // inferred type of error because os.Open returns that var err error err = os.Open("/tmp/file") var i int i err = 0, (error)((*MyErr)(nil)) // err is of type (error)((*MyErr)(nil)) Note well that "err" is a different type in those two cases. Type inference, plus the fact that short variable decleration re-uses an existing variable if the type is compatible, can result in previous code changing what type a variable is. It's well known that an interface with a type is != nil (i.e. "var nilErr *MyErr = nil; var errIface error = nilErr" results in an 'errIface' that is not equal to an untyped nil). I hope that explanation made sense to you.
- jdsalaro 6y agoThank you!