没有不值得去解决的问题,也没有不值得去学习的技术!

Advanced Go: Understanding Custom Error Interfaces and Infinite Recursion Pitfalls

In Go, an error is not an exception but a value. By implementing the error interface, we can create custom error types with rich contextual information. This article uses the “Exercise: Errors” from the Go Tour to explore how to implement a square root function that supports negative number detection, and reveals the “infinite recursion” trap that is easy to fall into.

I. Core Requirements Analysis

We need to complete the following tasks:

* Define a custom error type ErrNegativeSqrt.

* Implement the Error() string method for this type so that it satisfies the error interface.

* Modify the Sqrt function to return this custom error when the input is a negative number.

II. Code Implementation

Define the Custom Error Type

First, we define a new type based on `float64`:

Go
type ErrNegativeSqrt float64

Implement the Error Interface

This is the most critical part of the exercise. We need to make ErrNegativeSqrt implement the error interface:

Go
func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}

> ⚠️ Pitfall Guide: Why must we convert to `float64(e)`?
> If you directly write `fmt.Sprint(e)` in the Error() method, the program will fall into infinite recursion.
> Reason: When `fmt.Sprint` prints a variable, if it finds that the variable implements the error interface, it will prioritize calling its Error() method.
> Result: Error() calls `fmt.Sprint` -> `fmt.Sprint` calls Error() again -> infinite loop.
> Solution: Convert the type to a basic type via `float64(e)` to break this call chain.

Complete the Sqrt Function

Utilize the standard library `math.Sqrt` and add error checking logic:

Go
package main

import (
	"fmt"
	"math"
)

// ErrNegativeSqrt 自定义错误类型
type ErrNegativeSqrt float64

// Error 实现 error 接口
// 注意:必须将 e 转换为 float64(e),否则 fmt.Sprint(e) 会再次调用 Error() 方法,导致无限递归
func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}

// Sqrt 计算平方根,如果输入为负数则返回错误
func Sqrt(x float64) (float64, error) {
	if x < 0 {
		return 0, ErrNegativeSqrt(x)
	}
	return math.Sqrt(x), nil
}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}

III. Execution Result After running the main function, the output is as follows:

Plaintext
/app/go-tour/exercise-errors # go run main.go 
1.4142135623730951 <nil>
0 cannot Sqrt negative number: -2

IV. Summary
Through this simple exercise, we learned:
1. Implicit implementation of interfaces: Any type can become an error as long as it defines the Error() string method.
2. Errors as data: We can carry extra fields in errors just like handling ordinary structs (such as the numeric value in this example).
3. Formatting details: When using the `fmt` package to handle types that implement their own interfaces, be mindful of type conversion to avoid recursion.

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系