1

Is there a way to check in Go, whether a file descriptor is valid (after the initial open operation)?

Consider the following code fragment that opens a debug file if specified in command-line flags

    upstreamDebug := flag.String("upstream_debug", "", "File for debugging")
    flag.Parse()

    var upstreamDebugFile os.File
    if *upstreamDebug != "" {
        upstreamDebugFile, err := os.OpenFile(*upstreamDebug, os.O_CREATE|os.O_WRONLY, 0644)
        if err != nil {
            log.Fatal(err)
        }
        log.Println("writing to", upstreamDebugFile.Name())
        defer upstreamDebugFile.Close()
    }

So far, so good. But later on we wish to write to upstreamDebugFile, if & only if it's a valid descriptor - outside of this if { .. } clause.

Is there a way of testing it for validity? There does not seem to be any built-in method in the lib. Comparison with nil

    if upstreamDebugFile == nil {

gives an error

cannot convert nil to type os.File
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
tuck1s
  • 1,002
  • 9
  • 28
  • 2
    The application declares two variables named upstreamDebugFile and those variables have different types. Fix by declaring the outer variable as an *os.File and replacing the short variable declaration in the inner scope with assignment. – Charlie Tumahai Oct 11 '19 at 20:55
  • See also https://stackoverflow.com/questions/37351022/golang-mixed-assignation-and-declaration – chash Oct 11 '19 at 21:00
  • Thank you! Yes I had a scope error there; now fixed. I'm using the upstreamDebugFile value as an io.WriteCloser. I find it does not give equality with nil, even when the file is not set. – tuck1s Oct 11 '19 at 21:23
  • .. however if I treat it only as a `*os.File`, test for equality with `nil` works OK. – tuck1s Oct 11 '19 at 21:44
  • 1
    https://golang.org/doc/faq#nil_error "An interface value is nil only if the V and T are both unset" – chash Oct 11 '19 at 21:51
  • 1
    Post an [mcve]. The comments mentions an io.WriteCloser, but that's not shown in the question. – Charlie Tumahai Oct 12 '19 at 00:10
  • Agreed. Actually Torek's response below contains a great example (minus my silly scope error, and with the interface that was the heart of the issue). I have so much to learn from this! – tuck1s Oct 14 '19 at 11:29

1 Answers1

1

Per the comment:

I'm using the upstreamDebugFile value as an io.WriteCloser. I find it does not give equality with nil, even when the file is not set.

An io.WriteCloser is an instance of an interface. Meanwhile, os.OpenFile returns *os.File, which is a pointer to a struct. This implements the interface, but is not a value of the type of that interface. That's no big deal for some uses, because the fact that it implements the interface means that you can store the *os.File value in an io.WriteCloser.

However, as the following program shows, you must then be careful about testing whether an io.WriteCloser is nil, because it often isn't:

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    var iface io.WriteCloser
    p, err := os.OpenFile("/bad/foo", os.O_CREATE, 0666)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("before assignment, iface = %#v, and iface == nil => %t\n", iface, iface == nil)
    iface = p
    fmt.Printf("after assignment, iface = %#v, and iface == nil => %t\n", iface, iface == nil)
    fmt.Printf("meanwhile p == nil => %t\n", p == nil)
}

Go Playground output:

open /bad/foo: No such file or directory
before assignment, iface = <nil>, and iface == nil => true
after assignment, iface = (*os.File)(nil), and iface == nil => false
meanwhile p == nil => true

Once iface has an assigned type, you can't just test it as a whole. You must start testing its actual value, of its underlying type, as in this program:

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    var iface io.WriteCloser
    p, err := os.OpenFile("/bad/foo", os.O_CREATE, 0666)
    if err != nil {
        fmt.Println(err)
    }
    iface = p
    fmt.Printf("iface.(*os.File) == nil => %t\n", iface.(*os.File) == nil)
}

Try it on the Go Playground. But note that if iface has never been assigned a value of type *os.File, the type assertion will fail!

If what you're going to store is always going to be *os.File, just declare your variable to be *os.File up front:

var foo *os.File
if somecond {
    var err error
    foo, err = os.OpenFile(... arguments ...)
    ...
}
// later:
if foo != nil {
    ...
}

Note that you can also do this:

var iface io.WriteCloser
...
if somecond {
    p, err := os.OpenFile(...)
    ...
    if p != nil {
        iface = p
    }
}

and now iface == nil suffices again. But unless you're really planning to use multiple different underlying io.WriteCloser implementations, keep it simple and just use *os.File directly.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thank you for a very detailed explanation. As per @hmm comment above and as per your answer, I now test the *os.File value directly and that does what I need. – tuck1s Oct 14 '19 at 11:19
  • That type assertion is neat, but your remark `never been assigned a value of type *os.File, the type assertion will fail!` leads me to wonder - is there actually a nice, safe robust way to check an interface before using? – tuck1s Oct 14 '19 at 11:32
  • 1
    Sure: use either a type-*testing* conversion, or use a type-switch, or use `reflect` (slow but fully general). Using reflect is a bit tricky. A type-testing assertion has the form: `x, ok := iface.(sometype)`. If `iface`'s actual type, when the code is executed, is `sometype`, `x` becomes a value of that type and `ok` becomes `true`. If not, `x` becomes the zero-value of that type, and `ok` becomes `false`. – torek Oct 14 '19 at 20:48
  • 1
    The type-switch looks like: `switch v := iface.(type)` followed by a bunch of `case` statements with types in them. The case statement that runs is the one when `v` has the given type as acquired from the runtime value in `iface`. See the golang spec for more detail. – torek Oct 14 '19 at 20:51