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: ? : for returning on function errors #65579

Closed
1 of 4 tasks
tsukinoko-kun opened this issue Feb 7, 2024 · 5 comments
Closed
1 of 4 tasks

proposal: Go 2: ? : for returning on function errors #65579

tsukinoko-kun opened this issue Feb 7, 2024 · 5 comments
Labels
error-handling Language & library change proposals that are about error handling. LanguageChange Proposal Proposal-FinalCommentPeriod v2 A language change or incompatible library change
Milestone

Comments

@tsukinoko-kun
Copy link

Go Programming Experience

Experienced

Other Languages Experience

Rust, TypeScript, C, C++, C#

Related Idea

  • Has this idea, or one like it, been proposed before?
  • Does this affect error handling?
  • Is this about generics?
  • Is this change backward compatible? Breaking the Go 1 compatibility guarantee is a large cost and requires a large benefit

Has this idea, or one like it, been proposed before?

Yes

#65266

No pattern matching. Special error syntax for the error type.

Does this affect error handling?

Yes

Code focuses on the happy path. Code gets smaller. Error handling is still there.

We replace

func foo() (*Data, error) {
	var data *Data
	data, err := getData()
	if err != nil {
		return errors.Wrap(err, "failed to get data")
	}
	doSomething(data)
	return data, nil
}

with

func foo() (*Data, error) {
	data := getData() ? err : nil, errors.Wrap(err, "failed to get data")
	doSomething(data)
	return data, nil
}

Is this about generics?

No

Proposal

Add an operator that behaves similar to the ? for Rust Results.
If an expression contains an error that is not nil, it should wrap the error with a message and return it.
Or skip the wrapping and just return the error if no wrap message is specified.
Tools like gopls have to check if this is possible.

Language Spec Changes

No response

Informal Change

No response

Is this change backward compatible?

Yes

Now:

func foo() (*Data, error) {
	var data *Data
	data, err := getData()
	if err != nil {
		return errors.Wrap(err, "failed to get data")
	}
	doSomething(data)
	return data, nil
}

New

func foo() (*Data, error) {
	data := getData() ? err : nil, errors.Wrap(err, "failed to get data")
	doSomething(data)
	return data, nil
}

Some can still use the old syntax

Orthogonality: How does this change interact or overlap with existing features?

No response

Would this change make Go easier or harder to learn, and why?

Earier. Some people complain about the big parts of Go code being error handling. This could solve this while still keeping how error handling is done in Go.

Cost Description

Syntax change leads to changes in all tools that deal with Go Syntax. I don't think there is any runtime cost because it's basically syntactic sugar.

Changes to Go ToolChain

vet, gopls, gofmt

Performance Costs

No runtime cost. Can't tell about compile time but shouldn't be noticable.

Prototype

No response

@tsukinoko-kun tsukinoko-kun added LanguageChange Proposal v2 A language change or incompatible library change labels Feb 7, 2024
@gopherbot gopherbot added this to the Proposal milestone Feb 7, 2024
@ianlancetaylor ianlancetaylor added the error-handling Language & library change proposals that are about error handling. label Feb 7, 2024
@isgj
Copy link

isgj commented Feb 11, 2024

IMHO it will be hard to come with a reasonable construct that handles and map the error that is clearer than if err != nil {// map it and possibly return it}.

However there are situations where you don't want to do anything with the error, but return it. Ex: if getData in the proposal already adds the context info to the error it returns, add stack trace and so on, you might just want to return it. This could be

func foo() (*Data, error) {
	data := getData()?           // or getData()! or something else 
	doSomething(data)
	return data, nil
}

that will be the equivalent of

func foo() (*Data, error) {
        // num of vars `getData` returns + err
        // |                                    num of vars (zero value) `foo` returns + err
        // |                                             |
        _v1, _err := getData(); if _err != nil { return nil, _err }; data := _v1
	doSomething(data)
	return data, nil
}

Basically the new syntax would be used only when you write ..., err := somethingThatErrors(); if err != nil { return ... ,err }
Most probably something like this has already been proposed, or is a comment to one of many proposals, sorry for repeating.
I agree with the title of this issue, but not with what is proposed.

(I'd like a proposal with error-non-handling tag)

@seankhliao seankhliao changed the title proposal: Go 2: Syntax for exiting function when error occurs proposal: Go 2: ? : for returning on function errors Feb 11, 2024
@apparentlymart
Copy link

I'm understanding this proposal as adding a new ternary operator that takes three operands:

  1. An expression whose final result value is of type error.
  2. A symbol name that acts as a local variable of type error in the third operand.
  3. An expression whose results are assignable to the return values of the enclosing function.

The evaluation process would then be:

  1. Evaluate the expression in operand 1.
  2. If the final error result from that expression is nil, immediately return all of the other return values (if any), discarding the final error one, and terminate.
  3. Otherwise, assign the error result to the symbol given in operand 2, and then evaluate the expression in operand 3 with that symbol in scope. Assign its results to the function return values, and then immediately return from the enclosing function.

Please correct me if I misunderstood. 😀 The rest of what I wrote in this comment probably won't make sense if I didn't understand correctly.


This does seem plausible in principle, but I think my main reservations are about the specific syntax you chose here:

  • It resembles the ternary conditional operator in C and many of its descendants, but has a very different meaning.

    Since Go is quite "C-like" in various other ways, I expect someone new to Go but familiar with another language in the C family would initially assume this was a ternary conditional, rather than assuming it's a new language construct that they need to learn about.

  • I can't think of other places in the language where a sequence like symbol : expr introduces a local scope with an additional symbol inside it. That doesn't necessarily mean this couldn't be the first, but I think it would be preferable for this to resemble something else that's already familiar in the language, and ideally something that makes it clear which parts of the program have that err symbol in scope.

    One possible alternative could be: expr ? (err) { return expr }, which I wrote to resemble the function declaration syntax but without the func in front. However, this form does seem to imply that the part in { } is a normal block, in which case it would also support multiple statements and support not returning, which makes this considerably more general than your proposal.

    data := getData() ? (err) {
        return nil, errors.Wrap(err, "failed to get data")
    }

    If I were reviewing what I just proposed, I would immediately become skeptical about how much of an improvement it is over the original if statement approach -- it only really saves one line, now -- so I don't think this alternative is super compelling. Maybe others have better ideas.

A further random question, that doesn't really fit in the above list:

  • How does this generalize for functions that don't return an error, or even weirdo functions that might return more than one error for some reason?

    One way I could imagine specifying it is that the second operand can have multiple symbols separated by commas, up a maximum of the number of return values from the expression in operand 1. Each declared symbol would consume one of the trailing return values from the expression, and so the result of the operand in the non-error case is whatever results are left after all that consuming.

    Concretely: foo() ? a, b : ... would work as long as foo has at least two return values. If it has exactly two return values then this overall expression returns no results. If it has three return values then the overall expression has one result -- the first one -- and the other two get assigned to a and b respectively.

    However, this of course now complicates the matter of what the implied condition is for deciding whether to return or continue. Does it return early unless all of the specified symbols have their zero value assigned to them?

    It might also be reasonable to say that it's a compile-time error if the final return value isn't of type error, and that only one symbol is allowed and it's always of type error, and that in all other cases authors must use the traditional explicit if statement. That's not really an orthogonal design, but I do accept that any part of the language that's focused specifically on error handling is likely to be non-orthogonal, and indeed it's the classic debate in all of these proposals about whether the language should have non-orthogonal features focused specifically on error handling, rather than treating error handling as "normal code".

@WeetHet
Copy link

WeetHet commented Feb 24, 2024

I still like the try getData() syntax much more as we don't have that many postfix operators aside from ++

@ianlancetaylor
Copy link
Contributor

Based on the discussion above, and the emoji voting, this is a likely decline. Leaving open for four weeks for final comments.

@ianlancetaylor
Copy link
Contributor

No further comments.

@ianlancetaylor ianlancetaylor closed this as not planned Won't fix, can't repro, duplicate, stale Apr 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
error-handling Language & library change proposals that are about error handling. LanguageChange Proposal Proposal-FinalCommentPeriod v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

6 participants