Golang中文件夹名、包名与模块名的关系解析

Golang中文件夹名、包名与模块名的关系解析 我发现关于“文件夹名”、“包名”和“模块名”的文档让我有些困惑。以下是我的理解,如有错误,请指正。

这些概念加上工作区(workspace)的东西,比Python中的模块和包要复杂得多。

谢谢!

  1. 文件夹名 = 包名 = 模块名。 虽然文档没有明确说明,但这三者通常是一致的:文件夹名、包名和模块名(最后一部分)。

      folder name :                  ./greetings
      package name:            package greetings
      module name : module example.com/greetings
    
  2. main 包保持其名称不变,其余两者仍然匹配。

  3. import “name” 和 go mod init “name”,它们是相同的。

      import              "example.com/greetings"
      go mod init         "example.com/greetings"
    
  4. 子目录中的子包需要多一级路径。

      import              "example.com/hello/morestrings"
    
  5. 一个文件夹 = 一个包 = 一个模块。 但是子目录中的子包不需要运行 go mod init 命令。

$ pwd
/Users/ljh/Documents/work
$
$ find . -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g'
.
|____greetings
| |____go.mod
| |____greetings.go
|____hello
| |____morestrings
| | |____reverse.go
| |____go.mod
| |____hello.go
$
$
$ cat greetings/go.mod
module example.com/greetings
go 1.18
$
$
$ cat greetings/greetings.go
// https://golang.google.cn/doc/tutorial/create-module
package greetings
import "fmt"
func Hello(name string) string {
    message := fmt.Sprintf("Hi, %v. Welcome!", name)
    return message
}
$
$
$ cat hello/go.mod
module example.com/hello
go 1.18
replace example.com/greetings => ../greetings
require example.com/greetings v0.0.0-00010101000000-000000000000
$
$
$ cat hello/hello.go
// https://golang.google.cn/doc/tutorial/call-module-code
package main
import (
    "fmt"
    "example.com/greetings"
    "example.com/hello/morestrings"
)
func main() {
    message := greetings.Hello("Gladys")
    fmt.Println(message)
    fmt.Println(morestrings.ReverseRunes("!oG ,olleH"))
}
$
$
$ cat hello/morestrings/reverse.go
// https://golang.google.cn/doc/code
package morestrings
func ReverseRunes(s string) string {
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}
$

更多关于Golang中文件夹名、包名与模块名的关系解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

感谢Norbert,

我没有使用 go work init 命令。现在没问题了。

我还有一些困惑。

  1. 一个文件夹中的所有源文件都使用相同的包名,对吗?例如,在一个文件夹 hello 中,有两个文件:a.gob.go,它们都应该在包声明中使用名称 “hello”:
package hello
  1. 一个包由文件夹中所有使用相同包名的源文件组成,这样理解对吗?

  2. 包声明中的名称是否应该与文件夹名称相同?

更多关于Golang中文件夹名、包名与模块名的关系解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


ljh:

一个文件夹中的所有源文件都使用相同的包名,对吗?例如,在一个名为 hello 的文件夹中,有两个文件:a.go 和 b.go,它们都应该在 package 指令中使用名称 “hello”:

不是"应该",而是"必须"。如果同一个文件夹中存在不同的包,编译器会报错(允许使用 _test 后缀)。

ljh:

一个包由文件夹中所有使用相同包名的源文件组成,这样理解正确吗?

是的。

ljh:

package 指令中的名称是否应该与文件夹名称相同?

正如我所说,是的,除非你有非常充分的理由让它们不同。

模块中的代码可以正确构建。VSCode中的golang.go v0.32.0扩展却显示错误信息。看来即使是这个扩展也无法理解复杂的模块、包和工作区概念。

错误加载工作区:您当前不在模块内,也不在$GOPATH/src目录下。如果您正在使用模块,请将编辑器打开到您模块内的一个目录。

Screen Shot 2022-04-04 at 12.43.46

感谢Norbert,

我刚刚在规范中找到了相关说明:

共享相同包名的一组文件构成了一个包的实现。一个实现可能要求一个包的所有源文件都位于同一个目录中。

如果一个文件夹中,不同源文件的包声明里出现了不同的名称,它将无法构建,并显示如下信息:

... found packages ...

我进行了一个测试。实际上,文件夹名、包名和模块名可以不同。模块名用于 go mod init 命令和导入声明。

如果文件夹名与模块名不同,则需要使用 go mod edit -replace example.com/module_name=../folder_name

如果包名与模块名不同,导出的名称仍然以相同的方式被引用:package_name.Hello()。而导入声明看起来像 import "module_name"。这仅仅是不容易弄清楚包和导出的名称属于哪个模块。

Screen Shot 2022-04-04 at 21.25.44

你的编辑器工作区根目录不包含 go.mod 文件,因此它无法识别到一个模块。

针对你的问题,我大致回答一下,至少根据我的理解:

  1. 模块是你找到源代码和模块描述(即 go.mod 文件)的位置。模块本身也是一个包,并且每个包都可以包含子包,这些子包以树状结构组织。文件夹名称用于导入路径,而包名则在代码中用作限定符。假设有一个模块 example.com/foo/bar,其中包含 package quux,那么在导入文件中可以通过 quux 来访问。包名和文件夹名通常应该一致,除非你有充分的理由让它们不同。
  2. 我不确定你所说的“保留其名称”是什么意思。
  3. 它们不是。因为顶层模块中可以有(并且通常确实有)子包。
  4. 当然,子文件夹就是子文件夹,所以你需要将它们嵌套起来。这与你在本地文件系统上的操作没有什么不同。
  5. 一个文件夹就是一个包(加上测试),但一个模块可以有很多文件夹,因此也可以有很多子包。go mod init 命令创建的是一个模块,而不是一个包,所以你不需要为子包运行它。甚至对于一个模块,你也不一定需要它。理论上,你可以自己手动创建空的 go.mod 文件。我记得最小的内容就是 module 声明,后面跟着 go 指令。

在Go中,文件夹名、包名和模块名是三个不同的概念,它们的关系比你的理解要复杂一些。以下是详细的解析:

1. 模块名 (Module Name)

模块名在 go.mod 文件中定义,是项目的唯一标识符,通常采用反向域名格式:

// go.mod
module example.com/mymodule
go 1.21

2. 包名 (Package Name)

包名在每个Go源文件的顶部声明,定义了该目录下所有Go文件的命名空间:

// greetings.go
package greetings  // 包名,不一定要和文件夹名相同

3. 文件夹名 (Directory Name)

文件夹名是文件系统的目录名称,通常与包名一致,但不是强制的。

关键区别和示例

示例1:文件夹名 ≠ 包名

project/
├── go.mod              # module: example.com/project
└── pkg/
    └── utils/          # 文件夹名:utils
        └── helper.go   # 包名:tools (与文件夹名不同)
// helper.go
package tools  // 包名是tools,但文件夹名是utils

func HelperFunc() string {
    return "help"
}

导入时使用文件夹路径,但引用时使用包名:

import "example.com/project/pkg/utils"

func main() {
    tools.HelperFunc()  // 使用包名"tools",不是"utils"
}

示例2:多文件共享同一包

math/
├── arithmetic.go    # package math
├── algebra.go       # package math
└── geometry.go      # package math

所有文件必须声明相同的包名。

示例3:main包的特殊性

cmd/
├── server/          # 文件夹名:server
│   └── main.go      # 包名:main
└── client/          # 文件夹名:client  
│   └── main.go      # 包名:main

main包必须位于可执行文件的根目录。

示例4:导入路径解析

// go.mod
module github.com/user/repo

// 导入路径组成:
// github.com/user/repo  +  /cmd/app
import "github.com/user/repo/cmd/app"

// 实际查找:
// 1. 模块缓存
// 2. vendor目录  
// 3. 本地文件系统(使用replace时)

示例5:工作区模式 (Go 1.18+)

go.work
hello/
├── go.mod      # module: example.com/hello
└── hello.go
greetings/
├── go.mod      # module: example.com/greetings  
└── greetings.go
// go.work
go 1.21

use (
    ./hello
    ./greetings
)

你的理解修正:

  1. 文件夹名 ≠ 包名 ≠ 模块名

    • 模块名:项目级别的唯一标识
    • 包名:代码级别的命名空间
    • 文件夹名:文件系统组织方式
  2. import语句解析

    import "example.com/greetings"
    // Go会:
    // 1. 查找当前模块的go.mod
    // 2. 解析example.com/greetings对应的版本
    // 3. 下载到模块缓存或使用本地replace
    
  3. 子包不需要go mod init 正确,一个模块可以包含多个包,所有包共享同一个go.mod文件。

  4. 实际开发中的常见模式

myproject/
├── go.mod                    # module: github.com/username/myproject
├── cmd/
│   ├── server/              # 文件夹名:server
│   │   └── main.go          # 包名:main
│   └── cli/                 # 文件夹名:cli
│       └── main.go          # 包名:main
├── internal/                # 特殊文件夹,限制导入
│   └── utils/               # 文件夹名:utils
│       └── helper.go        # 包名:utils
├── pkg/
│   ├── api/                 # 文件夹名:api
│   │   └── api.go           # 包名:api
│   └── db/                  # 文件夹名:db
│       └── database.go      # 包名:db
└── go.sum

关键原则:

  • 模块名在go.mod中定义,全局唯一
  • 包名在.go文件中声明,同一目录下必须一致
  • 导入路径 = 模块名 + 目录路径
  • 引用时使用包名,不是文件夹名
回到顶部