Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: Go 2: operator to cause early function return on error #32601

Closed
klaidliadon opened this issue Jun 13, 2019 · 8 comments
Closed

proposal: Go 2: operator to cause early function return on error #32601

klaidliadon opened this issue Jun 13, 2019 · 8 comments
Labels
error-handling Language & library change proposals that are about error handling. FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Milestone

Comments

@klaidliadon
Copy link

klaidliadon commented Jun 13, 2019

Proposal

This proposal aims to reduce the boilerplate of basic error handling by introducing a new identifier.

We will use ? in this proposal, which will behave as follows: when a error is assigned to ?
the function would return immediately with the error received.

As for the other variables their values would be:

  • if they are not named zero value
  • the value assigned to the named variables otherwise

The use of ? would allow to use += and similar operators on the other value, as if ? was not there.
When ? receives an error the last value of the named value would be returned.

Examples

Anonymous

From:

func foo(path string) (io.ReadCloser, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    if _, err = fmt.Fprint(f, "foo\n"); err != nil {
        return nil, err
    }
    return f, nil
}

To:

func foo(path string) (io.ReadCloser, error) {
	f, ? := os.Open()
	_, ? = fmt.Fprint(f, "foo\n")
	return f, nil
}

Named

From:

func foo(r io.Reader) (n int, err error) {
    b := make([]byte, 1024)
    for {
        a, err := r.Read()
        n += a
        if err != nil {
            return n, err
        }
    }
}

To:

func foo(r io.Reader) (n int, err error) {
    b := make([]byte, 1024)
    for {
        n, ? += r.Read()
    }
}
@gopherbot gopherbot added this to the Proposal milestone Jun 13, 2019
@iand
Copy link
Contributor

iand commented Jun 13, 2019

This appears to be covered by #32500

@anacrolix
Copy link
Contributor

But what about wrapping errors?

@MichaelTJones
Copy link
Contributor

MichaelTJones commented Jun 13, 2019

This is pretty. I have one question about your phrasing to be certain of understanding. When you wrote:

func foo(r io.Reader) (n int, err error) {
    b := make([]byte, 1024)
    for {
        n, ? += r.Read()
    }
}

and mentioned "last value would be used" you mean this:

func foo(r io.Reader) (n int, err error) {
    b := make([]byte, 1024)
    for {
        tempN, tempE := r.Read()
        if tempE != nil {
            return n, tempE // "last" means n, not n+tempN, right?
        }
        n += tempN
    }
}

May be some debate about reserving the '?' for other uses but the code examples sure look nice.

@ianlancetaylor ianlancetaylor changed the title Proposal: errors, operator for early return of functions proposal: Go 2: operator to cause early function return on error Jun 13, 2019
@ianlancetaylor ianlancetaylor added v2 A language change or incompatible library change LanguageChange labels Jun 13, 2019
@alanfo
Copy link

alanfo commented Jun 13, 2019

I don't know whether this has been suggested before - frankly I have lost track of what has been suggested in the error handling saga! - but I agree with @MichaelTJones that the syntax is easy on the eye and it would be backwards compatible as ? is not currently used for any other purpose.

As far as decorating errors is concerned, you could use defer as in the try proposal (#32437) which would require the error return parameter to be named.

However, I still prefer the try built-in proposal for the following reasons:

  1. You wouldn't be able to use ? more than once in the same line.

  2. You'd always need an assignment statement even if you weren't interested in storing the results. So instead of :

    r := try(os.Open(src))
    defer r.Close()

    w := try(os.Create(dst))
    defer func() {
        w.Close()
        if err != nil {
            os.Remove(dst)
        }
    }()

    try(io.Copy(w, r))
    try(w.Close())

you'd have to write:

    r, ? := os.Open(src)
    defer r.Close()

    w, ? := os.Create(dst)
    defer func() {
        w.Close()
        if err != nil {
            os.Remove(dst)
        }
    }()

    _, ? := io.Copy(w, r) // assignment statement needed here
    ? := w.Close()        // and again here
  1. There would be no way to extend the proposal to support, say, named error handlers as you could do with try by simply adding a second parameter.

@networkimprov
Copy link

This is one of the most common suggestions in the responses to check/handle:
https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback#recurring-themes

I filed a sophisticated variant of this in #32500, and another in #27519, both of which add named handlers -- also a common request, documented in the link above.

Many folks, including Go team members, aren't keen to introduce a new symbol. I don't get why, as this is a clear way to simplify error checks without

  • burying them in the RHS with v = try(f()), or
  • cluttering the LHS with try v = f()

And it's not confined to type error nor the last return value.

@ianlancetaylor
Copy link
Contributor

For what it's worth, I think @griesemer addressed the concerns with adding new symbols in https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md :

Go has been designed with a strong emphasis on readability; we want even people unfamiliar with the language to be able to make some sense of Go code (that doesn’t imply that each name needs to be self-explanatory; we still have a language spec, after all). So far we have avoided cryptic abbreviations or symbols in the language, including unusual operators such as ?, which have ambiguous or non-obvious meanings.

@alanfo
Copy link

alanfo commented Jun 13, 2019

Ignoring the arguments for and against, when I compare the snippets in my previous post, I still feel more comfortable with try rather than ?. The latter somehow seems more obtrusive when there are a lot of them though I guess that's just a matter of taste.

@networkimprov
Copy link

Go is nicer to read than Rust or Bash. But it did introduce 3 operators and a dedicated symbol:
.() <- := _
They aren't cryptic when you know them.

This would add a dedicated symbol, for a case more common than any of the above.

The only functional complaint I've heard is that it doesn't provide a subexpression. However, the fact that try() does has provoked tremendous angst :-)

IMHO, a reasonable case for a subexpression is g(try shouldNotFail(arg)) where an error yields a panic. Or similarly, where an error is explicitly ignored.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
error-handling Language & library change proposals that are about error handling. FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

9 participants