Golang新手提问:如何理解模块机制

Golang新手提问:如何理解模块机制 我正在尝试创建一个模块(目前)只包含一个文件,该文件中有一个返回整数的函数。目前我的代码如下:

package foopkg

func Foo() int {
    return 42
}

我将这段代码放入了文件 foo.go 中。

这个文件位于文件夹 mod2 中。

我执行了以下命令:

go mod init mod2
go install mod2

我期望的是它能将这个模块安装到我的 GOPATH 下的相应文件夹中,这样我就可以在另一个项目中通过 import "mod2/foopkg" 来使用它。

但这并没有成功。

显然,我完全误解并混淆了一切,否则它应该能正常工作。

  • 我可以创建没有 main 函数、不构建可执行文件的模块吗?
  • 我可以使用不指向互联网上任何公共主机或仓库的模块名称吗?
  • install 命令实际上是否做了我假设它做的事情?
  • 我需要怎么做才能让它正常工作?

更多关于Golang新手提问:如何理解模块机制的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

mod2 甚至不是一个有效的模块名。据我所知,模块名需要以类似域名的片段开头,以表明其托管位置。

无论如何,在使用模块时,GOPATH 仅用于一些缓存,不会被用于查找内容。

// 代码示例
func example() {
    // 这里可以放置Go代码
}

更多关于Golang新手提问:如何理解模块机制的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Bernd_K:

replace _ => ./

这确实是一个有趣的技巧!根据文档,replace 需要一个模块路径和一个替换路径作为 => 字符串两侧的参数。"_" 并不是一个有效的模块路径,因此我不指望 go mod 会永远支持这个技巧。

在这种情况下,我会问自己一个问题:

如果我并不打算让子包对其他项目可用,那我真的需要这个子包吗?

特别是对于小型项目,你并不一定需要把所有东西都拆分成包。相反,你可以先开始把所有代码都放在 package main 中,稍后再进行重构。如果之后有任何一段代码看起来值得放入一个具有明确定义的包 API 的独立包中,那么这个包可能就值得作为一个独立的包存在,并通过其完全限定的导入路径供其他项目使用。

因此,完全限定的导入路径这个要求起初看起来像是一种限制,但从长远来看可能会带来好处。

感谢您的回答。我正在慢慢取得进展,go.mod中的replace指令可能会有所帮助(我希望如此)。

我能够部分理解为什么它被设计成这样,但其中一些对我来说似乎过于严格。在谷歌上搜索“相对导入”这个话题后,我发现了许多讨论,支持和反对这种需求的意见都很强烈。虽然我多少能理解为什么在引用其他模块时(如我上面的情况)可能会受到限制,但与此同时,我偶然发现了另一件事,我认为它被限制得太过分了,而且(在我看来)完全没有明显的理由:同一模块内部的相对导入

考虑以下结构:

example.com/mymodule
  |_ main.go (package main)
    |_ sub
      |_ sub.go (package sub)

如果我想从main内部引用sub包,我不能直接导入"./sub",由于某种我尚未理解的原因,这是不允许的。

然而,在尝试了replace指令后,我想出了以下方法:

replace _ => ./

现在,我似乎可以导入"_/sub"了,这样我至少可以在不知道模块名称的情况下引用模块根目录。我可以运行go rungo build,它也不会报错。

这会在以后给我带来麻烦吗?如果这些内部的子包都不打算独立使用,而仅仅是为了命名空间和代码组织而存在,那么是否存在任何会导致问题的场景?

Bernd_K:

  • 我能否拥有不包含 main 函数且不构建可执行文件的模块?

是的。模块可以包含库包。实际上,这是模块的主要目的——将包组织成带版本的库。

  • 我能否使用不指向互联网上任何公共主机或仓库的模块名称?

简短回答:是的,但是… 详细回答:模块在设计时就考虑了包的可用性。因此,默认情况下,模块路径和导入路径应指向远程仓库。

然而,即使你没有将你的库模块放在任何远程仓库上,你仍然可以假装你有。

  1. 使用一个指向远程仓库的模块路径。该仓库不需要实际存在。
  2. 使用 replace 指令来告知 Go 工具链实际的模块位于你本地文件系统的某个位置。

示例:假设你的模块路径是 “github.com/yourorg/yourmodule”。

你的 Go 代码使用 import "github.com/yourorg/yourmodule" 作为导入路径,就像该模块及其包在远程可用一样。

然后执行:

go mod edit -replace "github.com/yourorg/yourmodule" => "your/local/path/to/yourmodule"

现在你的 go.mod 文件包含了一个提示,指示在本地获取该模块及其包。

优点:一旦你完成了本地开发,你就可以将模块推送到远程仓库,移除 go.mod 中的 replace 指令(你可以使用 -dropreplace 来做到这一点),然后就完成了。无需将所有导入路径从本地更改为远程。

  • install 命令是否真的做了我假设它做的事情?

go install 会编译一个二进制文件并将其安装到 $(GOPATH)/bin 中。它对库模块没有用处。

  • 我需要做什么才能使这个方案生效?

参见上文——使用远程模块路径和 go mod edit -replace

理解Go模块机制

你的问题涉及Go模块的几个核心概念。让我逐一解释并提供解决方案:

1. 模块的基本结构

首先,你的模块结构需要调整。一个Go模块应该有以下结构:

mod2/
├── go.mod
└── foopkg/
    └── foo.go

2. 正确的代码示例

go.mod 文件:

module mod2

go 1.21  // 根据你的Go版本调整

foopkg/foo.go 文件:

package foopkg

func Foo() int {
    return 42
}

3. 回答你的具体问题

Q: 可以创建没有main函数的模块吗? A: 可以。Go模块可以只包含库包,不需要main函数。

Q: 可以使用本地模块名称吗? A: 可以。模块名称可以是任意路径,但如果是本地使用,建议使用带版本后缀或使用replace指令。

Q: go install的作用是什么? A: go install 用于安装可执行程序,而不是库模块。对于库模块,应该使用 go mod tidy 和版本管理。

4. 解决方案

方案1:使用本地路径(推荐用于开发)

mod2项目中:

# 初始化模块
go mod init mod2

# 确保代码结构正确
mkdir -p foopkg
mv foo.go foopkg/

使用该模块的项目中:

// go.mod
module myapp

go 1.21

require mod2 v0.0.0

replace mod2 => ../mod2  // 指向本地路径
// main.go
package main

import (
    "fmt"
    "mod2/foopkg"
)

func main() {
    fmt.Println(foopkg.Foo())  // 输出: 42
}

方案2:使用版本标签

mod2项目中:

# 创建版本标签
git tag v0.1.0
git push origin v0.1.0

使用该模块的项目中:

// go.mod
module myapp

go 1.21

require mod2 v0.1.0

5. 完整工作流程示例

创建模块:

# 创建模块目录
mkdir mod2 && cd mod2

# 初始化模块
go mod init example.com/user/mod2

# 创建包目录
mkdir foopkg

# 创建foo.go
cat > foopkg/foo.go << 'EOF'
package foopkg

func Foo() int {
    return 42
}

func Bar() string {
    return "Hello from mod2"
}
EOF

使用模块:

# 创建新项目
mkdir myapp && cd myapp
go mod init myapp

# 编辑go.mod添加replace
cat > go.mod << 'EOF'
module myapp

go 1.21

require example.com/user/mod2 v0.0.0

replace example.com/user/mod2 => ../mod2
EOF

# 创建main.go
cat > main.go << 'EOF'
package main

import (
    "fmt"
    "example.com/user/mod2/foopkg"
)

func main() {
    fmt.Println(foopkg.Foo())  // 输出: 42
    fmt.Println(foopkg.Bar())  // 输出: Hello from mod2
}
EOF

# 下载依赖
go mod tidy

# 运行程序
go run main.go

6. 关键要点

  1. 模块名称:可以是任意路径,但建议使用完整的导入路径
  2. 包目录:每个包应该在自己的目录中
  3. 本地开发:使用 replace 指令指向本地路径
  4. 版本管理:生产环境使用版本标签(v1.0.0, v2.0.0等)

这样你就能正确创建和使用本地Go模块了。

回到顶部