文章目录
- go语言error
- error是什么
-
- [errors.New() 函数实现:](#errors.New() 函数实现:)
- fmt.Errorf()实现
- 自定义error对象
- go语言defer
- defer执行顺序
- defer的使用场景
- defer与return
- go语言异常捕获
- recover异常捕获
- panic传递
- 依赖管理
- [Go Modules要点](#Go Modules要点)
- [Go Modules命令](#Go Modules命令)
- [Go Modules使用](#Go Modules使用)
go语言error
在Go语言开发中,我们会多次用到error这个东西,尤其是在函数的返回值上,由于go的函数支持多返回值,所以一般会用error作为其中一个返回值,代表该函数执行过程中或者逻辑有出错,那究竟error是个什么东西呢,下面我们就来一探究竟。
error是什么
error其实是golang的一个接口类型,就是一个普通的接口,并且也不会携带任何的堆栈信息。
接口的定义如下:
go
type error interface {
Error() string
}
通常我们会使用 errors.New() 或者 fmt.Errorf() 来返回一个 error 对象,但是需要注意,通过这两种方式返回的 error 对象都是不可以进行比较的,因为 errors.New() 返回的其实是一个地址,不能用来做等值判断,如果要做等值判断,需要自己实现,而 fmt.Errorf() 的内部其实也是用到了 errors.New()。
errors.New() 函数实现:
go
func New(text string) error {
return &errorString{text}
}
fmt.Errorf()实现
go
// Errorf formats according to a format specifier and returns the string as a
// value that satisfies error.
//
// If the format specifier includes a %w verb with an error operand,
// the returned error will implement an Unwrap method returning the operand.
// If there is more than one %w verb, the returned error will implement an
// Unwrap method returning a []error containing all the %w operands in the
// order they appear in the arguments.
// It is invalid to supply the %w verb with an operand that does not implement
// the error interface. The %w verb is otherwise a synonym for %v.
func Errorf(format string, a ...any) error {
p := newPrinter()
p.wrapErrs = true
p.doPrintf(format, a)
s := string(p.buf)
var err error
switch len(p.wrappedErrs) {
case 0:
err = errors.New(s)
case 1:
w := &wrapError{msg: s}
w.err, _ = a[p.wrappedErrs[0]].(error)
err = w
default:
if p.reordered {
slices.Sort(p.wrappedErrs)
}
var errs []error
for i, argNum := range p.wrappedErrs {
if i > 0 && p.wrappedErrs[i-1] == argNum {
continue
}
if e, ok := a[argNum].(error); ok {
errs = append(errs, e)
}
}
err = &wrapErrors{s, errs}
}
p.free()
return err
}
代码展示
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")
}
return num + 1, 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())
}
运行结果:
bash
nums is 2, err is <nil>
nums is -1, err is num is not a positive number
false
true
通过样例可以看到,通过上述方法创建出的两个 error 对象是不能直接比较的,即便是 error 信息一样,也会返回 false,如果我们想要比较,可以通过 Error() 方法拿到其中的 error 字符串信息,比较字符串。

自定义error对象
go语言内置的 error 创建方法非常简单,也易上手,但是有时候并不能满足我们的业务要求,因为他只返回了错误信息,类似于 Error 的 message,但是很多时候我们业务上还需要错误码,即 Error code。所以,很多时候我们可以自定义 error 对象
go
type MyError struct {
code int
msg string
}
对象定义完之后,接下来只需要实现 error 接口的 Error 方法即可,这样我们就自定义了一个同时带有错误码和错误信息的 error 对象。
下面请看具体例子:
go
package main
import "fmt"
type MyError struct {
code int
msg string
}
func (m MyError) Error() string {
return fmt.Sprintf("code:%d,msg:%v", m.code, m.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))
}
运行结果:
bash
code is 100, msg is test MyError
go语言defer
defer顾名思义,延迟。它是go语言中的一个关键字,主要用在函数或方法前面,作用是用于函数和方法的延迟调用,在语法上,defer与普通的函数调用没有什么区别。
在使用上非常简单,只需要弄清楚以下几点即可:
- 延迟的函数的什么时候被调用?
函数return之后,发生panic之前。准确来说,return语句其实包含两个操作: 1. 给返回值赋值 2. 执行真正的返回操作
defer在return给返回值赋值后、函数真正返回前执行的 - 延迟调用的语法规则
a. defer关键字后面表达式必须是函数或者方法调用
b. 延迟内容不能被括号括起来
defer执行顺序
上一小节说到defer关键字后面的函数调用会在函数return或者发生panic的时候执行,这个在单个defer的时候很好理解,但当一个函数中有多个defer的时候,他们的顺序是怎么样的呢?defer语句的执行顺序是先进后出LIFO。
下面看个具体例子:
go
package main
import "fmt"
func defer1() {
fmt.Println("defer1")
}
func defer2() {
fmt.Println("defer2")
}
func defer3() {
fmt.Println("defer3")
}
func main() {
defer defer1()
defer defer2()
defer defer3()
}
运行结果:
bash
defer3
defer2
defer1
可以看到执行顺序跟栈是一样的,先调用,后执行
defer的使用场景
通过前面的小节我们知道了defer关键字主要是用于延迟调用,那么什么场景下需要我们用到延迟调用了,有过golang基础的同学在一些代码中经常看到defer关键字。defer关键字一般用在以下两个场景中
资源的释放
通过defer延迟调用机制,我们可以简洁优雅处理资源回收问题,从而避免在复杂的代码逻辑情况下,遗漏相关的资源回收问题,用的比较多的就是类似网络连接,数据库连接,以及文件句柄的资源的释放。
看看下面一个复制文件的函数,
go
func CopyFile(dstFile, srcFile string) (wr int64, err error) {
src, err := os.Open(srcFile)
if err != nil {
return
}
dst, err := os.Create(dstFile)
if err != nil {
return
}
wr, err = io.Copy(dst, src)
dst.Close()
src.Close()
return
}
仔细看这段代码,其实是有问题的,比如当第6行执行失败,程序直接返回了,但我们并没有关闭前面打开的文件资源src,这样就造成了资源的浪费。
那么用defer我们要怎么做呢?
go
func CopyFile(dstFile, srcFile string) (wr int64, err error) {
src, err := os.Open(srcFile)
if err != nil {
return
}
// defer src.Close() 一定要放在 os.Open 成功之后:
// 否则如果 open 失败,src 是 nil,你 defer 了 nil.Close() 就会 panic。
defer src.Close()
dst, err := os.Create(dstFile)
if err != nil {
return
}
defer dst.Close()
wr, err = io.Copy(dst, src)
return wr, err
}
只要我们正确打开了某个资源,比如src和dst,没返回err的情况下,都可以用defer延迟调用来关闭资源,注意,这是go语言中非常常见的一种资源关闭方式。
配合recover一起处理panic
defer另一个常用的地方就是在处理程序panic的时候,go语言中用panic来抛出异常,用recover来捕获异常,所以当我们的程序出现异常的时候,我们需要知道是发生了什么异常的时候,就可以用defer recover来捕获异常。
go
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
a := 1
b := 0
fmt.Println("result:", a/b)
}
运行结果:
bash
runtime error: integer divide by zero
可以看到,程序并没有输出result,这是因为我们尝试对一个除数为0的数做除法,这是不允许的,所以程序回panic,但我们用defer在程序发生panic的时候捕获了这个异常,打印出异常信息:runtime error: integer divide by zero。
defer与return
前面第一小节我们介绍过defer函数的执行是在return的时候,那么再具体一点,在return的时候,defer具体做了什么?又会带来什么结果?这是一个非常值得探讨的问题,也是面试官在面试中经常会问的问题,往往通过这个问题就可以看出一个面试者对go语言掌握的扎不扎实。
例子1:
go
package main
import "fmt"
func deferRun() {
var num = 1
defer fmt.Printf("num is %d", num)
num = 2
return
}
func main() {
deferRun()
}
运行结果:
bash
num is 1
为什么?
延迟函数 defer fmt.Printf("num is %d", num) 的参数num在 defer 语句出现的时候就已经确定,num=1,所以不管后面怎么修改 a 的值,最终调用defer函数传递给defer函数的参数已经固定是1了,不会再变化。
例子2:
go
package main
import (
"fmt"
)
func deferRun1() {
var num = 1
numptr := &num
// 0xc00008e008 1 1
defer fmt.Println("defer1:\n", numptr, num, *numptr)
// 0xc00008e008 2 2
defer func() {
fmt.Println("defer2:\n", numptr, num, *numptr)
}()
// 0xc00008e008 1 2
defer func(num int, numptr *int) {
fmt.Println("defer3:\n", numptr, num, *numptr)
}(num, numptr)
num = 2
fmt.Println(numptr, num, *numptr)
return
}
func main() {
deferRun1()
}
bash
root@GoLang:~/proj/goforjob# go run main.go
0xc00008e008 2 2
defer3:
0xc00008e008 1 2
defer2:
0xc00008e008 2 2
defer1:
0xc00008e008 1 1


例子3:
go
package main
import "fmt"
func main() {
deferRun()
}
func deferRun() {
var arr = [4]int{1, 2, 3, 4}
defer printArr(&arr)
arr[0] = 100
return
}
func printArr(arr *[4]int) {
for i := range arr {
fmt.Println(arr[i])
}
}
运行结果:
bash
100
2
3
4
为什么?
通过前一个地址,我们知道在defer出现的时候,参数已经确定,但是这里传递的是地址,地址没变,但是地址对应的内容被修改了,所以输出会被修改。
例子4:
go
package main
import "fmt"
func main() {
res := deferRun()
fmt.Println(res)
}
func deferRun() (res int) {
num := 1
defer func() {
res++
}()
return num
}
运行结果:
bash
2
为什么?
这是一个非常经典的例子,要想准确的的程序的执行结果,需要我们对函数return的执行有一个细致的了解。其实函数的return并非一个原子操作,return的过程可以被分解为以下三步:
- 设置返回值
- 执行defer语句
- 将结果返回
所以,在本例中,第一步是将result的值设置为num,此时还未执行defer,num的值是1,所以result被设置为1,然后再执行defer语句将result+1,最终将result返回,所以会打印出2
例子5:
go
package main
import "fmt"
func main() {
res := deferRun()
fmt.Println(res)
}
func deferRun() int {
var num int
defer func() {
num++
}()
return num
}
运行结果:
bash
0
本例和前面的区别返回值是匿名的,但是我们可以同样运用上面的思路,自己创建一个返回值,这里假设为res,运用前面的思路分析,第一步将res设置为1,第一步执行defer将num+1,第二步将res返回,所以最终结果是0。
例子6:
go
package main
import "fmt"
func main() {
res := deferRun()
fmt.Println(res)
}
func deferRun() int {
num := 1
defer func() {
num++
}()
return num
}
运行结果:
bash
1
同样的思路不难分析:自己创建一个返回值,这里假设为res,第一步将res设置为num,所以res的值为1,第二二步执行defer将num+1,此时num为2,但是res为1,第三步将res返回,所以最终结果是1。
例子7:
go
package main
import "fmt"
func main() {
res := deferRun()
fmt.Println(res)
}
func deferRun() (res int) {
num := 1
defer func() {
num++
}()
return num
}
运行结果:
bash
1
不难分析运行结果还是1,同样的三步分析法,因为defer改变的是num的值,而不是改变的res的值,所以结果不会变,不过defer函数里变为res++,那么结果就是2了。
所以,当我们碰到defer与return确定最终的返回值,可以总结为以下两点:
- defer 定义的延迟函数的参数在 defer 语句出时就已经确定下来了
- return 不是原子级操作,执行过程是:设置返回值-->执行 defer 语句-->将结果返回
go语言异常捕获
recover异常捕获
异常其实就是指程序运行过程中发生了panic,那么我们为了不让程序报错退出,可以在程序中加入recover机制,将异常捕获,打印出异常,这样也方便我们定位错误。而捕获的方式我们之前在讲defer的时候也提到过,一般是用recover和defer搭配使用来捕获异常。
下面请看个具体例子:
go
func main() {
defer func() {
if error := recover(); error != nil {
fmt.Println("出现了panic,使用reover获取信息:", error)
}
}()
fmt.Println("11111111111")
panic("出现panic")
fmt.Println("22222222222")
}
运行结果:
bash
11111111111
出现了panic,使用reover获取信息: 出现panic
注意,这里有了recover之后,程序不会在panic处中断,在执行完panic之后,会接下来执行defer recover函数,但是当前函数panic后面的代码不会被执行,但是调用该函数的代码会接着执行。
如果我们在main函数中未加入defer func(){...},当我们的程序运行到底8行时就会panic掉,而通常在我们的业务程序中对于程序panic是不可容忍的,我们需要程序健壮的运行,而不是是不是因为一些panic挂掉又被拉起,所以当发生panic的时候我们要让程序能够继续运行,并且获取到发生panic的具体错误,这就可以用上述方法。
panic传递
当一个函数发生了panic之后,若在当前函数中没有recover,会一直向外层传递直到主函数,如果迟迟没有recover的话,那么程序将终止。如果在过程中遇到了最近的recover,则将被捕获。
看下面例子:
go
package main
import "fmt"
func testPanic1() {
fmt.Println("testPanic1上半部分")
testPanic2()
fmt.Println("testPanic1下半部分")
}
func testPanic2() {
defer func() {
recover()
}()
fmt.Println("testPanic2上半部分")
testPanic3()
fmt.Println("testPanic2下半部分")
}
func testPanic3() {
fmt.Println("testPanic3上半部分")
panic("testPanic3出现了panic")
fmt.Println("testPanic3下半部分")
}
func main() {
fmt.Println("程序开始")
testPanic1()
fmt.Println("程序结束")
}
运行结果:
bash
程序开始
testPanic1上半部分
testPanic2上半部分
testPanic3上半部分
testPanic1下半部分
程序结束
解析:
调用链:main-->testPanic1-->testPanic2-->testPanic3,但是在testPanic3中发现了一个panic,由于testPanic3没有recover,向上找,在testPanic2中找到了recover,panic被捕获了,程序接着运行,由于testPanic3发生了panic,所以不再继续执行,函数跳出返回到testPanic2,testPanic2中捕获到了panic,也不会再继续执行,跳出函数testPanic2,到了testPanic1接着运行。
所以recover和panic可以总结为以下两点:
- recover()只能恢复当前函数级或以当前函数为首的调用链中的函数中的panic(),恢复后调用当前函数结束,但是调用此函数的函数继续执行
- 函数发生了panic之后会一直向上传递,如果直至main函数都没有recover(),程序将终止,如果是碰见了recover(),将被recover捕获。
我觉得可以更形象的说: panic()中断了其所在的函数级,并和病毒一样向上回溯传播;recover()是抗体,阻止了panic()继续向上传播(即阻止panic()传到testPanic1),但是recover()所在的函数级一定也被panic感染了,tastPanic2没有被执行,而是回到一直健康没被感染的testPanic1
依赖管理
在工程代码中,每种语言基本上都有自己的依赖管理工具,比如python的pip、node.js的npm,java的maven,rust的cargo,Go语言也有提供自己的依赖库管理工具。Go语言从v1.5开始开始引入vendor模式,如果项目目录下有vendor目录,那么go工具链会优先使用vendor内的包进行编译、测试等。在go1.11之后,go语言主要使用Go modules对代码依赖进行管理。
Go Modules要点
GO111MODULE环境变量
这个环境变量是Go Modules的开关,主要有以下参数:
- auto:只在项目包含了go.mod文件时启动go modules,在Go1.13版本中是默认值
- on:无脑启动Go Modules,推荐设置,Go1.14版本以后的默认值
- off:禁用Go Modules,一般没有使用go modules的工程使用;
GOPROXY
该环境变量用于设置Go模块代理,Go后续在拉取模块版本时能够脱离传统的VCS方式从镜像站点快速拉取,GOPROXY的值要以英文逗号分割,默认值是 https://proxy.golang.org,direct,但是该地址在国内无法访问,所以可以使用 goproxy.cn 来代替(七牛云配置),设置命令:
go
go env -w GOPROXY=https://goproxy.cn,direct
也可以使用其他配置,比如阿里配置:
go
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/
该环境变量也可以关闭,可以设置为"off",禁止Go在后续操作中使用任何Go module proxy;
上面的配置中我们用逗号分割后面的值是 direct,它是什么意思呢?
direct 为特殊指示符,因为我们指定了镜像地址,默认是从镜像站点拉取,但是有些库可能不存在镜像站点中,direct 可以指示Go回源到模块版本的源地址去抓取,比如github,当go module proxy返回404、410这类错误时,其会自动尝试列表中的下一个,遇见 direct 时回源地址抓取;
GOSUMDB
GOSUMDB(go checksum database)是Go官方为了go modules安全考虑,设定的module校验数据库,你在本地对依赖进行变动(更新/添加)操作时,Go 会自动去这个服务器进行数据校验,保证你下的这个代码库和世界上其他人下的代码库是一样的,保证Go在拉取模块版本时拉取到的模块版本数据未经过篡改
GOSUMDB的值自定义格式如下:
- 格式 1:<SUMDB_NAME>+<PUBLIC_KEY>。
- 格式 2:<SUMDB_NAME>+<PUBLIC_KEY> <SUMDB_URL>。
GOSUMDB的默认值是 sum.golang.org,默认值与自定义值的格式不一样,默认值在国内是无法访问,这个值我们一般不动,因为我们一般已经设置好了GOPROXY,goproxy.cn支持代理 sum.golang.org;
所以,环境变量 GOSUMDB 可以用来配置你使用哪个校验服务器和公钥来做依赖包的校验
但是在使用的时候需要注意,如果你的代码仓库或者模块是私有的,那么它的校验值不应该出现在互联网的公有数据库里面,但是我们本地编译的时候默认所有的依赖下载都会去尝试做校验,这样不仅会校验失败,更会泄漏一些私有仓库的路径等信息,我们可以使用 GONOSUMDB 这个环境变量来设置不做校验的代码仓库,它可以设置多个匹配路径,用逗号相隔。
例如:
go
go env -w GONOSUMDB=*.example.com,test.xyz/com
这样的话,像 "http://git.example.com","test.xyz/com"这些公司和自己的私有仓库就都不会做校验了。
GONOPROXY/GONOSUMDB/GOPRIVATE
这三个环境变量放在一起说,一般在项目中不经常使用,这三个环境变量主要用于私有模块的拉取,在GOPROXY、GOSUMDB中无法访问到模块的场景中,例如拉取git上的私有仓库;
GONOPROXY、GONOSUMDB的默认值是GOPRIVATE的值,所以我们一般直接使用GOPRIVATE即可,其值也是可以设置多个,以英文逗号进行分割;例如:
go
go env -w GOPRIVATE="github.com/asong2020/go-localcache,git.xxxx.com"
也可以使用通配符的方式进行设置,对域名设置通配符号,这样子域名就都不经过Go module proxy和Go checksum database
全局缓存
go mod download会将依赖缓存到本地,缓存的目录是GOPATH/pkg/mod/cache、GOPATH/pkg/sum,这些缓存依赖可以被多个项目使用,未来可能会迁移到 $GOCACHE 下面;
可以使用 go clean -modcache 清理所有已缓存的模块版本数据;
Go Modules命令
我们可以使用 go help mod 查看可以使用的命令:
bash
root@GoLang:~/proj/goforjob# go help mod
Go mod provides access to operations on modules.
Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.
Usage:
go mod <command> [arguments]
The commands are:
download download modules to local cache
edit edit go.mod from tools or scripts
graph print module requirement graph
init initialize new module in current directory
tidy add missing and remove unused modules
vendor make vendored copy of dependencies
verify verify dependencies have expected content
why explain why packages or modules are needed
Use "go help mod <command>" for more information about a command.

go.mod文件
go.mod是启用Go modules的项目所必须且最重要的文件,其描述了当前项目的元信息,每个go.mod文件开头符合包含如下信息:
module:用于定义当前项目的模块路径(突破$GOPATH路径)
go:当前项目Go版本,目前只是标记作用
require:用于设置一个特定的模块版本
exclude:用于从使用中排除一个特定的模块版本
replace:用于将一个模块版本替换为另外一个模块版本,例如chromedp使用golang.org/x/image这个package一般直连是获取不了的,但是它有一个github.com/golang/image的镜像,所以我们要用replace来用镜像替换它
restrict:用来声明该第三方模块的某些发行版本不能被其他模块使用,在Go1.16引入
示例如下:
go
module rotatebot
go 1.17
require (
github.com/gin-gonic/gin v1.8.1
github.com/sirupsen/logrus v1.9.0
gorm.io/driver/sqlite v1.4.3
gorm.io/gorm v1.24.2
)
require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
)
exclude (
github.com/json-iterator/go v1.1.12
)
replace (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd ==> github.com/modern-go/concurrent v0.0.0-20190606011245-iyfhlkiuhydg
)
retract v0.2.0
假设我们有上述go.mod文件,接下来我们分模块详细介绍一下各个部分
module
go.mod文件的第一行是module,表示工程里的依赖的基路径,例如上面的项目:
go
module rotatebot
工程里的import的路径都是以rotatebot开头的字符串
go version
go.mod文件的第二行是go version,其是用来指定你的代码所需要的最低版本:
go
go 1.17
require
require用来指定该项目所需要的各个依赖库以及他们的版本,从上面的例子中我们看到版本部分有不同的写法,还有注释,接下来我们来解释一下这部分;
indirect注释
go
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
以下场景才会添加indirect注释:
- 当前项目依赖包A,A又依赖包B,但是A的go.mod文件中缺失B,所以在当前项目go.mod中补充B并添加indirect注释
- 当前项目依赖包A,A又依赖包B,但是依赖包A没有go.mod文件,所以在当前项目go.mod中补充B并添加indirect注释
- 当前项目依赖包A,A又依赖包B,当依赖包A降级不再依赖B时,这个时候就会标记indirect注释,可以执行go mod tidy移除该依赖;
Go1.17版本对此做了优化,indirect 的 module 将被放在单独 require 块的,这样看起来更加清晰明了。
go
module github.com/LingoRihood/GoDistributeCache
go 1.25.4
require (
github.com/sirupsen/logrus v1.9.3
go.etcd.io/etcd/client/v3 v3.6.6
google.golang.org/grpc v1.71.1
google.golang.org/protobuf v1.36.5
)
require (
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
go.etcd.io/etcd/api/v3 v3.6.6 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.6.6 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
)
incompatible标记
incompatible标记其实是一个module标签归规范约束,Go module 的版本选择机制规定,Module 的版本号需要遵循 v.. 的格式,此外,如果 major 版本号大于 1 时,其版本号还需要体现在 Module 名字中。
比如 Module github.com/RainbowMango/m,如果其版本号增长到 v2.x.x 时,其 Module 名字中需要相应的改变为:github.com/RainbowMango/m/v2。即,如果 major 版本号大于 1 时,需要在 Module 名字中体现版本。
那么如果 Module 的 major 版本号虽然变成了 v2.x.x,但 Module 名字仍保持原样会怎么样呢?其他项目是否还可以引用呢?
假设 github.com/gin-contrib/sse 的当前版本为 v3.5.0,按照 Go module 的版本选择机制,其 Module 名字需要相应的改变为:github.com/gin-contrib/sse/v3,但是如果 module 名没改,还是 github.com/gin-contrib/sse,则在被形目引用的时候,就会在后面加上 incompatible 标记,变成
go
require (
github.com/gin-contrib/sse v3.6.0+incompatible
)
除了增加 +incompatible(不兼容)标识外,在其使用上没有区别
版本号
go module 拉取依赖包本质也是 go get 行为,go get 主要提供了以下命令:

go get 拉取依赖包取决于依赖包是否有发布的 tags:
- 拉取的依赖包没有发布 tags
默认取主分支最近一次的 commit 的 commit hash,生成一个伪版本号 - 拉取的依赖包有发布 tags
- 如果只有单个模块,那么就取主版本号最大的那个 tag
- 如果有多个模块,则推算相应的模块路径,取主版本号最大的那个 tag
没有发布的 tags:
bash
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
v0.0.0:根据 commit 的 base version 生成的:
- 如果没有 base version,那么就是 vx.0.0 的形式
- 如果 base version 是一个预发版本,那么就是 vx.y.z-pre.0 的形式
- 如果 base version 是一个正式发布的版本,那么它就 patch 号加 1,就是 vx.y.(z+1)-0 的形式
20190718012654 :是这次提交的时间,格式是 yyyyMMddhhmmss
fb15b899a751:是这个版本的 commit id,通过这个可以确定这个库的特定版本
有发布的 tags
bash
github.com/gin-contrib/sse v0.1.0
replace
因为某些未知原因,并不是所有的包都能直接用go get获取到,或者说是我们想要在官方的依赖库中集成一些我们自己的功能,这时我们就需要使用go modules的replace功能了
replace顾名思义,就是用新的package去替换另一个package,他们可以是不同的package,也可以是同一个package的不同版本。看一下基本的语法:
go
go mod edit -replace=old[@v]=new[@v]
old 是要被替换的 package,new 就是用于替换的 package。
replace 的使用步骤:
- 首先 go get new-package(如果你知道 package 的版本 tag,那么这一步其实可以省略,如果想使用最新的版本而不想确认版本号,则需要这一步)
- 然后查看 go.mod,手动复制 new-package 的版本号(如果你知道版本号,则跳过)
- go mod edit -replace=old[@v]=new[@v]
- 接着 go mod tidy 或者 go build 或者使用其他的 go tools,他们会去获取 new-package 然后替换掉 old-package
- 最后,在你的代码里直接使用 old-package 的名字,golang 会自动识别出 replace,然后实际你的程序将会使用 new-package,替换成功
exclude
这个特性是在 Go1.16 版本中引入,用来声明该第三方模块的某些发行版本不能被其他模块使用;
使用场景:发生严重问题或者无意发布某些版本后,模块的维护者可以撤回该版本,支持撤回单个或多个版本;
这种场景以前的解决办法:
维护者删除有问题版本的 tag,重新打一个新版本的 tag;
使用者发现有问题的版本 tag 丢失,手动介入升级,并且不明真因;
引入 retract 后,维护者可以使用 retract 在 go.mod 中添加有问题的版本:
go
// 严重bug...
retract (
v0.1.0
v0.2.0
)
重新发布新版本后,在引用该依赖库的使用执行 go list 可以看到 版本和"严重bug..."的提醒;
该特性的主要目的是将问题更直观的反馈到开发者的手中;
go.sum文件
Go 在做依赖管理时会创建两个文件,go.mod 和 go.sum ,go.mod 的重要性不言而喻,这个文件几乎提供了依赖版本的全部信息 。而 go.sum 则是记录了所有依赖的 module 的校验信息,以防下载的依赖被恶意篡改,主要用于安全校验。这个文件我们一般不需要编辑,更新以来的时候会自动更新。
每行的格式如下:
bash
<module> <version> <hash>
<module> <version>/go.mod <hash>
比如:
bash
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/etcd/api/v3 v3.6.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ=
go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4=
go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs=
go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI=
go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec=
go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
其中 module 是依赖的路径。
version 是依赖的版本号。如果 version 后面跟 /go.mod 表示对应哈希值是 module 的 go.mod 文件;否则,哈希值是 module 的 .zip 文件。
hash 是以 h1: 开头的字符串,表示生成 checksum 的算法是第一版的 HASH 算法(SHA256)。如果将来在 SHA-256 中发现漏洞,将添加对另一种算法的支持,可能会命名为 h2。
Go Modules使用
使用go modules的一个前置条件是Go语言版本大于等于Go1.11;
然后我们要检查环境变量GO111MODULE是否开启,执行go env查看:
go
go env | grep GO111MODULE
GO111MODULE="on"
如果GO111MODULE=off,可以执行一下命令打开
go
go env -w GO111MODULE=on
接下来就可以使用GO MODULE管理项目工程了,先创建一个项目目录
go
mkdir -p go_tour/main
cd go_tour
执行:
go
go mod init go_tour
运行结果:
go
go: creating new go.mod: module go_tour
会在go_tour目录下生成一个go.mod文件
在main包下建立main.go文件
go
package main
import "github.com/tidwall/gjson"
const json = `{
"name": {
"hello": "golang",
"key1": "value1"
},
"id": 12345
}`
func main() {
// gjson.Get(原始json字符串, 查询路径)
// 第一个参数:json(string)
// 第二个参数:"name.key1"(查询路径)
// 先进入 name 字段(对象),再在 name 对象里取 key1
// 相当于json["name"]["key1"]
value := gjson.Get(json, "name.key1")
// println(...):Go 的内置简单打印函数(不需要 fmt 包)。
// 实际项目更常用 fmt.Println,因为格式更稳定、功能更丰富
println(value.String())
}
然后在go_tour目录下执行go mod tidy命令,可以看到:
go
root@GoLang:~/proj/goforjob# go mod tidy
go: finding module for package github.com/tidwall/gjson
go: downloading github.com/tidwall/gjson v1.18.0
go: found github.com/tidwall/gjson in github.com/tidwall/gjson v1.18.0
go: downloading github.com/tidwall/pretty v1.2.0
go: downloading github.com/tidwall/match v1.1.1
可以看到此时的go.mod文件内容:
go
module go_tour
go 1.25.4
require github.com/tidwall/gjson v1.18.0
require (
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
)
依赖已经安装好,并且可以使用了,在go.mod下还可以看到一个go.sum文件对依赖包的校验,go.sum我们一般不用管
bash
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
此时执行go run main.go就可以看到执行结果了
bash
value1
以上就是用go module管理依赖库的简单用法。
之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!