Go by Example - Generics, Errors and Custom Errors

Go by Example - Generics, Errors and Custom Errors

介绍go中的泛型, 错误处理和自定义错误类型

Generics

从 1.18 版开始,Go 增加了对泛型(也称为类型参数)的支持。

generics.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package main

import "fmt"

// 作为泛型函数的一个示例,MapKeys 接收任意类型的映射,并返回其键的片段
// 该函数有两个类型参数--K 和 V;K 具有可比较约束
// 这意味着我们可以使用 == 和 != 操作符比较该类型的值。这是 Go 中 map keys 所必需的
// V 具有 any 约束,这意味着它不受任何限制(any 是 interface{} 的别名)
func MapKeys[K comparable, V any](m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}

// 作为泛型的一个例子,List 是一个单链列表,其值可以是任何类型的
type List[T any] struct {
head, tail *element[T]
}

type element[T any] struct {
next *element[T]
val T
}

// 我们可以像定义普通类型一样定义泛型的方法,但必须保留类型参数
// 类型是 List[T],而不是 List
func (lst *List[T]) Push(v T) {
if lst.tail == nil {
lst.head = &element[T]{val: v}
lst.tail = lst.head
} else {
lst.tail.next = &element[T]{val: v}
lst.tail = lst.tail.next
}
}

func (lst *List[T]) GetAll() []T {
var elems []T
for e := lst.head; e != nil; e = e.next {
elems = append(elems, e.val)
}
return elems
}

func main() {
var m = map[int]string{1: "2", 2: "4", 4: "8"}

// 在调用泛型函数时,我们通常可以依赖类型推断
// 请注意,在调用 MapKeys 时,我们不必指定 K 和 V 的类型,编译器会自动推断出它们
fmt.Println("keys:", MapKeys(m))

// ......不过我们也可以明确指定它们
_ = MapKeys[int, string](m)

lst := List[int]{}
lst.Push(10)
lst.Push(13)
lst.Push(23)
fmt.Println("list:", lst.GetAll())
}
log
1
2
3
$ go run generics.go
keys: [4 1 2]
list: [10 13 23]

注意:Go 中没有定义 map keys 的迭代顺序,因此不同的调用可能会产生不同的顺序。

Errors

在 Go 中,通过显式、单独的返回值来传达错误是习以为常的。这与 Java 和 Ruby 等语言中使用的异常,以及 C 语言中有时使用的重载单一结果/错误值形成了鲜明对比。Go 的方法使得查看哪些函数返回错误变得容易,并能使用与其他非错误任务相同的语言结构来处理它们。

更多详情,请参阅 errors 包的文档本篇博文

errors.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package main

import (
"errors"
"fmt"
)

// 按照惯例,error是最后一个返回值,并具有内置接口 error 类型
func f(arg int) (int, error) {
if arg == 42 {
// errors.New 用给定的错误信息构造一个基本错误值
return -1, errors.New("can't work with 42")
}
// 错误位置上的nil表示没有错误
return arg + 3, nil
}

// 哨兵错误是一个预先声明的变量,用于表示特定的错误条件
var ErrOutOfTea = fmt.Errorf("no more tea available")
var ErrPower = fmt.Errorf("can't boil water")

func makeTea(arg int) error {
if arg == 2 {
return ErrOutOfTea
} else if arg == 4 {

// 我们可以用更高级别的错误来包装错误,以添加上下文
// 最简单的方法是使用 fmt.Errorf 中的 %w 动词
// 封装的错误会创建一个逻辑链(A 封装 B,B 封装 C 等),可以使用 errors.Is 和 errors.As 等函数进行查询
return fmt.Errorf("making tea: %w", ErrPower)
}
return nil
}

func main() {
for _, i := range []int{7, 42} {
// 在 if 行中使用内联错误检查很常见
if r, e := f(i); e != nil {
fmt.Println("f failed:", e)
} else {
fmt.Println("f worked:", r)
}
}

for i := range 5 {
if err := makeTea(i); err != nil {

// errors.Is 检查给定错误(或其链中的任何错误)是否与特定错误值相匹配
// 这对封装或嵌套错误特别有用,可让您识别特定错误类型或错误链中的哨兵错误
if errors.Is(err, ErrOutOfTea) {
fmt.Println("We should buy new tea!")
} else if errors.Is(err, ErrPower) {
fmt.Println("Now it is dark.")
} else {
fmt.Printf("unknown error: %s\n", err)
}
continue
}

fmt.Println("Tea is ready!")
}
}
log
1
2
3
4
5
6
7
8
$ go run errors.go
f worked: 10
f failed: can't work with 42
Tea is ready!
Tea is ready!
We should buy new tea!
Tea is ready!
Now it is dark.

Custom Errors

通过在自定义类型上实现 Error() 方法,可以将其用作错误。下面是上面示例的一个变体,它使用自定义类型明确表示参数错误。

custom-errors.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import (
"errors"
"fmt"
)

// 自定义错误类型通常具有后缀“error”
type argError struct {
arg int
message string
}

// 添加此 Error 方法可使 argError 实现错误接口
func (e *argError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.message)
}

func f(arg int) (int, error) {
if arg == 42 {
// 返回我们自定义的错误类型
return -1, &argError{arg, "can't work with it"}
}
return arg + 3, nil
}

func main() {

_, err := f(42)
var ae *argError
// errors.As 是 errors.Is 的高级版本
// 它检查给定错误(或其链中的任何错误)是否与特定错误类型匹配,并转换为该类型的值,返回 true。如果不匹配,则返回 false
if errors.As(err, &ae) {
fmt.Println(ae.arg)
fmt.Println(ae.message)
} else {
fmt.Println("err doesn't match argError")
}
}
log
1
2
3
$ go run custom-errors.go
42
can't work with it

参考链接

作者

孤独小狼

发布于

2024-02-18

更新于

2024-02-18

许可协议

评论