Go version

go version go1.24.5 linux/amd64

Output of go env in your module/workspace:

AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/ubuntu/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/ubuntu/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3272950330=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/tmp/example/go.mod'
GOMODCACHE='/home/ubuntu/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/ubuntu/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/home/ubuntu/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.5.linux-amd64'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/ubuntu/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/home/ubuntu/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.5.linux-amd64/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.24.5'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

I tried running go vet on the following code and expected no errors:

// example1.go
package example

import "fmt"

func ConditionallyFormattedMessage(message string, args ...interface{}) string {
        if len(args) == 0 {
                return message
        } else {
                return fmt.Sprintf(message, args...)
        }
}

func Example1(arg string) string {
        return ConditionallyFormattedMessage(arg)
}

What did you see happen?

Running go vet on the above code results in a printf problem being reported despite there being none:

$ go vet
# example.com/m
./example1.go:15:46: non-constant format string in call to example.com/m.ConditionallyFormattedMessage

What did you expect to see?

I expected no errors.

Additional context

The above is a minimal reproduction of an issue I'm seeing with methods in the (quite popular) errorx package such as (*github.com/joomcode/errorx.Type).New and (*github.com/joomcode/errorx.Type).Wrap. The errorx package has a method that is very similar to the ConditionallyFormattedMessage() function above - ErrorBuilder.WithConditionallyFormattedMessage(): https://github.com/joomcode/errorx/blob/3280086cb5400e2900300ba010ddf658fa717fc4/builder.go#L84-L95 which is used by the aforementioned New and Wrap methods: https://github.com/joomcode/errorx/blob/3280086cb5400e2900300ba010ddf658fa717fc4/type.go#L41-L45 https://github.com/joomcode/errorx/blob/3280086cb5400e2900300ba010ddf658fa717fc4/type.go#L60-L65

An alternative reproduction using github.com/joomcode/errorx@v1.2.0 can be seen below:

// example2.go
package example

import "github.com/joomcode/errorx"

func Example2(arg string) *errorx.Error {
        return errorx.InternalError.New(arg)
}
$ go vet
# example.com/m
./example2.go:7:34: non-constant format string in call to (*github.com/joomcode/errorx.Type).New

Comment From: gabyhelp

Related Issues

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: cagedmantis

@adonovan @matloob @findleyr

Comment From: adonovan

The tacit assumption of the printf checker is that "conditionally printf" is not a great way to design APIs. For example, ConditionallyFormattedMessage("100%!") returns "100%!", but as soon as you add arguments, the interpretation of the string becomes non-literal, which is potentially surprising. So my advice is: eliminate the special case for zero args. And add an "f" suffix to the function.

However, you're not the first person to run into this issue, so we have at least a problem of inadequate documentation. We can fix that.