Skip to content

proposal: Go 2: block scoped error handling #33161

Closed
@mvndaai

Description

@mvndaai

After reading through the problem overview the try() proposal and every comment on the try() issue #32437 I like the idea of try(), thank you everyone for such hard work! The implementations so far have not solved what I view as the issue of error boilerplate, namely the scope of the err var.

Problem

Go scopes errors using inline if statements, but does not scope errors when a variable needs to be used after.

Scoped:

if err := foo(); err != nil {
if _, err := bar(); err != nil {

Not Scoped:

r, err := bar()
if err != nil {

When the error is not scoped it is has multiple problems. The ones that come to mind are:

  • Shadowing
  • All errs need to be the same type
  • The variable existing past when handling makes sense

Proposal

I propose the language add try/handle keywords similar to what was proposed by @james-lawrence.

r := try bar() handle(err error) {
    return err
}

The try keyword would return everything but the final value. The handle block would only run if the final value was non-zero.

This proposal also solves many common complaints from the try proposal #32437:

  • Requiring a defer function
  • Requiring a named returned which some developers try to avoid
  • Implicit error handling
  • Obscured return
  • Adding complexity to error decoration

Examples

CopyFile

The CopyFile func found in the overview becomes

func CopyFile(src, dst string) error {
	r := try os.Open(src) handle(err error) {
		return err
	}
	defer try r.Close() handle(err error) {
		//handle
	}

	w := try os.Create(dst) handle(err error) {
		return err
	}
	defer try w.Close() handle(err error) {
		//handle
	}

	try io.Copy(w, r); handle(err error) {
		return err
	}
	try  w.Close(); handle(err error) {
		return err
	}
}

The main difference between current error boilerplate and using block scoped error handling are:

  • The err from os.Open(src) doesn't live for the whole scope of the function
  • The defer functions can handle their errors in the same manner as other functions

Hex

The hex example in the overview becomes:

func main() {
	hex := try ioutil.ReadAll(os.Stdin) handle(err error) {
		log.Fatal(err)
	}

	data := try parseHexdump(string(hex)) handle(err error) {
		log.Fatal(err)
	}

	os.Stdout.Write(data)
}

This is very similar to the current go except the err returned from ioutil.ReadAll isn't overwritten by the err from parseHexdump.

Inline/Curried functions

The try proposal comments included examples of nested/curried functions that caused some worry such as this AsCommit.

func AsCommit() error {
    return try(try(try(tail()).find()).auth())
}

Wrapping a try/handle in () would mean a variable never needs to be saved if it is not wanted. This case is similar to using an anonymous func func()int { ... }() and handling the error, but would allow use of other keywords like break, continue, and return.

Current Go

func AsCommit() error {
	t, err := tail()
	if err != nil {
		return err
	}
	f, err := t.find()
	if err != nil {
		return err
	}
	if _, err := f.auth(); err != nil {
		return err
	}
}

With block scoped error handling (please don't do this)

func AsCommit() error {
	(try tail() handle(err error) {
		return err
	}).(try find() handle(err error) {
		return err
	}).(try auth() handle(err error) {
		return err
	})
}

Struct Init

type foo struct {
	Value int
}

Current go

func styleA(s string) error {
	f := foo{}
	var err error
	f.Value, err = strconv.Atoi(s)
	if err != nil {
		return errors.Wrap(err, "value could not be converted")
	}
func styleB(s string) error {
	n, err := strconv.Atoi(s)
	if err != nil {
		return errors.Wrap(err, "value could not be converted")
	}
	f := foo{
		Value: n,
	}

With block scoped error handling

func styleB(s string) error {
	f := foo{}
	f.Value: try strconv.Atoi(s) handle(err error) {
		return errors.Wrap(err, "value could not be converted")
	}
func styleA(s string) error {
	f := foo{
		Value: (try strconv.Atoi(s) handle(err error) {
			return errors.Wrap(err, "value could not be converted")
		}),
	}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions