What version of Go are you using (go version)?

$ go version
go version go1.14.4 linux/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="$HOME/.cache/go-build"
GOENV="$HOME/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="$HOME/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/lib/google-golang"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/google-golang/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build196177420=/tmp/go-build -gno-record-gcc-switches"

What did you do?

https://play.golang.org/p/YVE8n6N0xUT

func printError(err error) {
    fmt.Printf("%v, %s, %q\n", err, err, err)
}

func main() {
    printError(nil)
    printError(io.EOF)
}

What did you expect to see?

<nil>, <nil>, <nil>
EOF, EOF, "EOF"

What did you see instead?

<nil>, %!s(<nil>), %!q(<nil>)
EOF, EOF, "EOF"

What's the rationale?

It's often useful to print error values enclosed with quotes in test failures:

if (err != nil) != tc.wantErr {
  t.Fatalf("Foo(...) returned %q, wantErr: %t", err, tc.wantErr) // problematic when err == nil
}

but %q (as well as %s) treats a nil value as a format error and prints %!q(<nil>) instead of plain <nil>. Writing \"%v\" instead of %q avoids the ugly error output, but it doesn't handle escaping well, and it's not desirable to have even "<nil>" enclosed with quotes. This leaves the plain %v as the only easy option (unless you are willing to have another if).

Wouldn't it be nice to have %q (and %s by extension) print just <nil>, as with %v, if the argument is nil? %q and %s already handle an error argument as a string (through the Error method) if the argument is not nil, but having an error output for a nil argument limits its usefulness.

Comment From: ianlancetaylor

CC @robpike

Comment From: robpike

I'm not convinced either way. It seems odd to me that a correct output from %q would not include quotes, but "<nil>" is also clearly wrong.

It may be wisest to do nothing for most types and do something special only for the error type, where people are used to seeing nil in test output and such.

But it's a fiddly thing to do.

Comment From: musiphil

< and > in <nil> (kind of) serve as quotes in the nil case. As you said, using the "..." syntax (even "") would be clearly wrong, but <nil> signals that it's not even a valid string but just the plain nil.

As for the work, wouldn't it suffice to add 'q' and 's' in printArg?

Comment From: dmitshur

I've also run into this issue in the past, and my initial suspicion was that was an issue in fmt package that needed to be fixed. I've spent more time investigating and considering what can be done, but in the end, I couldn't convince myself that there were any favorable changes that should be done in fmt itself. (Of course, it's possible I missed something that can be done.)

I instead modified the code to use %v in cases where the error might be nil, and reserved my use of %s (or %q) to cases where I've checked that it's not.

Comment From: ianlancetaylor

@robpike The catch is that since error is an interface type, a nil error gets converted to the empty interface as nil when calling fmt.Printf and friends. By the time that the fmt package sees the value, it's just a nil value of type interface{}, and the fact that it was once an error has been lost.

Comment From: robpike

I'm not opposed to this change, and I'm also not enthusiastically in favor. My main concern at this point is the effect it will have on tests, as it could change golden output. It might be more disruptive than it's worth, but we can try it in the cycle if you like. As stated above, it's very easy to do. The challenge lies elsewhere.

Comment From: robpike

@ianlancetaylor The package knows it's an error as it needs to check for the Error method, so it could be done there. But it's simpler and easier to explain if the change, should it happen, is done at the top of printArg.

Comment From: ianlancetaylor

@robpike I may be missing something but I think that in this case there is no way that the fmt functions can tell that the value was originally an error. https://play.golang.org/p/8xDEPCAqoFa

Comment From: robpike

@ianlancetaylor I see what you mean. Let's make the conversation more confusing: https://play.golang.org/p/pVnlOIX1ArU

In any case, as I said most recently, it's probably best to do this for all types in printArg if we do it at all. I'd like some discussion about that, though, because it might be disruptive.

Comment From: gonnafaraway

@robpike hi it's still actual.