Go linter that checks for required fields in structs.

  • By Abhinav Gupta
  • Last update: Mar 30, 2023
  • Comments: 5

requiredfield

Introduction

Go Reference CI codecov

requiredfield is a linter for Go that verifies that required fields of a struct are filled when it is initialized. Whether a field is required is specified with a comment.

For example:

type BufWriter struct {
    W      io.Writer     // required
    Buffer *bytes.Buffer
}

The linter will return an error on the following snippet:

w := BufWriter{Buffer: b}
// ERROR: missing required fields: W

To read more about the motivation for this linter, see Motivation.

Installation

Install the binary from source by running:

go install go.abhg.dev/requiredfield/cmd/[email protected]

Usage

To use the linter, run the binary directly:

requiredfield ./...

Alternatively, use it with go vet:

go vet -vettool=$(which requiredfield) ./...

Overview

To indicate that a field is required, add a // required comment next to it.

type BufWriter struct {
    W      io.Writer     // required
    Buffer *bytes.Buffer
}

This indicates that the W field is required.

All instantiations of BufWriter using the T{...} form will be required to set the W field explicitly.

For example:

w := BufWriter{Buffer: b}
// ERROR: missing required fields: W

Syntax

Fields are marked as required by adding a comment in one of the following forms next to them:

// required
// required<sep><description>

Where <sep> is a non-alphanumeric character, and <description> is an optional description.

For example:

type User struct {
    Name  string // required: must be non-empty
    Email string
}

The description is for the benefit of other readers only. requiredfield will ignore it.

Positioning

The // required comment must be on the line where the field is defined.

GOOD                         | BAD
-----------------------------+-------------------
type User struct {           | type User struct {
    Name string // required  |     // required
}                            |     Name string
                             | }

If the field definition is spread across multiple lines, the comment must be on the last of these. For example,

type Watcher struct {
    Callback func(
        ctx context.Context,
        req *Request,
    ) // required
}

Behavior

Any time a struct is initialized in the form T{..}, requiredfield will ensure that all its required fields are set explicitly.

u := User{
    Email: email,
}
// ERROR: missing required fields: Name

Required fields can be set to the zero value of their type, but that choice must be made explicitly.

u := User{
    Name: "", // computed below
    Email: email,
}
// ...
u.Name = name

FAQ

Why a comment instead of a struct tag?

The reasons for this choice are both, philosophical and cosmetic.

First, the philosophical reason: requiredfield is a linter that runs at compile-time, and therefore wants its footprint limited to compile-time only. Struct tags get compiled into your binary and are available at runtime via reflection. It would become possible for someone to change how the program behaves based on the value of those struct tags. requiredfield considers that a violation of the linter's boundaries, and aims to prevent that by using comments instead.

The cosmetic reason is much easier to explain: Struct tags are uglier than line comments.

Author ID `required:"true"`

// versus

Author ID // required

Motivation

A common pattern in Go is to use a struct to pass several parameters to a function. This is often referred to as a "parameter object" or a "parameter struct". If you're unfamiliar with the concept, you can read more about it in Designing Go Libraries > Parameter objects.

In short, the pattern provides some advantages:

  1. readability: names of fields are visible at call sites, allowing them to act as a form of documentation similar to named parameters in other languages
  2. flexibility: new fields can be added without updating all existing call sites

These are both desirable properties for libraries: users of the library get a readable API and maintainers of the library can add new optional fields without a major version bump.

For applications, however, the flexibility afforded by the pattern can turn into a problem. Application-internal packages rarely cares about API backwards compatibility and are prone to adding new required parameters to functions. If they use parameter objects, they lose the ability to safely add these required parameters: they can no longer have the compiler tell them that they missed a spot.

So application developers are left to choose between:

  • parameter objects: get readability, lose safety
  • functions with tens of parameters: lose readability, get safety

requiredfield aims to fill this gap with parameter objects so that applications can still get the readability benefits of using them without sacrificing safety.

License

This software is made available under the MIT license.

Download

requiredfield.zip

Comments(5)

  • 1

    Bump golang.org/x/tools from 0.7.0 to 0.8.0

    Bumps golang.org/x/tools from 0.7.0 to 0.8.0.

    Release notes

    Sourced from golang.org/x/tools's releases.

    gopls/v0.8.0

    Go version support

    Support for Go 1.18

    Version 0.8.0 of gopls supports features added to Go 1.18, specifically:

    • Support for multi-module workspaces using go.work files.
    • Diagnostics for Fuzz tests.
    • Improved support for generics.

    To use these features, gopls must be installed using Go 1.18. See go.dev/dl for the latest status of Go 1.18 -- as of writing Go 1.18 is not yet released, but Go 1.18 RC1 may be used.

    Dropped support for Go 1.12

    Version 0.8.0 of gopls no longer supports building at Go 1.12. See golang/go#50827 for more details.

    New Features

    go.work integration

    Gopls now supports multi-module workspaces using go.work files. To use this feature, create a go.work file that includes the modules you want to work on, and then open your editor to the directory containing go.work. For more information, see the go.work reference, or the gopls documentation for multi-module workspaces.

    Diagnostics for Fuzz tests

    When writing Fuzz tests, gopls provides diagnostics for invalid arguments to Fuzz. image

    Improved support for generics

    Honor the language version configured in go.mod

    gopls now provides diagnostics for language features based on the go directive in the applicable go.mod file for a package.

    For some errors related to incompatible language versions, gopls offers a quick-fix to update the go.mod Go version. (note that if the go.mod file is open, it may need to be saved in order for language version changes to take effect) gomodedit

    Improved completion with type parameters

    Gopls autocompletion is improved in several contexts when using generic types.

    genericvarcompl

    New code action to add missing method stubs

    Gopls now provides a code action to implement missing methods.

    implementiface

    Improved performance and accuracy for workspace symbol requests

    ... (truncated)

    Commits
    • 5ef3193 gopls/internal/lsp/source/typerefs: reexpress tests wrt ExternalRefs
    • c5f768a go.mod: update golang.org/x dependencies
    • 7c33a56 gopls/internal/lsp/source: show both the original declaration and the value o...
    • 4d205d8 gopls/doc: add instructions for using go.work with the Go distribution
    • d5076cc gopls/internal/lsp/cache: don't trace a region for MetadataForFile
    • f796361 gopls/internal/lsp: add tracing instrumentation for all Server methods
    • e104501 gopls/internal/astutil: TestPurgeFuncBodies requires source code for std
    • 6520870 gopls/internal/lsp/source/typerefs: allow for duplicate decls
    • 902fdca gopls/internal/lsp/source/typerefs: purge func bodies before parsing
    • 58c9a63 go/packages/internal/nodecount: count ast.Node frequency
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
  • 2

    var form tracking

    The linter only supports the x := T{..} form of structs. It may be possible to also track var form:

    var x T
    x.Foo = a
    x.Bar = b
    dostuff(x)
    

    This will likely need to account for control flow:

    if cond {
      x.Foo = whatever
    }
    dostuff(x)
    // ERROR: required field may be unset: Foo
    

    From cursory research, this will likely need to inspect the SSA IR.

  • 3

    doc: How to group fields, how to document fields

    The documentation doesn't make it clear how to group multiple required fields. For the same type, it's currently:

    type foo struct {
      A, B, C string // required
    }
    

    It also isn't clear that you can still document your fields like so:

    type bar struct {
      // Baz specifies stuff.
      Baz stuff // required
    }
    
  • 4

    Required by default, granular opt-in/out

    There are frequent cases where "field is required" is the default, and optional is the exception. e.g. there are a fair number of instances of // optional [desc] in the Go source itself.

    There should be a way to opt into "required by default" at a package level. This wouldn't be a global default for this because it would have far too many false positives.

    With package-level opt-in, we'll inevitably need struct-level opt-out, so this issue is also for granular required/optional opt-in for structs or packages.

  • 5

    flag: -strict mode

    In strict mode, the linter will disallow var-based instantiations of structs with required fields. (Pointers would be allowed.) The argument for that being that with var x Foo is equivalent to x := Foo{}, and that's not allowed if there are any required fields.

    This would be opt-in.