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: errors: allow function on left hand side of assignment #52416

Closed
switchupcb opened this issue Apr 18, 2022 · 27 comments
Closed

proposal: Go 2: errors: allow function on left hand side of assignment #52416

switchupcb opened this issue Apr 18, 2022 · 27 comments
Labels
error-handling Go2 LanguageChange Proposal Proposal-FinalCommentPeriod
Milestone

Comments

@switchupcb
Copy link

@switchupcb switchupcb commented Apr 18, 2022

Backwards compatibility should be a function of a previous design's quality. It should NOT be a function of adherence to backwards compatibility. Go 2 does not promise to be compatible with Go 1: While compatibility is nice to have, it isn't required. Change is inevitable. So why are we limiting ourselves on this specific issue? Luckily, the answer to that question does NOT matter for this proposal. It's backwards compatible and boomer approved.

With that out of the way...

Proposal

You want error handling ideas. Yet restriction is bad for creativity. This proposal is unhinged. I might not even like it... but I'm making it anyways. Perhaps, my outlook will change. Perhaps it won't and I will regret this decision for the rest of my life. All I know is that it isn't a duplicate.

An error is a value in Go, so anything that is applicable to errors should also be applicable to values; except panic and recover I guess... If we are going to use a function, it is either a built-in or keyword; none of this top of the page, 4th dimension, quantum realm nonsense.

People want to be able to return if not nil, wrap errors, and feed their families, all with a single error feature. Now, I'm not gonna lie: This one might leave lil timmy a lil hungry. Expecting a single feature to handle errors is like expecting the language to work without if statements and... Let's not get ahead of ourselves here. This proposal will meet the requirements.

At this point, I know you have your tongue out panting like a dog in front of your computer. heh heh heh. Otherwise, you are NOT amused right now. You may be asking yourself if this post even has a proposal in the first place. It does. I just needed to make it longer so it seems more sophisticated than the other ones. So without further ado...

Add the ability to accept functions in place of a value - or only error values - if it isn't equivalent to its zero value.

func bezo(baller, dollers string) error {
    bucko, (err) := strconv.Atoi(baller) // returns err if not nil
    bucks, _ := strconv.Atoi(dollers)
    fmt.Println("$:", bucko + bucks)

    ballin, fmt.Errorf("error: balled too hard\n%w", err) := isBaller(bucko + bucks, 2147483647) // returns err if not nil
    if ballin {
        take50(err) := MakeMackenzieHappy() // returns err (cause not nil)
    }
    return nil
}

For the MENSA members out there.

func musk(money string) (bool, error) {
    if money == "long" {
        money = "longer"
    }

    sec, fmt.Tweet(err) := findSEC() // returns false, fmt.Tweet(err) error if not nil
    RunFrom(sec)
    return false, nil
}

READ THE FOLLOWING FOR CONTEXT BEFORE COMMENTING
#52416 (comment)
#52416 (comment)
#52416 (comment)
#52416 (comment)

Potential Supporters

@ianlancetaylor Due to my flow control.
@networkimprov he did it. he solved go errors.

Similar Proposals (Not Duplicates)
#21732 @faiface +8 -8
idek @lldld
#14066 @gertcuykens
#46655 @smasher164

Symbolic Proposals
#50207 @misaka123456
#42214 @kidlj
#32884 @integrii @alanfo
#42318 @be-impl +18 -19 @sirkon
#36390 @cmidt-veasna -5

Implications: #52415
More Implications: #52380

Downvote Offset: #52416 (comment)

@gopherbot gopherbot added this to the Proposal milestone Apr 18, 2022
@ianlancetaylor ianlancetaylor added LanguageChange Go2 error-handling labels Apr 18, 2022
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Apr 18, 2022

If I'm following this, you are suggesting that we permit functions on the left hand side of an assignment. If we assign a non-nil value to a function, then we instead call the function, and then we return the result of calling the function from the caller.

In this example:

    ballin, fmt.Errorf("error: balled too hard\n%w", err) := isBaller(bucko + bucks, 2147483647)

where is err declared?

This seems pretty similar to #32473.

@ianlancetaylor ianlancetaylor added the WaitingForInfo label Apr 18, 2022
@efronlicht
Copy link

@efronlicht efronlicht commented Apr 18, 2022

I don't understand why this has so much weird meme-ish language. It makes it difficult for me to follow what you are talking about. Can you please cut out the noise and restate your proposal as simply as possible?

@switchupcb
Copy link
Author

@switchupcb switchupcb commented Apr 18, 2022

If I'm following this, you are suggesting that we permit functions on the left hand side of an assignment. If we assign a non-nil value to a function, then we instead call the function, and then we return the result of calling the function from the caller.

@ianlancetaylor You explained it! The #32473 is similar but his example is a bit complicated. It shouldn't be much more complicated than what you said. Also, we should NOT allow keywords such as return, break on the LHS.

Th err is declared in the function on LHS.

@switchupcb
Copy link
Author

@switchupcb switchupcb commented Apr 18, 2022

@efronlicht I have a broken keyboard right now so I can't use bigger words.

@efronlicht
Copy link

@efronlicht efronlicht commented Apr 18, 2022

Is this some kind of bizarre performance art? I'll leave you to it.

@switchupcb
Copy link
Author

@switchupcb switchupcb commented Apr 19, 2022

fmt.Errof("%v, %w", a, err) := simple()

WHAT HAPPENS HERE MR SMART GUY? HUH! IS a RETURNED AS WELL?

@switchupcb
Copy link
Author

@switchupcb switchupcb commented Apr 19, 2022

What happens in any other case.

// in a func that returns string and error
if err != nil {
   return "", err
}

Your question is not an issue with any proposal but rather the way Go handles values. Are other values reliable or not when an error is returned? _ implies that they can be. However, when that is the case, you have ignored the error explicitly (with _), implying that the other value is useful.

The difference here is that we are not USING variables but returning them. The reason you use the zero value for everything other than error is because we can't assume that the func wrap returns the result. As an example:

func leet() (string, error) {
     // a is a bool, not a string
     // err is an error, which fmt.Errorf returns
    fmt.Errof("%v, %w", a, err) := simple() // return "", err when err != nil

    return "code", nil
}

There is one significant drawback to [the current] approach, at least for some applications: there is no way to know how much of the processing completed before the error occurred. If that information is important, a more fine-grained approach is necessary. Often, though, an all-or-nothing check at the end is sufficient.
@robpike

To be honest, keeping errors the same isn't that bad. Google has like 20000 programmers so 3 lines shouldn't be a big deal. Google can adopt trio-programming or require more leetcode during interview. The industry will catch on eventually.

EDIT: panic and recover can be used on all values making it consistent. So the question becomes whether we limit assignment funcs to a single error and returning zero values for everything else.

@switchupcb
Copy link
Author

@switchupcb switchupcb commented Apr 19, 2022

@efronlicht @smasher164 @solumn @kidlj @sirkon @l-zeuch @jimen0 @generikvault @zxr90 I used big words.

alt-73 alt-32 alt-117 alt-115 alt-101 alt-100 alt-32 alt-98 alt-105 alt-103 alt-32 alt-119 alt-111 alt-114 alt-100 alt-115

@sirkon
Copy link

@sirkon sirkon commented Apr 19, 2022

Stop mention me please.

This proposal is all about LOC reduction at the cost of line width and readability. I don't see how it solves real issues of Go error handling, some of the are:

  1. No type system support for mandatory error handling. I mean you can write like this and will get NPD in the end:

     file, err := os.Open(filename)
     if err != nil {
         fmt.Println(err)
     }
    
     filedata, err := io.ReadAll(file)
     …
    
  2. Some kind of "instability" of error handling:

     if err := f(x); err != nil {
         …
     }
    

And this fragment must be rewritten completely if a new return parameter is added:

     res, err := f(x)
     if err != nil {
         …
     }

Back to your proposal:

  • It doesn't solve No 1
  • It will make kind of rewrites of No 2 straightforward in simplest cases where it just returns raw or wrapped error. In the same time it will introduce a more annoying kind of literal "instability" where you need to extend error processing logic.

@switchupcb
Copy link
Author

@switchupcb switchupcb commented Apr 19, 2022

@sirkon Why would that need to be rewritten and where is a new return parameter being added? What is NPD?

I think it can solve 1 but that would imply making the declaration local in the call (err) which is not the best.

It will make kind of rewrites of 2 in simplest cases where it just returns raw or wrapped error.

Yes.

People want to be able to return if not nil, wrap errors...


In this code:

 file, err := os.Open(filename)
 if err != nil {
     fmt.Println(err)
 }

 filedata, err := io.ReadAll(file)

Open the file, print the error (if it is there), then read it. What is the issue with this code? Why can't you add a return?

Also, we can't add type system support for mandatory error handling because error handling isn't mandatory via _ unless you are proposing another error type that requires you to handle your errors. I haven't thought about the implications of that but we should consider that in another proposal.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Apr 19, 2022

@switchupcb Please follow the Go Community Code of Conduct. Please be polite and respectful in your comments on this issue tracker.

@switchupcb
Copy link
Author

@switchupcb switchupcb commented Apr 19, 2022

Yes, but that is the actual question. I don't know what NPD is or where a return parameter is added. I can't understand what he is saying.

@ianlancetaylor ianlancetaylor removed the WaitingForInfo label Apr 19, 2022
@ianlancetaylor ianlancetaylor changed the title proposal: errors: func wrap proposal: Go 2: errors: allow function on left hand side of assignment Apr 19, 2022
@sirkon
Copy link

@sirkon sirkon commented Apr 19, 2022

We have two main forms of error processing right now:

if err := …; err != nil {
    …
}
v1, …, vN, err :=if err != nil {
    …
}

This is already too much. You propose the third way.

No, thanks.

@switchupcb
Copy link
Author

@switchupcb switchupcb commented Apr 19, 2022

@sirkon Well, if it's an issue from 58% of Gophers, we need to make proposals on it. I think you bring up yet another possible idea: remove a method of handling errors to standardize the language entirely. This isn't backwards compatible but it may have benefits that outweigh the current way of doing things.

@switchupcb
Copy link
Author

@switchupcb switchupcb commented Apr 19, 2022

@sirkon has got me thinking here. At first I didn't like this idea. However, treating errors as a special value is probably justified by the existence of panic and recover. The question he is proposing is whether this proposal is consistent with the current two methods of handling errors. Well, he isn't proposing the question literally but it's not like we will remove generics because we can use interfaces.

Two main forms

v1, v2, vN, err := simple()
if err != nil {
    return err
}
if err := simple(); err != nil {
   return err
}

This proposal implements:

v1, v2, vN, (err) := simple()

Alternatively.

v1, v2, vN, fmt.Errof("balled too hard\n%w", err) := simple()

This proposal could (but in my opinion, should NOT) allow:

if v1, v2, vN, (err) := simple(); ... {
    ...
}

Alternatively (not necessary in my opinion; just do 3 with a standalone if line).

if v1, v2, vN, fmt.Errof("balled too hard\n%w", err) := simple(); ... {
    ...
}

Feel free to go against my opinion, though.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Apr 19, 2022

This is an implicit statement: panic and recover can only be used on error values.

I just want to note that this is not true of Go today, and making it work that way would be an incompatible change. Code can call panic with a value of any type, and recover will return whatever value is passed to panic.

@switchupcb
Copy link
Author

@switchupcb switchupcb commented Apr 19, 2022

@ianlancetaylor Fixed! With this determined, there is one more thing we must consider before submitting this proposal (which is NOT the same as approving it).

Justified by the consistency of panic and recover, this feature could be used on other values.

string(a), err := why() // where a being 123 returns "123", nil

This would then justify the following:

func woe() (string, error) {
    // where a being 123 returns "123", nil
    // where err being not nil returns "", err
    // where both being zero or nil doesn't return
    string(a), (err) := why()

    // Alternatively.
    string(a), b, fmt.Errof("balled too hard\n%w", err) := why()
    return a, nil
}

This is too ambiguous and contradictory. I don't like the ability to use functions on the values that aren't error. So this proposal is only justified if it makes sense to only allow this on error types; which implies that they are a special type of value. What are your thoughts?

@switchupcb
Copy link
Author

@switchupcb switchupcb commented Apr 19, 2022

Also, the example in #52416 (comment) implies that we only return if err is not nil, cause otherwise you run into the issue above.

@switchupcb
Copy link
Author

@switchupcb switchupcb commented Apr 19, 2022

An additional concern I'd have it explicitness. This proposal simplifies the use of the error that one line. It says "do something here with the error" but then (similar to _) requires you to know what that thing is: With any of these proposals, you have to know what calling a function or keyword does.

I think something like #37141 is more explicit but overall carries the same functionality. Compared to #37141, this proposal ends up being a similar idea, but the difference is that #37141 adds a keyword (pass, check, whatever) and an additional line of code to declarations. This maintains explicitness and prevents the usage of the new feature in if statements. So at the current time, I like #37141 better than this proposal unless you are seeing this proposal in a different way.

a, err := why()
check err // or pass fmt.Errorf etc

The only issue with #37141 (in a similar manner to this proposal) is it ideally limits the functionality to errors.

@hummerd
Copy link

@hummerd hummerd commented Apr 21, 2022

Just a syntax proposal

   ballin, err := isBaller(bucko + bucks, 2147483647)
   return! fmt.Errorf("error: balled too hard\n%w", err)
  • Returns expr at right of ! if err not nil and default value for any other return values.
  • Still explicit return (behavior for other returned values is quite common and expected - as for me at least)
  • Does not overloads one line with a, fmt.Errorf(...) := call(toSmth) a call and error processing
  • It is just like macro if err != nil { return nil, ..., expr(err) }, with minimum magic here

@acehow
Copy link

@acehow acehow commented Apr 22, 2022

dispite whatever it is, go team should put a better error handle on the agenda, people bored to write more if err != nil. may be you can take a look https://github.com/goplus/gop/wiki/Error-Handling . after all, they already implemented a better error handle way.

@Splizard
Copy link

@Splizard Splizard commented Apr 22, 2022

@switchupcb
One of the most common issues with error-checking proposals (including all of the ones linked in the proposal), is that they lack explicit control flow keywords. This proposal does not address this, the syntax is easy to confuse with function calls and type conversions.

@hummerd @switchupcb
Check out #39372 for an example of a fleshed out error-checking/error-passing proposal with more explicit control flow.

For comparison:

func bezo(baller, dollers string) error {
    bucko, err := strconv.Atoi(baller); err.return 
    bucks, _ := strconv.Atoi(dollers)
    fmt.Println("$:", bucko + bucks)

    ballin, err := isBaller(bucko + bucks, 2147483647); err.return fmt.Errorf("error: balled too hard\n%w", err)
    if ballin {
        err := MakeMackenzieHappy(); err.return take50(err)
    }
    return nil
}

func musk(money string) (bool, error) {
    if money == "long" {
        money = "longer"
    }

    sec, err := findSEC(); err.return fmt.Tweet(err)
    RunFrom(sec)
    return false, nil
}

@theherk
Copy link

@theherk theherk commented Apr 22, 2022

@switchupcb: With regards to

What is NPD?

It is simply shorthand for this error, Invalid memory address or nil pointer dereference, which takes place when an application tries to dereference an uninitialized pointer. I assume, but I haven't seen this abbreviation.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented May 4, 2022

Based on the discussion above, and the emoji voting, this is a likely decline.

Leaving open for four weeks for final comments.

@switchupcb
Copy link
Author

@switchupcb switchupcb commented May 18, 2022

@theherk There is no way his code could dereference unless it is wrong. His whole point was not changing errors in the first place because we have two ways to handle them.

This is already too much. You propose the third way.

I'm not sure if he read the proposal because of the backwards compatibility misconception, but it doesn't matter. He is an if err != nil maxi.

He's right though. Vim users only have like 80 characters for width and this would result in code with lots of 80+ character lines.

@theherk
Copy link

@theherk theherk commented May 18, 2022

@switchupcb I'm not disputing any of that, nor making any statement on the proposal; just clarifying.

@atdiar
Copy link

@atdiar atdiar commented May 20, 2022

@switchupcb

Instead of using an anonymous function, you might have wanted to argue for allowing error values to be assignable to error functions, which would be any function with an error argument.
Not sure it is tractable but it is an interesting idea conceptually. Something like:

func SomeFn() (any,error){ return ... }
func AnotherFn() error{ return ... }

func() error{
   var errh ErrorHandler // A weird kind of interface for error handling functions.
   errh = func(err error)error{ return err} // or something more complex

   v,errh:= SomeFn()
   errh= AnotherFn()
   fmt.Print(v)
   return nil
}

where errh would be called and its value would be returned if the error assigned to it is not nil.

This is just an exercise as I do not have issues with if err!= nil at all in Go myself.
Also it interacts a little with the shorthand variable declaration.

Maybe if you had presented the proposal a bit more professionally, people would be a bit less likely to ignore it. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
error-handling Go2 LanguageChange Proposal Proposal-FinalCommentPeriod
Projects
None yet
Development

No branches or pull requests

10 participants