Skip to content

proposal: fmt: add deferred/lazy formatting support to Errorf #76783

@vsaljooghi

Description

@vsaljooghi

Proposal Details

Hello,

I have a custom error type defined as follows:

type ErrorInfo struct {
	ErrCode int
	Err     error
}

func NewErrInfo(errCode int, err error) *ErrorInfo {
	return &ErrorInfo{
		ErrCode: errCode,
		Err:     err,
	}
}

func (ei *ErrorInfo) Error() string {
	if ei.Err == nil {
		return fmt.Sprintf("error code (%d)", ei.ErrCode)
	}

	return fmt.Sprintf("%s (%d)", ei.Err.Error(), ei.ErrCode)
}

This *ErrorInfo object is typically created in a low-level function with only the ErrCode set. The actual human-readable error corresponding to ErrCode is assigned later in a high-level function.

The workflow is:

  1. A low-level function creates a *ErrorInfo with just the error code.
  2. This object is wrapped in an error using %w and propagated up through intermediate functions, which may enrich the error.
  3. In the high-level function, I use errors.As() to locate the original *ErrorInfo inside the wrapped error chain.
  4. Once found, I modify the object (for example, mapping ErrCode to a human-readable error and setting ErrInfo.Err).
  5. I then want to print the top-level error message so it reflects the updated *ErrorInfo.

The problem with using fmt.Errorf() directly is that it constructs the error string immediately. If the *ErrorInfo is modified later, these changes are not reflected in the printed message.

Workaround: lazy error formatting
To solve this, I created a lazyError wrapper that defers formatting until Error() is called:

type lazyError struct {
	format string
	args   []interface{}
}

func (le *lazyError) Error() string {
	return fmt.Errorf(le.format, le.args...).Error()
}

func (le *lazyError) Unwrap() []error {
	err := fmt.Errorf(le.format, le.args...)

	switch x := err.(type) {
	case interface{ Unwrap() error }:
		return []error{x.Unwrap()}
	case interface{ Unwrap() []error }:
		return x.Unwrap()
	default:
		return nil
	}
}

func Errorf(format string, args ...interface{}) error {
	return &lazyError{
		format: format,
		args: args,
	}
}

it would be great if fmt.Errorf() do lazy formatting out of box

Best Regards,
Vahid

Metadata

Metadata

Assignees

No one assigned

    Labels

    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolProposal

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions