vault backup: 2026-06-07 12:14:39

This commit is contained in:
2026-06-07 12:14:39 +08:00
parent 1a72bc4d82
commit 7ce9b83218
61 changed files with 8409 additions and 14429 deletions
+143 -108
View File
@@ -1,145 +1,180 @@
---
tags:
- Go
- golang
- go基础语法
- error
tags: [go, golang, go基础语法, error]
create time: 2026-06-07 15:00
---
# Go语言error
在`Go`语言开发中,我们会多次用到`error`这个东西,尤其是在函数的返回值上,由于`go`的函数支持多返回值,所以一般会用`error`作为其中一个返回值,代表该函数执行过程中或者逻辑有出错,那究竟`error`是个什么东西呢,下面我们就来一探究竟。
# Go 语言 error
## 概述
`error` 是 Go 的内置接口(只有一个 `Error() string` 方法)。本文讲解 error 的本质、创建方式、自定义 error、错误包装与比较。
## 正文
### error 到底是什么?
## error是什么
`error`其实是Go语言的一个接口类型,就是一个普通的接口,并且也不会携带任何的堆栈信息。
接口的定义如下:
```go
type error interface {
Error() string
}
```
通常我们会使用`errors.New()`或者`fmt.Errorf()`来返回一个`error`对象,但是需要注意,通过这两种方式返回的`error`对象都是不可以进行比较的,因为`errors.New()`返回的其实是一个地址,不能用来做等值判断,如果要是先等值判断,需要自己实现,而`fmt.Error()`的内部其实也是用到了`errors.New()`。
1. `errors.New()`函数实现:
**error 就是一个接口**。任何实现了 `Error() string` 方法的类型都可以作为 error 返回。这意味着你可以完全自定义错误类型。
> [!question] ❓ 思考
> 既然 error 只是一个接口,那为什么不能直接用 `==` 比较两个 error?
### 创建 error 的两种方式
#### errors.New() — 简单错误
```go
err := errors.New("something went wrong")
```
底层实现:
```go
func New(text string) error {
return &errorString{text}
}
```
2. `fmt.Errorf()`实现:
```go
func Errorf(format string, a ...interface{}) error {
p := newPrinter()
p.wrapErrs = true
p.doPrintf(format, a)
s := string(p.buf)
var err error
if p.wrappedErr == nil {
err = errors.New(s)
} else {
err = &wrapError{s, p.wrappedErr}
}
p.free()
return err
return &errorString{text} // 返回指针
}
```
代码展示:
```go
package main
import (
"fmt"
"errors"
)
// 定义一个正数自加的函数,当传入的整数小于等于0的时候报错
func getPositiveSelfAdd(num int) (int,error) {
if num <=0 {
return -1,fmt.Errorf("num is not a positive number")
#### fmt.Errorf() — 带格式化的错误
```go
err := fmt.Errorf("failed to open file %s: %v", path, innerErr)
```
支持 `%w` 包装错误(见下文"错误包装"章节)。
> [!warning] ⚠️ error 不能用 == 直接比较
> `errors.New("hello") == errors.New("hello")` 结果是 `false`,因为它们是不同的指针实例。需要比较字符串内容:`err.Error() == "hello"`。
### 标准错误处理模式
Go 约定:**error 作为最后一个返回值**:
```go
func ReadFile(name string) (string, error) {
data, err := os.ReadFile(name)
if err != nil {
return "", err // 向上层传递错误
}
return num+1,nil
return string(data), nil
}
func main() {
num1,err1 := getPositiveSelfAdd(1)
fmt.Printf("nums is %d, err is %v\n",num1 ,err1)
num2,err2 := getPositiveSelfAdd(-2)
fmt.Printf("nums is %d, err is %v\n",num2 ,err2)
err3 := errors.New("hello")
err4 := errors.New("hello")
fmt.Println(err3==err4)
fmt.Println(err3.Error()==err4.Error())
// 调用方
content, err := ReadFile("config.json")
if err != nil {
log.Fatal(err)
}
```
运行结果:
```
nums is 2, err is <nil>
nums is -1, err is num is not a positive number
false
false
```
通过样例可以看到,通过上述方法创建出的两个`error`对象是不能直接比较的,即便是`error`信息一样,也会返回`false`,如果我们想要比较,可以通过`Error()`方法拿到其中的`error`字符串信息,比较字符串。
## 自定义error对象
Go语言内置的`error`创建方法非常简单,也易上手,但是有时候并不能满足我们的业务要求,因为他只返回了错误信息,类似于`error`的`message`,但是很多时候我们业务上还需要错误码,即`error code`。所以,很多时候我们可以自定义`error`对象。
> [!tip] 💡 防御性编程
> ```go
> if err != nil {
> return err // 尽早返回,避免嵌套
> }
> // 下面是正常逻辑
> ```
> 这就是著名的 "error laddering"——每个错误处理块都缩进一级,最终正常逻辑在最外层。
### 自定义 error
实现 `Error() string` 方法即可:
```go
type MyError struct {
code int
msg string
type NotFoundError struct {
Resource string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("resource not found: %s", e.Resource)
}
// 使用
err := &NotFoundError{Resource: "user_42"}
fmt.Println(err) // resource not found: user_42
```
对象定义完之后,接下来只需要实现`error`接口的`Error`方法即可,这样,我们就自定义了一个同时带有错误码和错误信息的error对象。
下面请看具体例子:
如果需要携带额外信息(如错误码):
```go
package main
import "fmt"
type MyError struct {
code int
msg string
type AppError struct {
Code int
Msg string
}
func (m MyError) Error() string {
return fmt.Sprintf("code:%d,msg:%v", m.code, m.msg)
func (e *AppError) Error() string {
return fmt.Sprintf("[%d] %s", e.Code, e.Msg)
}
func NewError(code int, msg string) error {
return MyError{
code: code,
msg: msg,
}
}
func Code(err error) int {
if e, ok := err.(MyError); ok {
return e.code
}
return -1
}
func Msg(err error) string {
if e, ok := err.(MyError); ok {
return e.msg
}
return ""
}
func main() {
err := NewError(100, "test MyError")
fmt.Printf("code is %d, msg is %s", Code(err), Msg(err))
// 提取错误码
func ErrorCode(err error) int {
if appErr, ok := err.(*AppError); ok {
return appErr.Code
}
return -1
}
```
运行结果:
> [!note] 📝 用类型断言提取信息
> `err.(*AppError)` 将 error 接口还原为具体类型,可以访问额外的字段。
### 错误包装(Go 1.13+)
`fmt.Errorf` 支持 `%w` 来**包装**错误,保留原始错误链:
```go
func LoadConfig(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("load config: %w", err)
}
return nil
}
// 检查是否包含特定错误
var fsErr *fs.PathError
if errors.As(err, &fsErr) {
fmt.Printf("文件系统错误: %v\n", fsErr)
}
// 检查错误链中是否有某个错误
if errors.Is(err, io.EOF) {
fmt.Println("到达文件末尾")
}
```
code is 100, msg is test MyError
```
> [!info] ℹ️ 三个核心函数
> | 函数 | 用途 |
> |------|------|
> | `errors.Is(err, target)` | 检查 err 是否在错误链中等于 target |
> | `errors.As(err, target)` | 将 err 链中的第一个匹配类型赋值给 target |
> | `fmt.Errorf("%w", err)` | 包装错误,保留原始错误 |
```mermaid
graph TD
A["load config 错误"] --> B["os.Open 错误"]
B --> C["permission denied"]
style A fill:#f9f,stroke:#333
style B fill:#bbf,stroke:#333
style C fill:#ff9,stroke:#333
```
> [!warning] ⚠️ %w vs %v
> - `%w` — **包装**错误,可以用 `errors.Is/As` 追溯
> - `%v` — 仅格式化输出,不保留错误链关系
### 最佳实践总结
| 场景 | 推荐做法 |
|------|---------|
| 简单错误 | `errors.New()` |
| 带上下文的错误 | `fmt.Errorf("context: %w", err)` |
| 需要提取错误码 | 自定义 struct + `Error() string` |
| 检查错误类型 | `errors.As(err, &target)` |
| 判断是否某类错误 | `errors.Is(err, target)` |
| 业务逻辑错误 | 定义专门的错误类型(如 `ErrNotFound`) |