require 声明依赖模块路径与版本,是依赖解析基础;replace 仅在构建时重定向模块路径,不改变声明,二者必须共存——无 require 则 replace 无效,无 replace 则按默认方式拉取。

go.mod 中 require 和 replace 的基本作用关系
require 声明项目依赖的模块路径和版本,是构建时 Go 工具链解析依赖图的依据;replace 不改变依赖声明,只在构建阶段将某个模块路径映射到本地目录或另一仓库地址。两者必须共存才有意义——没有 require,replace 无从生效;只有 require 没有 replace,就走默认的代理或直接拉取远程 tag/commit。
常见错误现象:replace 写了但没效果,往往是因为对应模块没出现在 require 列表里(包括间接依赖),或者路径拼写不一致(比如大小写、.git 后缀、v0/v1 版本号差异)。
-
require必须显式存在,哪怕只是间接依赖,也建议用go get -u或手动补全后再加replace -
replace的左边必须与require中声明的模块路径完全一致(包括末尾/v2这类语义化版本后缀) - 本地路径替换时,目标目录下必须有合法的
go.mod文件,否则go build会报no required module provides package
本地开发调试时用 replace 指向未发布的模块
典型场景:你正在同时开发 github.com/myorg/lib 和 github.com/myorg/app,后者依赖前者,但 lib 还没打 tag,也不希望推到远程测试。这时在 app/go.mod 里写:
require github.com/myorg/lib v0.0.0-00010101000000-000000000000 replace github.com/myorg/lib => ../lib
注意:require 版本不能留空或写 latest,Go 要求必须是合法伪版本(如上例)或具体 tag;../lib 是相对于当前 go.mod 文件的路径,不是 GOPATH 或 module root。
立即学习“go语言免费学习笔记(深入)”;
- 如果
../lib下没有go.mod,运行go mod init github.com/myorg/lib初始化 - 修改本地
lib后,app不需要go mod tidy就能立即生效(因为replace是构建时重定向) - CI 环境中应避免提交含本地
replace的go.mod,可用go mod edit -dropreplace=...清理
解决私有模块或 fork 后的依赖冲突
当你 fork 了一个开源库(比如 github.com/oldorg/tool),修复 bug 后想让自己的项目用这个 fork,但原项目仍被其他依赖间接引入,就会产生版本冲突。此时需同时处理直接和间接依赖:
先运行 go list -m all | grep oldorg/tool 确认它是否在依赖图中;再在 go.mod 中写:
require github.com/oldorg/tool v1.2.3 replace github.com/oldorg/tool => github.com/myfork/tool v1.2.4
关键点在于右边的 github.com/myfork/tool 必须已发布 tag(如 v1.2.4),且其 go.mod 中的 module 名仍是 github.com/oldorg/tool(否则导入路径不匹配)。
- 若 fork 仓库的
go.mod改成了github.com/myfork/tool,则必须同步改所有import语句,否则编译失败 - 多个
replace可共存,但不能对同一模块路径写两条replace,否则go mod tidy会报错 - 使用
go mod graph | grep tool查看该模块是否被多个路径引入,必要时对每个路径单独replace
replace 在 vendor 模式下的行为差异
启用 go mod vendor 后,replace 依然生效,但 vendor 目录中存放的是替换后的代码(即 ../lib 或 github.com/myfork/tool 的内容),而不是原始 require 的模块。这意味着:
- 执行
go mod vendor前,确保所有replace目标可访问(比如本地路径存在、fork 仓库可 clone) - vendor 后的代码与
replace指向的内容强绑定,换环境时若缺失替换源,go build -mod=vendor会失败 - 不建议在 vendor 项目中长期保留指向本地路径的
replace,CI 构建时容易出错
真正容易被忽略的是:一旦用了 replace,整个构建链路就脱离了 go.sum 的原始校验逻辑——Go 不再验证被替换模块的 checksum,而是信任你提供的来源。这在安全审计或合规场景中需要额外说明。










