nonnil — never return a bare nil for a Null-Object interface
Now maintained in the go-vet-analyzers org
nonnil has moved to github.com/go-vet-analyzers/nonnil. The install path below reflects the new home.
github.com/go-vet-analyzers/nonnil is a
golang.org/x/tools/go/analysis
analyzer (a go vet tool) that enforces the org's Null-Object invariant: a
value whose type is a Null-Object interface — any interface that declares
IsNull() bool — must never be a bare nil. It must instead be a real Null
object (Null.New(), NullError.New(), …) that
callers can always send messages to.
This turns "always return a Null object, never nil" from a matter of human
discipline into a check that fails CI, closing the one gap that lets the
nil-dereference the pattern exists to prevent slip back in.
What it enforces
The analyzer treats an interface type as a Null-Object interface when that
type declares an IsNull() bool method — taking no parameters and returning a
single bool — directly or through embedding. (isNullObjectInterface resolves
this with types.LookupFieldOrMethod.)
At every site where a bare nil would re-enter such a type, it reports a
diagnostic:
return nil— anilin any result position of a function or function literal whose declared result type is a Null-Object interface. Only the position-aligned form (return a, b, c) is inspected; a naked return or a single call feeding multiple results (return f()) is left alone to avoid false positives.x = nil— assignment ofnilto a Null-Object-typed variable or field.var x T = nil— initialisation of a value spec whose explicit type is a Null-Object interface.- composite literals — a
nilelement targeting a Null-Object-typed slot: a struct field (T{Field: nil}or positional), a map value (map[K]T{k: nil}), or a slice/array element ([]T{nil},[N]T{nil}).
The diagnostic for a return reads:
return a Null object (e.g. Null.New()) instead of nil for <Type>: it is a
Null-Object interface (IsNull() bool), and nil reintroduces the nil-dereference
the pattern prevents
Assignment / construction sites produce the parallel message
assign/initialise/set field … to/set map value to/set element to a
Null object instead of nil.
Deliberately conservative
Interfaces without IsNull() are never flagged — including the builtin
error and Result.Interface — so false positives are minimal. A typed
conversion return (Thing)(nil), a nil interface variable, an omitted
struct field (its zero value), and a nil map key are all left alone, since
they are explicit, structural, or uncommon.
Flagged vs. accepted
import (
Error "github.com/go-composites/error/src"
NullError "github.com/go-composites/error/src/null"
)
// FLAGGED: Error.Interface declares IsNull() bool, and this returns a bare nil.
func lookup(bad bool) Error.Interface {
if bad {
return nil // nonnil: return a Null object (e.g. Null.New()) instead of nil
}
return Error.New("boom")
}
// ACCEPTED: hands back a real Null object the caller can always message.
func lookupOK(bad bool) Error.Interface {
if bad {
return NullError.New()
}
return Error.New("boom")
}
type Box struct {
Err Error.Interface
}
_ = Box{Err: nil} // FLAGGED: set field Err to a Null object instead of nil
_ = Box{Err: NullError.New()} // ACCEPTED
var e Error.Interface = nil // FLAGGED: initialise a Null object instead of nil
e = nil // FLAGGED: assign a Null object instead of nil
var ok error = nil // ACCEPTED: builtin error has no IsNull(), not a Null-Object interface
Install & run
go install github.com/go-vet-analyzers/nonnil/cmd/nonnil@latest
go vet -vettool=$(which nonnil) ./...
The command wraps the analyzer with singlechecker, so it also runs standalone
and accepts the usual go vet flags and package patterns.
CI integration
Every org repo runs nonnil on push and pull request via the nonnil.yml
workflow, which installs the tool and vets the whole module:
- run: go install github.com/go-vet-analyzers/nonnil/cmd/nonnil@latest
- run: go vet -vettool="$(go env GOPATH)/bin/nonnil" ./...
A bare nil in any of the positions above turns the job red, making the mistake
un-mergeable.
License
BSD-3-Clause © the go-vet-analyzers/nonnil authors.