golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.19k stars 17.57k forks source link

proposal: Go 2: introduce "then" keyword in if statement to allow one liner if statement for better error handling. #46717

Closed ibudisteanu closed 3 years ago

ibudisteanu commented 3 years ago

Would you consider yourself a novice, intermediate, or experienced Go programmer? Experienced

What other languages do you have experience with? C, C++, Javascript, Java, Python, Delphi, Rust

Would this change make Go easier or harder to learn, and why? I don't think it makes the GO harder to learn. The proposed "then" keyword allows the one line if statement

Has this idea, or one like it, been proposed before? The solution is similar with the most other error handling proposals. This proposal doesn't add any other code changes

If so, how does this proposal differ?

Who does this proposal help, and why? **1. It helps authors of Go source code avoid three extra lines of code in the common case where they early exit on an error.

  1. It helps readers of Go source code follow a function's core logic more easily, by collapsing the gap between two successive lines of logic.
  2. Preserves the imperative style of Go source code. Unlike the try proposal, this proposal can't be abused to favor function composition.**

What is the proposed change? **1. A new keyword "then" for the if statement is introduced.

  1. The keyword is only valid in if statements on the right-hand-side of the if statement allowing one line if statements.
  2. After the keyword a statement is required but it can be a block as well.
  3. In all other cases, check is a normal identifier. This keeps the change backwards-compatible to Go1.
  4. Given a function like this:**
func foo() (v1 T, err error) {
  var x T
  if x,  err = f(); err != nil then return
}
is equivalent to the following code:

func foo() (v1 T, err error) {
  var x T
  if x, err = f(); err != nil {
    return nil, err
  }
}

Is this change backward compatible? Yes. Totally!

Show example code before and after the change. Before https://play.golang.org/p/bQeCE9zzDPa

func printSum(a, b string) (err error) {
        var x,y int
    if x, err = strconv.Atoi(a); err != nil {
        return
    }
    if y, err = strconv.Atoi(b); err != nil {
        return
    }
    fmt.Println("result:", x + y)
    return nil
}

After using the "then" keyword

func printSum(a, b string) (err error) {
        var x,y int
    if x, err = strconv.Atoi(a); err != nil then return
    if y, err = strconv.Atoi(b); err != nil then return
    fmt.Println("result:", x + y)
    return nil
}

What is the cost of this proposal? (Every language change has a cost). None. Just a regex parse.

How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected? I guess it requires only one regex expression.

What is the compile time cost? Time-complexity-wise the same. Can be implemented entirely in the frontend.

What is the run time cost? Nothing

Can you describe a possible implementation? It is just another regex in the language parser

Does this affect error handling? Yes

We are working on a go project. We have about 18,000 LOC. about 18% of the LOC are just error handling... Error handling in GO is a nightmare because of the required brackets and code format.

It can also be handy in other parts of the code

func (wallet *Wallet) loadWallet() error {

   if _, err := os.Stat("wallet.txt") ; err == nil {
        if err := wallet.read(); err != nil { 
           return err 
       }
        if wallet.encrypted {
           if err := wallet.decrypt(); err != nil {
             return err
          }
       } 

   } else {
       if err := wallet.createAddress(); err != nil then return err
   }

   if wallet.addresses == 0 {
         errors.new("There are no addresses in the wallet")
   }
   return nil
}
func (wallet *Wallet) loadWallet() error {

   if _, err := os.Stat("wallet.txt") ; err == nil {
        if err := wallet.read(); err != nil then return err
        if wallet.encrypted then
           if err := wallet.decrypt(); err != nil then return err     
   } else then
       if err := wallet.createAddress(); err != nil then return err

   if wallet.addresses == 0 return errors.new("There are no addresses in the wallet")

   return nil
}
ibudisteanu commented 3 years ago

The proposed solution reduces dramatically the entire code that a developer has to write, as there are many cases when one line if statement is required.

davecheney commented 3 years ago

What if we fixed go fmt so you could write

if x, err = strconv.Atoi(a); err != nil { return }

It's two characters shorter than than, if that's important.

ibudisteanu commented 3 years ago

The issue i have is that the IDE i am using is automatically formatting all the if statements and is also folding by expanding the blocks most of the time. The issue I believe is the number of lines of code rather the 2 extra characters. Ideally would be "?"

davecheney commented 3 years ago

Right, but if we’re talking bout chasing the language, something on the table for comparison should be changing go fmt to permit trivial if blocks to stay on the same line.

jfesler commented 3 years ago

Whatever change is made (if at all), the IDE will have to change. So arguments about the IDE doing it wrong are moot.

ibudisteanu commented 3 years ago

I was just pointing that I believe most people including me think GO doesn't have proper ways to handle errors is mostly because GO requires brackets in if statements and IDEs are automatically expanding these blocks most of the time.

seankhliao commented 3 years ago

I don't see how this is significantly different from a single line if, other than being more complex #27135 and others

ianlancetaylor commented 3 years ago

This has some similarities to #38151. Also to #32946.

ibudisteanu commented 3 years ago

My proposal was that using the proposed keyword "then" you can solve most error handling in a single line. Also for integrating this proposal I believe it requires adding one single regex expression for parsing the word "then" and expecting an instruction.

Most likely the community will fork golang and create a golang compiler that allows one liner as you guys simply don't want this. Something like a simpler version of Go+

urandom commented 3 years ago

@davecheney

You can't. That would cause huge swaths of unnecessary changes for any commit

b97tsk commented 3 years ago

Maybe change gofmt to allow following to stay on the same line:

if err != nil {{ return }}

Or when there is a comment:

if err != nil { return } //gofmt:inline
ibudisteanu commented 3 years ago

how about if err != nil then return

@89z everybody hates the golang 3 liner error handling in big projects

ibudisteanu commented 3 years ago

I wrote about 16k LOC in golang in the last 2 months. I enjoy the language but the error handling is extremely verbose. I even looked for golang transpiler on the web. I found this https://github.com/lunixbochs/og

https://github.com/PandoraPay/go-pandora-pay

Regarding your examples, the "then' keyword would solve most of the issues above. Check this out.

re := regexp.MustCompile("some (pattern)")
if find := re.FindStringSubmatch("some input"); err == nil then  return find[1], nil
else then return "", errors.New("not found")
func blah() (*os.File, error) {
   if f, err := os.Open("something"); err == nil then return f, nil
   else then  nil, fmt.Errorf("call os.Open in func blah: %v", err)
}

Because I find golang very verbose, It makes me believe it is intended for very small projects, like scripts, or small servelet.

LE2: As I mentioned before, doing some code analysis, I found that about 18% of the LOC are the "infamous 3 line error handling". Taking in consideration the 3 line error handling is actually 3 lines it means that about 40% of the code is just error handling ... It means, there is something wrong with the golang three liner error handling

ibudisteanu commented 3 years ago

@89z i just want to have a one liner error handling. I totally believe that something must be done. I proposed one option "then" keyword. I don't care how it can be done we want one liner error handling :D .

ibudisteanu commented 3 years ago

i@89z that is not one liner error handling. The IDE I am using is unfolding by expanding these blocks most of the time. Also when committing on github, the LOC are expanded again. As far as I understood it is the gofmt that is doing this.

func two(name string) (f *os.File, err error) {
   if f, err = os.Open(name); err != nil { return }
   return f, nil
}

also this approach with if f, err = func(); err != nil {return}

it is not that great because in case func() is also returning some value and error, the value is being propagated

ibudisteanu commented 3 years ago

I don't know how to disable gofmt in goland. If you tell me then I will disable it and I will never activate gofmt again. I only see that you guys received over 50+ proposals in all these years for different one liner error handling and all of them were refused for one reason or another.

To illustrate why gofmt is bad in practice... Just a random thing I was working on right now... and the reason why a real world one liner if statement and error handling is really useful.

select {
case newWork, ok := <-continueProcessingCn:
    if !ok {
        return
    }
    if newWork != nil {
        work = newWork
        listIndex = 0
        txMap = make(map[string]bool)
    }
case _, ok := <-suspendProcessingCn:
    if !ok {
        return
    }
    continue
}
if work == nil {
    continue
}
if len(txList) > 1 {
    sortTxs(txList)
}
select{
case newWork, ok := <- continueProcessingCn:
    if !ok then return
    if newWork != nil {
        work = newWork
        listIndex = 0
        txMap = make(map[string]bool)
    }
}
case _, ok := <- suspendProcessingCn:
    if !ok then return
    continue
}
if work == nil then continue
if len(txList) > 1 then sortTxs(txList)

22 LOC vs 15 LOC. A reduction of 31.81% LOC

fzipp commented 3 years ago

@ibudisteanu Less lines of code do not mean more readable. You can write the whole program in one line if you insert the semicolons. gofmt was designed under the premise that your 22 LOC version is preferable to the 15 LOC version.

ibudisteanu commented 3 years ago

@fzipp I honestly believe that the 2nd option with 30% less lines of code is more readable that the 1st option especially when you have large projects.

fzipp commented 3 years ago

@ibudisteanu Your variant tucks away what is, in my opinion, the most important information of a function body: where does the control flow change, what are the edge cases.

ianlancetaylor commented 3 years ago

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

ianlancetaylor commented 3 years ago

No further comments.

hauntedness commented 2 years ago

looks lots of people and I want a one line format for if err. curious that the community allow one line function but not allow one line if statement. with full respect to this decision, I spent 2 days and found below workaround with vscode go. 1. turn off default gofmt by "[go]": { "editor.formatOnSave": false, }, 2. thanks for the popular formatter https://github.com/mvdan/gofumpt. I forked and make it able to format if statement into one line
https://github.com/hauntedness/gofumpt my fork only allow merging if statement in case:

or there is another formatter allow one line if statement https://github.com/mbenkmann/goformat. 3. install https://marketplace.visualstudio.com/items?itemName=emeraldwalk.RunOnSave and config format for go "emeraldwalk.runonsave": { "commands": [ { "match": "\.go$", "isAsync": true, "cmd": "gofumpt -l -w ${file}" }, ] },