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`:
type ErrNegativeSqrt float64Implement the Error Interface
This is the most critical part of the exercise. We need to make ErrNegativeSqrt implement the error interface:
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:
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:
/app/go-tour/exercise-errors # go run main.go
1.4142135623730951 <nil>
0 cannot Sqrt negative number: -2IV. 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.
