exhaustruct
is a golang analyzer that finds structures with uninitialized fields
Package being renamed to dev.gaijin.team/go/exhaustruct/v4
and all further updates will be published under this name.
go get -u dev.gaijin.team/go/exhaustruct/v4/cmd/exhaustruct
exhaustruct [-flag] [package]
Flags:
-i pattern | -include-rx pattern
Regular expression to match type names that should be processed.
Anonymous structs can be matched by '<anonymous>' alias.
Example: .*/http\.Cookie
-e pattern | -exclude-rx pattern
Regular expression to exclude type names from processing, has precedence over -include.
Anonymous structs can be matched by '<anonymous>' alias.
Example: .*/http\.Cookie
-allow-empty
Allow empty structures globally, effectively excluding them from the check
-allow-empty-returns
Allow empty structures in return statements
-allow-empty-declarations
Allow empty structures in variable declarations
-allow-empty-rx pattern
Regular expression to match type names that should be allowed to be empty.
Anonymous structs can be matched by '<anonymous>' alias.
Example: .*/http\.Cookie
If you're using golangci-lint, refer to the linters settings for the most up-to-date configuration guidance.
exhaustruct
supports comment directives to mark individual structure declarations as ignored during linting or enforce
it's check regardless global configuration. Comment directives have precedence over global configuration.
//exhaustruct:ignore
- ignore structure during linting//exhaustruct:enforce
- enforce structure check during linting, even in case global configuration says it should be ignored.
Note: all directives can be placed on the line above opening bracket or on the same line.
Also, any additional comment can be placed same line right after the directive or anywhere around it, but directive should be at the very beginning of the line. It is recommended to comment directives, especially when ignoring structures - it will help to understand the reason later.
By default, linter will check all structures and report if any accessible field initialization is missing.
package main
type Config struct {
Host string
Port int
Database string
}
// Without any flags - requires all fields
func createConfig() Config {
return Config{} // ERROR: missing fields Host, Port, Database
}
func createValidConfig() Config {
return Config{
Host: "localhost",
Port: 5432,
Database: "mydb",
}
}
Rationale: Useful when working with configuration structs that have sensible defaults, or when migrating codebases where empty structs are acceptable everywhere.
exhaustruct -allow-empty ./...
package main
// With -allow-empty: ALL empty structs are allowed
func createConfig() Config {
return Config{} // OK: empty structs allowed globally
}
var globalConfig = Config{} // OK: empty structs allowed globally
func processConfigs() []Config {
return []Config{{}} // OK: empty structs allowed globally
}
Rationale: Common pattern where functions return zero-value structs in error conditions, while still enforcing
proper initialization in other contexts.
This option allows zero-value structs only as a direct child of return statement.
exhaustruct -allow-empty-returns ./...
package main
// With -allow-empty-returns: empty structs allowed only in return statements
func createConfig() Config {
return Config{} // OK: empty struct in return statement
}
func initializeConfig() {
var config = Config{} // ERROR: empty struct in variable declaration
_ = config
}
func processConfigs() []Config {
return []Config{{}} // ERROR: empty struct in slice literal (not direct child of return statement)
}
Rationale: Allows empty initialization in variable declarations while enforcing proper initialization in other contexts. Useful for gradual struct population patterns. This option allows zero-value structs only as a direct child of variable declaration statement.
exhaustruct -allow-empty-declarations ./...
package main
// With -allow-empty-declarations: empty structs allowed in variable declarations
func initializeConfig() {
var config = Config{} // OK: empty struct in variable declaration
config := Config{} // OK: empty struct in short variable declaration
ptr := &Config{} // OK: empty struct in pointer declaration
_ = config
_ = ptr
}
func createConfig() Config {
return Config{} // ERROR: empty struct in return statement
}
func processConfigs() []Config {
return []Config{{}} // ERROR: empty struct in slice literal
}
Rationale: Granular control allowing empty structs only for specific types, typically third-party libraries or specific patterns where empty initialization is common practice.
exhaustruct -allow-empty-include ".*Config.*" -allow-empty-include ".*Options.*" ./...
package main
type DatabaseConfig struct {
Host string
Port int
}
type ServerOptions struct {
Timeout int
MaxConns int
}
type UserData struct {
Name string
Email string
}
func example() {
// OK: matches .*Config.* pattern
config := DatabaseConfig{}
// OK: matches .*Options.* pattern
opts := ServerOptions{}
// ERROR: doesn't match any pattern
user := UserData{}
_ = config
_ = opts
_ = user
}
In order to avoid unnecessary noise, when dealing with non-pointer types returned along with errors - exhaustruct
will
ignore non-error types, in case return statement contains non-nil value that satisfies error
interface.
package main
import (
"errors"
)
type Shape struct {
Length int
Width int
}
func NewShape() (Shape, error) {
return Shape{}, errors.New("error") // will not raise an error
}
type MyError struct {
Err error
}
func (e MyError) Error() string {
return e.Err.Error()
}
func NewSquare() (Shape, error) {
return Shape{}, &MyError{Err: errors.New("error")} // will not raise an error
}
func NewCircle() (Shape, error) {
return Shape{}, &MyError{} // will raise "main.MyError is missing field Err"
}