Skip to content

firefart/nonamedreturns

Repository files navigation

nonamedreturns

A Go linter that reports all named returns in function and method signatures.

I hate named returns in golang because they are error prone (see Why are named returns error prone? below). That's why I wrote this linter. That's all.

Tutorial on how to write your own linter: https://disaev.me/p/writing-useful-go-analysis-linter/

Installation

As a standalone binary

go install github.com/firefart/nonamedreturns@latest

Prebuilt binaries for Linux, macOS and Windows are also attached to every release.

Via golangci-lint

nonamedreturns is bundled with golangci-lint. You do not need to install it separately; just enable it in your configuration (see Settings).

Usage

Standalone

The binary is a standard go/analysis checker, so it accepts the usual package patterns:

# check the current module
nonamedreturns ./...

# check a single package
nonamedreturns ./internal/foo

Any named return value produces a diagnostic such as:

./foo.go:10:1: named return "err" with type "error" found

To see all available flags run:

nonamedreturns -help

golangci-lint

Enable the linter in your .golangci.yml:

linters:
  enable:
    - nonamedreturns

Then run it as part of your normal lint step:

golangci-lint run ./...

Settings

The linter has a single setting.

report-error-in-defer

Type bool
Default false
Standalone flag -report-error-in-defer
golangci-lint key report-error-in-defer

A common, legitimate use of a named return is a named error that is inspected or modified inside a defer before the function returns. By default the linter does not report a named return when all of the following are true:

  1. its type is exactly the built-in error,
  2. it is referenced (read or assigned) inside a defer closure in the same function, and
  3. it is assigned somewhere in the function body (inside the defer or anywhere else).

Set report-error-in-defer to true if you want these named errors reported as well.

Example that is allowed by default

func doRequest(ctx context.Context) (err error) {
 span := startSpan(ctx)
 defer func() {
  // err is read here to record the outcome
  if err != nil {
   span.RecordError(err)
  }
  span.End()
 }()

 err = callSomething()
 return
}

This is not reported with the default settings (the named error is used in the defer and assigned in the body), but is reported when report-error-in-defer: true.

Standalone

nonamedreturns -report-error-in-defer=true ./...

golangci-lint

linters:
  enable:
    - nonamedreturns
  settings:
    nonamedreturns:
      # report named error if it is assigned inside defer
      report-error-in-defer: true

Why are named returns error prone?

  1. Shadowing of Variables If you have a named return variable that is also used as a local variable within the function, it can lead to shadowing issues. This occurs when the named return variable is unintentionally shadowed by a local variable with the same name, leading to unexpected behavior.

  2. Accidental Changes Developers might inadvertently modify the value of a named return variable within the function, thinking it only affects the local variable and not the actual return value.

  3. Readability Issues While named returns can improve readability for method signatures, they can also make the code harder to understand if misused. It may be unclear whether a variable is a local variable or a return variable, especially in larger functions.

  4. Unused Variables Named returns often result in variables being declared in the function signature that are not used within the function body. This can lead to confusion and may make the code less maintainable.

  5. Unintentional scope increase Named return variables have scope for the whole function, as opposed to local variable which have scope only after they are defined. So, even if you are setting the value at the last few lines of the function, its scope still spans the whole function. This allows the variable to be referenced, before it's assigned the first time.

    Example

    func test(input s) (ret bool) {
       ....
       // ret is accessed before it's first assigned,
       // this code will not error out as the variable
       // s already defined.
       if ret {
          // do some stuff
       }
       ....
       ret = someRandomFunc(input) // ret is first assigned here
       }
    }

About

golang linter to detect named returns

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

  •  

Contributors

Languages