Error Handling in Go: The New ? operator

Go's new `?` operator for error handling aims to reduce boilerplate code while maintaining clarity. It simplifies syntax but could lead to variable shadowing and increased complexity for newcomers.

 

Background

Error handling has always been an essential part of programming. However, the Go language is controversial because of its unique error-handling mode. Any blog about how to hate the Go language will definitely mention “tedious error handling.” This problem has sparked much discussion in the Go community about how to reduce template code while maintaining clarity and maintainability.
在我自己的一个项目中,有 422 个 if err != nil

Details of Proposal

ianlancetaylor made a new proposal #71203 ,to introduce the operator ?, for error handling in Go. is used to simplify Go’s error handling. In the future, Go’s error handling might look like this.

1
2
3
4
5
6
7
// now
result, err := someFunction()
if err != nil {
    return nil, err
}
// proposal ? 
result := someFunction()?

In this example, the results of the two writings are equivalent: if someFunction() returns an error, it returns.
That’s the core of the proposal, the main goal of which is to reduce the amount of templated code while maintaining Go’s philosophy of explicitness and simplicity. ? is a syntactic sugar that, when used after a function call that returns multiple values (e.g. (T, error)), automatically checks if the last value is non-zero (indicating an error). The compiler will generate the same code as before for this style of writing, ensuring compatibility.
In the formal proposal, ianlancetaylor elaborates on the ? syntax rules:

  • ? can only appear at the end of an assignment statement or expression statement, and the expression must have a return value
  • For expression statements, ? “absorbs’’ the last value of the expression (usually err).
  • For assignment statements, ? “absorbs” the last value of the expression on the right (usually err) so that the number of values on the right will be one more than the number of variables on the left.
  • This “absorbed” value is called qvalue and must be of an interface type that implements the error interface.
  • The ? can be followed by a code block. If there is no code block, the function returns immediately if the qvalue is not nil and assigns qvalue to the last return value. If ? is followed by a code block, the code block is executed when qvalue is not nil. A variable named err is implicitly declared within the code block with the same value and type as qvalue.

A basic usage scenario might look like this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15

r := os.Open("file.txt") ? // ?Absorbs error from os.Open, and returns it if it's not null.

func Run() error { 
	Start() ? // If Start returns a non-nil error, return the error immediately.
	return nil 
}

func process() error {
	result := doSomething() ? {
		return fmt.Errorf("something failed: %v", err)  // qvalue 
	}
	anotherResult := doAnotherThing(result)?
	return nil
}

Pros

The most crucial benefit of this proposal is that it will reduce the amount of duplicate code in Go programs, as described in the proposal.

reduces the error handling boilerplate from 9 tokens to 5, 24 non-whitespace characters to 12, and 3 boilerplate lines to 2.

Cons

The biggest drawback is that all Go books and wikis need to be updated, and newcomers may need to understand the concept, as it is not quite the same as any other language implementation. This change will involve a lot of code, including Go src, so the Go Core Team is under much pressure because there is only one chance.

Shadowing

? followed by a block of code that implicitly declares an err variable, which can lead to problems with variable shadowing. An example of this is mentioned in the proposal.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
for n = 1; !utf8.FullRune(r.buf[:n]); n++ {
	r.buf[n], err = r.readByte()
	if err != nil {
		if err == io.EOF {
			err = nil // must change outer err
			break
		}
		return
	}
}
// code that later returns err

In this example, the assignment err = nil has to change the err variable that exists outside of the for loop. Using the ? operator would introduce a new err variable shadowing the outer one.
In this example, using the ? operator would cause a compiler error because the assignment err = nil would set a variable that is never used.

The Mental Burden of Writing Code Increases

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func F1() error {
	err := G1()
	log.Print(err)
	G2() ?
	{
		log.Print(err)
	}
	return nil
}

func F2() error {
	err := G1()
	log.Print(err)
	G2() ? {
		log.Print(err)
	}
	return nil
}

In this example, both functions are legal. Only the line break of G2 is different, but their behavior is entirely different. This difference cannot be made up by fmt and other means.

Unchanging Rationality

Although Go’s error-handling mechanism is often criticized, it remains in place. Therefore, the community needs to consider whether changes are necessary. In the proposal, Ian Lance Taylor repeatedly stated: “Perhaps no change is better than this change. Perhaps no change is better than any change.” The Go Core Team is not entirely resolute about error handling and improvements. I think
It feels more like it was forced by public opinion and pressure from the Go community.

Generic: Don’t disturb me

Summary

The new proposal highlights the Go Core Team’s ongoing attentiveness to the community. The?operator proposal brings a fresh perspective to the Go language’s error-handling mechanisms. By introducing a more concise syntax, the proposal aims to significantly reduce the code required for error handling, thereby enhancing the clarity of the main code flow. While some disagreements still exist, it’s evident that someone is finally advocating for it.


  • Long Time Link
  • If you find my blog helpful, please subscribe to me via RSS
  • Or follow me on X
  • If you have a Medium account, follow me there. My articles will be published there as soon as possible.
Licensed under CC BY-NC-SA 4.0
Last updated on Jan 11, 2025 19:35 CST
Built with Hugo
Theme Stack designed by Jimmy