News for hackers. Stuff that matters.
Re:Down the Golang Nil Rabbit Hole by TheDong on Tuesday March 30, 2021 @01:33AM (#26631497)
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

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.

Re:Down the Golang Nil Rabbit Hole by jdsalaro on Tuesday March 30, 2021 @03:02PM (#26638869)
Can you elaborate on how and why err==nil changes in your second case? A link to the docs would be helpful.

Thanks!

Re:Down the Golang Nil Rabbit Hole by TheDong on Tuesday March 30, 2021 @03:32PM (#26639262)
It'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 ) and secondly, nil interfaces vs typed nils stored in an interface type (see this FAQ entry 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.

Re:Down the Golang Nil Rabbit Hole by jdsalaro on Tuesday March 30, 2021 @04:13PM (#26639789)
Thank you!
Re:Down the Golang Nil Rabbit Hole by jrockway on Tuesday March 30, 2021 @05:07AM (#26632498)
I 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.

Re:Down the Golang Nil Rabbit Hole by pishpash on Tuesday March 30, 2021 @02:51AM (#26631813)
Sure, 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))