The Golang linter that checks that there is no simultaneous return of `nil` error and an invalid value.

nilnil

CI Go Report Card Coverage MIT License

Checks that there is no simultaneous return of nil error and an invalid value.

Installation & usage

$ go install github.com/Antonboom/nilnil@latest
$ nilnil ./...

Motivation

return nil, nil is not idiomatic for Go. The developers are used to the fact that if there is no error, then the return value is valid and can be used without additional checks:

user, err := getUser()
if err != nil {
    return err
}
if user != nil { // Ambiguous!
    // Use user.
}

In the worst case, code like this can lead to panic.

Rewrite the example for sentinel error:

user, err := getUser()
if errors.Is(err, errUserNotFound) {
    // Do something and return.
}
if err != nil {
    return err
}

// Use user.

What if I think it's bullshit?

I understand that each case needs to be analyzed separately, but I hope that the linter will make you think again - is it necessary to use an ambiguous API or is it better to do it using a sentinel error?

In any case, you can just not enable the linter.

Configuration

# command line (see help for full list of types)
$ nilnil --checked-types ptr,func ./...
# https://golangci-lint.run/usage/configuration/
nilnil:
  checked-types:
    - ptr
    - func
    - iface
    - map
    - chan

Examples

parsePublicKey from crypto/tls
// BEFORE

func parsePublicKey(algo PublicKeyAlgorithm, keyData *publicKeyInfo) (interface{}, error) {
    der := cryptobyte.String(keyData.PublicKey.RightAlign())
    switch algo {
    case RSA:
        // ...
        return pub, nil
    case ECDSA:
        // ...
        return pub, nil
    case Ed25519:
        // ...
        return ed25519.PublicKey(der), nil
    case DSA:
        // ...
        return pub, nil
    default:
        return nil, nil
    }
}

// AFTER

var errUnknownPublicKeyAlgo = errors.New("unknown public key algo")

func parsePublicKey(algo PublicKeyAlgorithm, keyData *publicKeyInfo) (interface{}, error) {
    der := cryptobyte.String(keyData.PublicKey.RightAlign())
    switch algo {
    case RSA:
        // ...
        return pub, nil
    case ECDSA:
        // ...
        return pub, nil
    case Ed25519:
        // ...
        return ed25519.PublicKey(der), nil
    case DSA:
        // ...
        return pub, nil
    default:
        return nil, fmt.Errorf("%w: %v", errUnknownPublicKeyAlgo, algo)
    }
}
http2clientConnReadLoop from net/http
// BEFORE

// As a special case, handleResponse may return (nil, nil) to skip the frame.
func (rl *http2clientConnReadLoop) handleResponse(/* ... */) (*Response, error) {
    if statusCode >= 100 && statusCode <= 199 {
        return nil, nil
    }
}

// ...
res, err := rl.handleResponse(cs, f)
if err != nil {
	return err
}
if res == nil {
    // (nil, nil) special case. See handleResponse docs.
    return nil
}

// AFTER

var errNeedSkipFrame = errors.New("need skip frame")

// As a special case, handleResponse may return errNeedSkipFrame to skip the frame.
func (rl *http2clientConnReadLoop) handleResponse(/* ... */) (*Response, error) {
    if statusCode >= 100 && statusCode <= 199 {
        return nil, errNeedSkipFrame
    }
}

// ...
res, err := rl.handleResponse(cs, f)
if errors.Is(err, errNeedSkipFrame) {
    return nil
}
if err != nil {
    return err
}
Not implemented
// BEFORE

func (s *Service) StartStream(ctx context.Context) (*Stream, error) {
    return nil, nil
}

// AFTER

func (s *Service) StartStream(ctx context.Context) (*Stream, error) {
    return nil, errors.New("not implemented")
}
nil-safe type
package ratelimiter

type RateLimiter struct {
    // ...
}

func New() (*RateLimiter, error) {
    // It's OK, RateLimiter is nil-safe.
    // But it's better not to do it anyway.
    return nil, nil
}

func (r *RateLimiter) Allow() bool {
    if r == nil {
        return true
    }
    return r.allow()
}

Assumptions

Click to expand
  • Linter only checks funcs with two return arguments, the last of which has error type.
  • Next types are checked:
    • pointers, functions & interfaces (panic: invalid memory address or nil pointer dereference);
    • maps (panic: assignment to entry in nil map);
    • channels (fatal error: all goroutines are asleep - deadlock!)
  • uinptr & unsafe.Pointer are not checked as a special case.
  • Supported only explicit return nil, nil.
  • Types from external packages are not supported.

Check Golang source code

Click to expand
$ cd $GOROOT/src
$ nilnil ./...
/usr/local/go/src/net/sockopt_posix.go:48:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/crypto/x509/parser.go:321:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/crypto/tls/key_agreement.go:45:2: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/database/sql/driver/types.go:157:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/database/sql/driver/types.go:231:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/database/sql/driver/types.go:262:4: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/debug/dwarf/entry.go:882:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/debug/dwarf/line.go:146:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/debug/dwarf/line.go:153:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/debug/dwarf/typeunit.go:138:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/debug/pe/file.go:450:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/net/http/h2_bundle.go:8644:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/net/http/transfer.go:768:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/net/http/transfer.go:778:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/net/http/transfer.go:801:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/go/build/build.go:1404:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/go/build/build.go:1414:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/go/build/build.go:1419:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/go/build/build.go:1453:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/internal/profile/legacy_profile.go:1087:4: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/net/internal/socktest/switch.go:142:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/crypto/tls/handshake_server_test.go:411:4: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/crypto/tls/handshake_server_test.go:1012:3: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/crypto/tls/handshake_server_test.go:1470:4: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/crypto/tls/tls_test.go:747:4: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/crypto/tls/tls_test.go:751:4: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/crypto/tls/tls_test.go:755:4: return both the `nil` error and invalid value: use a sentinel error instead
/usr/local/go/src/encoding/xml/xml_test.go:92:4: return both the `nil` error and invalid value: use a sentinel error instead
Owner
Anton Telyshev
https://www.golang-courses.ru/
Anton Telyshev
Similar Resources

A linter that handles struct tags.

Tagliatelle A linter that handles struct tags. Supported string casing: camel pascal kebab snake goCamel Respects Go's common initialisms (e.g. HttpRe

Dec 15, 2022

Linter for PostgreSQL

Использование Проверить миграции: oh-my-pg-linter check ./migrations/*.sql Добавить директории с дополнительными проверками (переопределение - кто пос

Nov 25, 2021

containedctx detects is a linter that detects struct contained context.Context field

containedctx containedctx detects is a linter that detects struct contained context.Context field Instruction go install github.com/sivchari/contained

Oct 22, 2022

World's spookiest linter

nosleep The world's spookiest linter nosleep is a golang-ci compatible linter which checks for and fails if it detects usages of time.Sleep. Why did y

Oct 15, 2022

Go linter to analyze expression groups: require 'import' declaration groups

grouper — a Go linter to analyze expression groups Installation

Jun 19, 2022

funcresult — a Go linter to analyze function result parameters

Go linter to analyze function result parameters: require named / unnamed function result parameters

Jan 27, 2022

Goalinter-v1: Goa framework (version1) linter

goavl: Goa framework (ver1) linter goavlは、goa version1(フォーク版)のlinterです。開発目的は、goa

Jul 28, 2022

Linter for Go's fmt.Errorf message

wrapmsg wrapmsg is Go code linter. this enforces fmt.Errorf's message when you wrap error. Example // OK 👍🏻 if err := pkg.Cause(); err != nil { re

Dec 27, 2022

Fast division, modulus and divisibility checks in Go for divisors known only at runtime.

fastdiv Fast division, modulus and divisibility checks for divisors known only at runtime via the method of: "Faster Remainder by Direct Computation:

Jan 8, 2023
Go linter that checks types that are json encoded - reports unsupported types and unnecessary error checks

Checks types passed to the json encoding functions. Reports unsupported types and reports occations, where the check for the returned error can be omited.

Oct 7, 2022
nilassign finds that assigning to invalid memory address or nil pointer dereference.

nilassign nilassign finds that assigning to invalid memory address or nil pointer dereference. Instruction go install github.com/sivchari/nilassign/cm

Nov 4, 2021
Go linter which checks for dangerous unicode character sequences

bidichk - checks for dangerous unicode character sequences bidichk finds dangerous unicode character sequences in Go source files. Considered dangerou

Oct 5, 2022
nostdglobals is a simple Go linter that checks for usages of global variables defined in the go standard library

nostdglobals is a simple Go linter that checks for usages of global variables defined in the go standard library

Feb 17, 2022
a simple golang SSA viewer tool use for code analysis or make a linter
a simple golang SSA viewer tool use for code analysis or make a linter

ssaviewer A simple golang SSA viewer tool use for code analysis or make a linter ssa.html generate code modify from src/cmd/compile/internal/ssa/html.

May 17, 2022
misspelled word linter for Go comments, string literals and embedded files

gospel The gospel program lints Go source files for misspellings in comments, strings and embedded files. It uses hunspell to identify misspellings an

Aug 6, 2022
The most opinionated Go source code linter for code audit.
The most opinionated Go source code linter for code audit.

go-critic Highly extensible Go source code linter providing checks currently missing from other linters. There is never too much static code analysis.

Jan 6, 2023
[mirror] This is a linter for Go source code.

Golint is a linter for Go source code. Installation Golint requires a supported release of Go. go get -u golang.org/x/lint/golint To find out where g

Dec 23, 2022
Staticcheck - The advanced Go linter

The advanced Go linter Staticcheck is a state of the art linter for the Go programming language. Using static analysis, it finds bugs and performance

Jan 1, 2023
A Go linter to check that errors from external packages are wrapped

Wrapcheck A simple Go linter to check that errors from external packages are wrapped during return to help identify the error source during debugging.

Dec 27, 2022