Skip to content

proposal: spec: error handling with err != nil ? return #71808

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

Closed
2 of 4 tasks
nurali-techie opened this issue Feb 18, 2025 · 8 comments
Closed
2 of 4 tasks

proposal: spec: error handling with err != nil ? return #71808

nurali-techie opened this issue Feb 18, 2025 · 8 comments
Labels
error-handling Language & library change proposals that are about error handling. LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee LanguageProposal Issues describing a requested change to the Go language specification. Proposal
Milestone

Comments

@nurali-techie
Copy link

nurali-techie commented Feb 18, 2025

Problem

The primary issue with current error handling is the repetitive nature of the error-handling code.

Particularly 3-lines of error handling code block.

Example as below,

if err != nil {
    return nil, err
}

Proposal

Let's introduce support for the ternary operator in Go and use it to simplify error handling.

Syntax:

condition ? trueValue : falseValue

Note

A separate proposal for the "Ternary operator in Go" will be required once this error-handling proposal is approved.

Let's understand how ternary operator will simplify error-handling.

Example (repetition of error handling code)

func Do(input any) (any, error) {
	result, err := doA(input)
	if err != nil {
		return nil, err
	}

	result, err = doB(result)
	if err != nil {
		return nil, err
	}

	result, err = doC(result)
	if err != nil {
		return nil, err
	}
	return result, nil
}

Example (error handling using ternary operator)

func Do(input any) (any, error) {
	result, err := doA(input)
	err != nil ? return nil, err

	result, err = doB(result)
	err != nil ? return nil, err
	

	result, err = doC(result)
	err != nil ? return nil, err : return result, nil
}
Example (error handling using ternary operator "with parentheses")

Here is a version with parentheses. This (err != nil) instead err != nil.
The decision on whether to include parentheses for condition will be determined in the ternary operator proposal.

func Do(input any) (any, error) {
	result, err := doA(input)
	(err != nil) ? return nil, err

	result, err = doB(result)
	(err != nil) ? return nil, err
	

	result, err = doC(result)
	(err != nil) ? return nil, err : return result, nil
}

Overall, 3-lines of error handling code block is converted to 1-line with the help of ternary operator.

3-lines

if err != nil {
    return nil, err
}

1-line

err != nil ? return nil, err

Also, 3-lines of error handling code block is most used in go projects. Below are details about "Kubernetes" project, which is one of the biggest Go projects.

Kubernetes error handling details
# for github.com/kubernetes/kubernetes

# number of times "if err != nil" is checked
$ cat **/*.go | pcregrep --buffer-size=1000000 -M "if err != nil {" | wc -l
41466

# number of times "if err != nil" is checked and has one line in if block
$ cat **/*.go | pcregrep --buffer-size=1000000 -M "if err != nil {\n.*\n.*}" | wc -l
110397
# above 110397 should be divided by 3 => 110397 / 3 = 36799

In kubernetes project, there are 41466 times err is checked, out of that 36799 times having 3-lines of error handling code block.

These 36799 (88% of 41466) error handling code block can use ternary operator.

Pros

Pros-1: Orthogonal

Yes, ternary operator will be orthogonal to Go language.

The ternary operator will be a general feature, allowing the replacement of any if condition that has a single-line if and else section (else section is optional).

Below if condition,

func Max(a,b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

Using ternary operator,

func Max(a,b int) int {
	a > b ? return a : return b
}

Pros-2: Backward compatible

Yes, the addition of ternary operator is backward compatible.

The existing error-handling method will remain unchanged. Users will have the option to use the ternary operator for error handling or stick with the traditional approach.

Pros-3: Error is Value

Error is Value in Go and Error will remain Value in Go with this proposal.

This proposal does not change any semantics of Go errors. Errors in Go will continue to be values.

Cons

Cons-1: Not apply to all error-handling scenario

Scenario-1

A scenario with error-handling code spanning multiple lines cannot utilise a ternary operator.

func Hello() error {
	err := World()
	if err != nil {
		metrics.Record(err)
		log.Error(err)
		return err
	}
}

To utilize the ternary operator, the error-handling code can be moved to a separate function, as shown below.

func Hello() error {
	err := World()
	err != nil ? return handleError(err)
}

func handleError(err error) error {
	metrics.Record(err)
	log.Error(err)
	return nil, err
}

In general, the scenario of multi-line error-handling is comparatively very low. Most cases involve single-line error-handling, where the ternary operator shines.

Scenario-2

A scenario with an in-line error check cannot use the ternary operator.

if err := json.Unmarshal(data, v); err != nil {
	return err
}

To use the ternary operator, the in-line check can be separated, as shown below.

err := json.Unmarshal(data, v)
err != nil ? return err

While it's subjective, using the ternary operator in this scenario seems more streamlined.

Cons-2: Two ways of doing if/else

Go advocates one way of doing a particular thing. The ternary operator now provides an alternative way to implement if/else.

But there is a chance that Go developers might prefer using the ternary operator over if/else when the logic in the if/else section is a single line.

Discussion

What is the scope of this proposal wrt error-handling?

This proposal focuses on single-line error handling, which accounts for over 80% of cases in Go projects (for ex, Kubernetes, as previously detailed out). While it covers a major part of error handling, it doesn't address all scenarios.

Is this the full and final proposal for error-handling?

This proposal suggests introducing a ternary operator in Go, which can help address challenges related to error handling. However, it is not directly for error handling, leaving room for future error-handling proposals.

Ternary operator details

This proposal primarily focuses on error handling rather than the introduction of the ternary operator in Go. A separate proposal is needed to fully detail ternary operator support in Go, covering various aspects, including the following:

  • Will the condition require parentheses? -- This (err != nil) instead err != nil
  • Can the ternary operator be used to assign a value to a variable? -- max = (a > b) ? a : b
  • Will nested ternary operators be supported? -- max = (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c)

Additional details of the Proposal

Go Programming Experience

Experienced. I have been using Go at work for over 8 years.

Other Languages Experience

Java, 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?

No. As per my knowledge.

Does this affect error handling?

Yes

Is this about generics?

No

Is this change backward compatible?

Yes. Check Pros.

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

Check Pros.

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

The ternary operator is a well-known programming construct, making it easy to use with no learning curve.

Click to expand for more details

Language Spec Changes

It could be part of the ternary operator proposal.

Informal Change

It could be part of the ternary operator proposal.

Cost Description

It could be part of the ternary operator proposal.

Changes to Go ToolChain

It could be part of the ternary operator proposal.

Performance Costs

It could be part of the ternary operator proposal.

Prototype

It could be part of the ternary operator proposal.

@nurali-techie nurali-techie added LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee Proposal labels Feb 18, 2025
@gopherbot gopherbot added this to the Proposal milestone Feb 18, 2025
@gabyhelp gabyhelp added the LanguageProposal Issues describing a requested change to the Go language specification. label Feb 18, 2025
@seankhliao seankhliao added the error-handling Language & library change proposals that are about error handling. label Feb 18, 2025
@seankhliao
Copy link
Member

see https://go.dev/doc/faq#Does_Go_have_a_ternary_form
not having a ternary operator is a design decision.

@seankhliao seankhliao closed this as not planned Won't fix, can't repro, duplicate, stale Feb 18, 2025
@nurali-techie
Copy link
Author

nurali-techie commented Feb 18, 2025

This proposal is not about providing yet another “conditional control flow construct” with ternary operator.

This proposal is for error-handling and we should see from lens of solving error-handling problem and not in lens of introducing ternary operator support.

@seankhliao I would appreciate it if this proposal could remain open for a day or two to allow for further input from the Go community. Closing the proposal based on the reasoning that Why Go doesn't support the ternary operator seems like a swift decision, and it may be beneficial to consider additional viewpoints before concluding.

@ianlancetaylor
Copy link
Member

I can reopen this if you like, but this looks like a different syntax for an if statement, with some restrictions (though the restrictions are not clearly spelled out). It changes if to ? and moves it after the expression, and it drops the { and }. These don't seem like major changes. It is very unlikely that we would adopt an alternative syntax for something we can already do.

@nurali-techie
Copy link
Author

nurali-techie commented Feb 19, 2025

Thanks, @ianlancetaylor for follow-up. 👍

It changes if to ? and moves it after the expression, and it drops the { and }. These don't seem like major changes.

Here, we need to see how much it's simplifying error-handling. I have been writing Go code on the job for more than 8 years, and I definitely see value in the below left-side error-handling being converted to right-side error-handling.

Image

It is very unlikely that we would adopt an alternative syntax for something we can already do.

Again, we need to see a ternary operator to simplify error-handling rather than an alternative to if/else.

These 36799 (88% of 41466) error handling code block can use ternary operator.

As stated in proposal for Kubernates project, 88% of error-handing code will simplify with this proposal. This will be true for any Go project. One can run below grep command to see percentage error-handling simplify for given Go project.

# number of times "if err != nil" is checked
$ cat **/*.go | pcregrep --buffer-size=1000000 -M "if err != nil {" | wc -l

# number of times "if err != nil" is checked and has one line in if block
$ cat **/*.go | pcregrep --buffer-size=1000000 -M "if err != nil {\n.*\n.*}" | wc -l

I can reopen this if you like

I would request you to reopen and see how much Go community finds this proposal as practical progress for error-handling.

Thank you.

@doggedOwl
Copy link

doggedOwl commented Feb 19, 2025

This solves nothing for the interleaving of errors with other values.

result, err := doA(input)
err != nil ? return nil, err

this is exactly the same as before for reading and just reinforces the wrong idea that the problem many have with error handling is "not wanting to write 3 lines"
(or maybe it's even worse for reading because now there is not even enough vertical space or "generic shape" to mentally filter out error handling)

@seankhliao seankhliao changed the title proposal: spec: simplify error handling by introducing ternary operator in Go proposal: spec: error handling with err != nil ? return Feb 19, 2025
@apparentlymart
Copy link

apparentlymart commented Feb 19, 2025

While the analogy to the C-style ternary conditional operator is a useful hook to introduce this, it seems like what's proposed here is quite different:

  • All of the examples use it as a statement rather than an expression.
  • All of the examples show a return statement, rather than an expression, in the two result positions.

Therefore I guess I would characterize what's proposed here as a "conditional return statement" rather than as a general-purpose "conditional operator", and assume it has a specification something like the following:

  • It's allowed anywhere that an unconditional return statement would be allowed in Go today.
  • It consists of three clauses, the last of which is optional:
    1. An arbitrary boolean expression used as the predicate.
    2. A return statement to execute if the predicate produces true.
    3. A return statement to execute if the predicate produces false.
  • This new syntax is syntactic sugar over two specific shapes of if statement:
    1. In the two-clause form, PREDICATE ? TRUE-RETURN-STATEMENT desugars as:

      if PREDICATE {
          TRUE-RETURN-STATEMENT
      }
      // (otherwise execution continues as normal)
    2. In the three-clause form, PREDICATE ? TRUE-RETURN-STATEMENT : FALSE-RETURN-STATEMENT desugars as:

      if PREDICATE {
          TRUE-RETURN-STATEMENT
      } else {
          FALSE-RETURN-STATEMENT
      }
      // (subsequent code is unreachable)

      This three-clause form, by implication from the fourth item in the existing Terminating Statements definition, is a new kind of terminating statement.

Does the above seem like a suitable interpretation of the proposal?


The first advantage listed under "Pros" admittedly does seem to suggest that you intended to permit any statement to appear in the second and optional third clauses, but all of your examples show return statements and you mentioned that you intend this proposal to focus only on error handling, so I assume that your intention is that potentially extending this to support other kinds of statements in the second and third clauses would need to be a separate language proposal.

Since not having a conditional expression operator in Go was an intentional design choice (as previously discussed), I think it's best to focus narrowly only on the error-handling use-case for the sake of this proposal, since otherwise your proposal would need to offer some justification for revisiting the position that the C-style conditional operator tends to be hard to read.

@nurali-techie
Copy link
Author

Thank you all for sharing thoughts on the proposal. I can now see challenges with this proposal, like less readability with right-side error-handling code and there are differences compared to be ternary operator.

I also gather some thoughts / feedbacks from reddit (link).

@ianlancetaylor @seankhliao thanks for your feedback and reopening this proposal which helped me to better understand issues with the proposal. I am closing this proposal, Thank you Go team.

@nurali-techie nurali-techie closed this as not planned Won't fix, can't repro, duplicate, stale Feb 21, 2025
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 Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee LanguageProposal Issues describing a requested change to the Go language specification. Proposal
Projects
None yet
Development

No branches or pull requests

7 participants