为什么Canonical Import Path注释在Go中不再必要

本文永久链接 – https://tonybai.com/2024/10/02/why-canonical-import-paths-no-longer-necessary-in-go

Go语言自推出以来,一直以其简洁和高效的包管理系统著称。在Go 1.11版本之前,Canonical Import Path注释曾是一个重要的工具,用于防止包路径的导入冲突。然而,随着Go Modules的引入,这一工具的作用逐渐被淡化。那么Canonical Import Path注释是否还有必要存在呢?在这篇文章中,我就来介绍一下Canonical Import Path的历史及作用,并通过在Go Modules环境下的向后兼容性测试,讨论是否仍有必要继续使用这一注释。

1. 什么是Canonical Import Path注释?

Go在1.4版本中增加了Canonical Import Path,Canonical Import Path用于解决同一个包可能被通过多个导入路径导入的问题。比如当代码托管在像github.com这样的服务上时,导入路径会包含托管服务的域名,比如“github.com/rsc/pdf。但是Go开发者也可以为同一个包提供一个“自定义”或“vanity”导入路径,例如rsc.io/pdf。这样就会产生两个有效的导入路径,这会带来以下问题:

  • 同一个程序中可能会通过不同路径导入同一个包,造成不必要的重复。
  • 使用非官方路径时可能会错过包更新,因为路径没有得到正确识别。
  • 将包迁移到另一个托管服务时,可能会中断使用旧路径的客户端。

为了解决这个问题,Go 1.4引入了Canonical Import Path注释。在包声明中加上注释后,如果通过非Canonical Import Path导入包,Go命令将拒绝编译导入包的程序。

Canonical Import Path的语法很简单,在包声明的注释部分加上标识。例如,对于rsc.io/pdf包,声明可以写成:

package pdf // import "rsc.io/pdf"

这样,Go命令就会拒绝编译任何通过github.com/rsc/pdf路径导入的包,确保代码可以在不破坏用户代码的前提下自由迁移。

2. Go Modules及其对导入路径的影响

Go 1.11引入Go Modules后,Go通过go.mod文件管理包的依赖关系和版本,极大简化了包的管理过程。通过在go.mod中定义模块的根路径,Go Modules可以自动指示项目中所有包的导入路径,并且是唯一的,这使得Canonical Import Path在Go Modules环境下基本没什么必要性了

例如,假设go.mod文件定义了以下模块路径:

// go.mod
module rsc.io/pdf

那么位于项目根目录下的包的导入路径将被自动解析为rsc.io/pdf,避免了包路径冲突问题。因此,在Go Modules的支持下,手动设置Canonical Import Path注释变得不再必要。

Go提供了Go1向后兼容,在Go module下使用Canonical Import Path注释会是什么情况呢?我们接下来来看看。

3. 在Go Modules下使用Canonical Import Path注释

虽然Go Modules简化了包管理,很多老项目仍然保留了Canonical Import Path注释。为了验证在Go Modules环境下继续使用这些注释的兼容性,我进行了以下测试(测试环境使用的是包括Go 1.23.0版本在内的多个Go版本)。

在这个测试中,我们保持项目中的Canonical Import Path注释不变,看看它是否影响在Go Modules环境中的编译和运行。

这里我们直接使用位于github.com/rsc/pdf中的pdf包,该包在read.go文件中使用了Canonical Import Path注释:

// https://github.com/rsc/pdf/blob/master/read.go
package pdf // import "rsc.io/pdf"

我们先用Go 1.11版本之前的Go版本测试一下导入rsc.io/pdf包。由于Go 1.11版本之前依然采用的是GOPATH构建模式,因此需要先将github.com/rsc/pdf下载到\$GOPATH/src的github.com/rsc下,因为GOPATH模式下,go编译器回到\$GOPATH路径下搜寻依赖包。

接下来,我们建立demo1目录,并直接将github.com/rsc/pdf/pdfpasswd/main.go复制到demo1目录下,该main.go导入了”rsc.io/pdf”,我们将其改为导入”github.com/rsc/pdf”:

// demo1/main.go

package main

import (
    "flag"
    "fmt"
    "log"
    "os"

    "github.com/rsc/pdf"
)

var (
    alphabet  = flag.String("a", "0123456789", "alphabet")
    maxLength = flag.Int("m", 4, "max length")
)

func usage() {
    fmt.Fprintf(os.Stderr, "usage: pdfpasswd [-a alphabet] [-m maxlength] file\n")
    os.Exit(2)
}

func main() {
    log.SetFlags(0)
    log.SetPrefix("pdfpasswd: ")

    flag.Usage = usage
    flag.Parse()
    if flag.NArg() != 1 {
        usage()
    }

    f, err := os.Open(flag.Arg(0))
    if err != nil {
        log.Fatal(err)
    }

    last := ""
    alpha := *alphabet
    ctr := make([]int, *maxLength)
    pw := func() string {
        inc(ctr, len(alpha)+1)
        for !valid(ctr) {
            inc(ctr, len(alpha)+1)
        }
        if done(ctr) {
            return ""
        }
        buf := make([]byte, len(ctr))
        var i int
        for i = 0; i < len(buf); i++ {
            if ctr[i] == 0 {
                break
            }
            buf[i] = alpha[ctr[i]-1]
        }
        last = string(buf[:i])
        println(last)
        return last
    }
    st, err := f.Stat()
    if err != nil {
        log.Fatal(err)
    }
    _, err = pdf.NewReaderEncrypted(f, st.Size(), pw)
    if err != nil {
        if err == pdf.ErrInvalidPassword {
            log.Fatal("password not found")
        }
        log.Fatal("reading pdf: %v", err)
    }
    fmt.Printf("password: %q\n", last)
}

func inc(ctr []int, n int) {
    for i := 0; i < len(ctr); i++ {
        ctr[i]++
        if ctr[i] < n {
            break
        }
        ctr[i] = 0
    }
}

func done(ctr []int) bool {
    for _, x := range ctr {
        if x != 0 {
            return false
        }
    }
    return true
}

func valid(ctr []int) bool {
    i := len(ctr)
    for i > 0 && ctr[i-1] == 0 {
        i--
    }
    for i--; i >= 0; i-- {
        if ctr[i] == 0 {
            return false
        }
    }
    return true
}

然后,我们先用Go 1.10.8版本编译该main.go,得到下面结果:

$go run main.go
main.go:9:2: code in directory /Users/tonybai/Go/src/github.com/rsc/pdf expects import "rsc.io/pdf"

我们看到go 1.11之前的版本对pdf包声明的Canonical Import Path做了检查,如果实际导入路径(github.com/rsc/pdf)与其不符,Go编译器会报错!

接下来,我们来看看切换到go module模式后的编译结果,这里我们使用Go 1.12.7版本。我们创建go.mod文件:

// demo1/go.mod
module demo1

go 1.12

编译执行main.go:

$go run main.go
go: finding github.com/rsc/pdf v0.1.1
go: downloading github.com/rsc/pdf v0.1.1
go: extracting github.com/rsc/pdf v0.1.1
usage: pdfpasswd [-a alphabet] [-m maxlength] file
exit status 2

我们看到,go 1.12.7可以成功编译并运行main.go,即便后者没有使用Canonical Import Path导入pdf包。

而用最新的Go 1.23.0编译和运行,也是没问题的:

$go run main.go
usage: pdfpasswd [-a alphabet] [-m maxlength] file
exit status 2

由此可以得出结论:go module模式下,Go编译器已经不再校验导入包的Canonical Import Path了。

并且,即便main.go同时导入rsc.io/pdf和github.com/rsc/pdf也是没问题的:

import (
    "flag"
    "fmt"
    "log"
    "os"

    "github.com/rsc/pdf"
    _ "rsc.io/pdf"
)

这是因为github.com/rsc/pdf下没有go.mod,go编译器无法识别github.com/rsc/pdf和rsc.io/pdf是同一个包。我们再看一个uber-go/zap的例子:

package main

import (
    "fmt"

    _ "github.com/uber-go/zap"
    _ "go.uber.org/zap"
)

func main() {
    fmt.Println("hello, zap!")
}

针对这个main.go所在的go module进行go mod tidy,我们会得到如下错误结果:

$go mod tidy
go: finding module for package go.uber.org/zap
go: finding module for package github.com/uber-go/zap
go: downloading go.uber.org/zap v1.27.0
go: downloading github.com/uber-go/zap v1.27.0
go: found github.com/uber-go/zap in github.com/uber-go/zap v1.27.0
go: found go.uber.org/zap in go.uber.org/zap v1.27.0
go: demo imports
    github.com/uber-go/zap: github.com/uber-go/zap@v1.27.0: parsing go.mod:
    module declares its path as: go.uber.org/zap
            but was required as: github.com/uber-go/zap

我们看到:go命令检测出了github.com/uber-go/zap仓库下的go module是go.uber.org/zap,我们只能使用go.uber.org/zap作为zap包的导入路径。

4. 是否应移除Canonical Import Path注释?

在Go Modules已经成为Go项目默认包管理方式的背景下,Canonical Import Path的使用显得冗余。虽然保留这些注释不会导致兼容性问题,但移除它们可以让项目代码更加简洁,减少不必要的历史包袱。

对于已经迁移到Go Modules的老项目,开发者可以考虑逐步移除Canonical Import Path注释。对于新项目,则是没有必要添加Canonical Import Path注释,Go Modules已经足够强大,能够管理包路径和依赖;如果项目的用户仍依赖旧版Go工具链(GOPATH模式),保留Canonical Import Path注释则可以作为一种保险措施。

5. 小结

Canonical Import Path注释在Go 1.4引入时是为了解决包路径冲突和包迁移问题。然而,随着Go Modules的引入,包管理和路径控制功能逐渐被自动化,Canonical Import Path的作用显得不再必要。对于现代Go项目,开发者应考虑移除这一冗余的注释,这不仅是代码简化的一部分,也反映了Go生态系统中包管理方式的演进,并使项目更加符合Go语言的现代开发环境。


Gopher部落知识星球在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时,我们也会加强代码质量和最佳实践的分享,包括如何编写简洁、可读、可测试的Go代码。此外,我们还会加强星友之间的交流和互动。欢迎大家踊跃提问,分享心得,讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落,享受coding的快乐! 欢迎大家踊跃加入!

img{512x368}
img{512x368}

img{512x368}
img{512x368}

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻) – https://gopherdaily.tonybai.com

我的联系方式:

  • 微博(暂不可用):https://weibo.com/bigwhite20xx
  • 微博2:https://weibo.com/u/6484441286
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • Gopher Daily归档 – https://github.com/bigwhite/gopherdaily
  • Gopher Daily Feed订阅 – https://gopherdaily.tonybai.com/feed

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

跟上Go演进步伐,你只需要关注这几件事儿

本文永久链接 – https://tonybai.com/2024/09/30/how-to-keep-up-with-go-evolution

Go语言以其简洁、高效和强大的特性赢得了众多开发者的青睐。与许多主流编程语言有着明确的演进Roadmap或下一个版本spec不同,Go的演进过程更加独特、灵活与开放。这种看起来不那么正式和严肃的演进方式却也能让Go快速响应开发者的需求,同时保持语言的稳定性和一致性。

作为一名Go开发者,跟上Go的演进步伐,甚至是参与到这个激动人心的过程中来,不仅能让你更好地利用语言的新特性,还能帮助你更深入地理解Go的设计哲学。

但很多Go开发者只知道每年Go有两次的大版本Release,并通过大版本的Release Notes来了解Go的演进。这无可厚非,但对于那些想及时跟进Go演进的Gopher来说,光有一年两次的Release Notes还是不够的的,很难及时跟进Go的演进决策。

但如果直接到Go语言项目的issue中去翻阅,面对Go丰富的社区讨论和频繁的更新,你可能会感到无从下手。别担心,本文将为你指明方向,让你只需关注几个关键点,就能轻松跟上Go的演进步伐。

1. 开发计划早知道

Go的版本规划具有很高的灵活性。每个Go 1.x版本在开发前,Go语言项目相关人员都会在golang-dev讨论组上发布一个帖子,这个帖子通常的标题为”Planning Go 1.x”,例如”Planning Go 1.23″,如下图:

很多contributor,无论是Go团队的,还是外部贡献者的,会在该帖子下面留下自己的plan(注意:这些plan中的特性可不一定会在最终的版本中发布),然后等main tree开放后,就会将已经准备完毕的cl(changelist) merge到main tree中去,或开始提交cl,等待Go团队或社区的开发者进行评审。

当然对于Go 1.x这样的大版本,Go团队会在github建立专门的milestone跟踪,大家也可以在对应的milestone中看到该版本带来的新特性等,下图是目前正在积极开发的Go 1.24版本里程碑

通过查看这些Plan或定期查看Go 1.x里程碑,你可以提前了解Go的发展方向,为新版本的到来做好准备。

当然如果要了解那些更早的Go演进的决策,我们还得关注和跟踪下面的Proposal Project看板和三个关键的issue。

2. Proposal Project看板和三个关键的Issue

Go在早期并没有规范的proposal提案流程,更多是由Rob Pike、Robert Griesemer等三个Go语言之父,外加Ian Taylor和Russ Cox讨论确定,这一状态在Russ Cox建立明确的Go proposal提案流程后结束,提案流程是Go团队审查提案并决定接受或拒绝提案的过程。Russ Cox在提案流程中明确了Go项目的开发过程是设计驱动(design-driven)的,必须首先对语言、库或工具的重大更改进行讨论(包括Go语言项目主仓库和所有golang.org/x仓库中的API更改,以及对go command的命令行更改),并在实现这些设计之前进行正式记录。

Go团队目前使用Proposal Project看板和GitHub Issues来追踪语言的演进,下面我们来看看这个看板和值得关注的三个Issue。

2.1 Proposal Project看板

Proposal Project看板是Go团队跟踪proposal的全局视图,当然要理解该看板,我们需要先来简单看看Go的proposal流程以及每个提案的生命周期是怎样的。

Go Proposal流程并不复杂,可以概括为下面这个示意图:

该流程图展示了Go提案流程的几个主要步骤:

  • 任何人都可以作为提案作者,在Go项目上创建一个简短的issue来描述提案。
  • Go团队成员以及任何Go社区成员在issue上进行初步讨论,由一组人组成的Go提案审核委员会决定是接受提案、拒绝提案,还是需要进一步的设计文档。
  • 如果需要进一步的设计文档,提案作者会撰写一个详细的设计文档。
  • 在设计文档的评论减少/收敛后(意见趋于一致后),由Go提案审核委员会会进行最终讨论,决定接受或拒绝提案。

Go提案审查委员会使用GitHub项目看板来跟踪提案的状态并管理提案的生命周期(如下图所示):

该看板针对每个提案issue设置了几个生命周期状态:

  • Incoming:新提交的提案
  • Active:正在积极讨论的提案
  • Likely Accept / Likely Decline:可能被接受或拒绝的提案
  • Accepted / Declined:已被接受或拒绝的提案
  • Hold:需要设计修订或需要几周或更长时间才能获得附加信息的提案,这类提案一旦准备就绪,还会回到Active状态

了解了上述Go提案与审核流程,再看下面的几个关键Issue就容易多了。

2.2 proposal: review meeting minutes(33502)

该issue于2019年8月创建,其创建者为前Go团队技术负责人Russ Cox。这是目前Go语言项目最核心的追踪Issue,它记录了Go提案审查会议的纪要,通常每周更新一次(如下图所示):

我们看到内容包括:

  • 发布当周已经决策为Accepted和Declined的proposal列表
  • 后续Likely Accept和Likely Decline的proposal列表
  • 正处于Active讨论的proposal列表
  • 当前处于Hold状态的proposal列表

和Go提案看板不同,该issue是对提案Issue的状态变更的记录,Gopher可以第一时间看到每周Go提案的状态更新。

由于Russ Cox已经辞去了Go团队技术负责人的头衔,从2024年9月下旬开始,Go团队新的技术负责人Austin Clements将继续主持提案审核会议,并更新该Issue。

除了Review meeting minutes这个重要的issue外,还有两个issue值得我们关注,通过它们,我们可以及时了解到Go编译器和运行时的演进以及Go语法特性的演进。

2.3 Go compiler and runtime meeting notes(43930)

Go编译器和运行时团队定期(大约每周)召开会议,讨论Go编译器和运行时的后续开发和演进事宜,该会议是Google Go团队的内部会议,但Go团队觉得Go社区有必要了解这个会议上的一些讨论议题、过程与会议结论,从而知道Go编译器和运行时团队正在以及将要做什么。

于是前Go团队成员Jeremy Faller于2021年1月创建了该Issue,向Go社区发布Go编译器和运行时的最新演进动向。

之前Go编译器和运行时团队的负责人是Austin Clements,如今是CherryMui

2.4 spec: language change review meeting minutes(33892)

编译器和运行时之外,Gopher最关心的就是Go语法的演进以及Go语言规范的变更,这个事儿是由Go语言之父之一的Robert Griesemer亲自抓的。在2019年8月,Robert Griesemer就建立了跟踪Go语法变化的issue,当然最初是要跟踪Go2的演进,后来Go泛型落地后,Go2彻底融入了Go1,该issue也就变成了跟踪Go语法演进的Issue。Robert Griesemer主持的Go语言变更审查会议每月举行一次,并将会议讨论的记录发布到该Issue上。

3. Discussion与Russ Cox博客

关于Go语言演进的动向,还有两个渠道可以关注,一个是Go团队在github repo上发起的discussionRuss Cox在2021年7月启用了discussion,旨在寻找一个地方来扩大许多人可能想要参与的讨论。当前,该discussion仅针对非常有限的事项添加讨论,并且只有少数Go核心团队的人才有发起discussion的权限。一些在前几个版本的重要语言特性变化以及标准库的变化,都在这里进行了充分的讨论,比如loopvar语义修正自定义iterator开启标准库major版本更新的math/rand/v2以及gonew工具等。

另外一个则是Russ Cox的博客,作为Go项目团队前技术负责人,作为Rob Pike的接班人,Russ Cox很好地完成了承上启下的作用,并为Go的演进和发展确立了演进框架、方法以及方向。Russ Cox经常在自己的博客上先“憋大招,做铺垫”!最典型的就是vgo,也就是go module的前身,在短短几周内Russ Cox在博客上发表了7篇关于vgo的设计思路文章,为后来Go module的落地奠定了基础,至此基本上不再有Gopher抱怨Go依赖管理了。Russ Cox现已辞去Go技术负责人的头衔,后续是否还能在他的博客上看到Go相关的新特性的设计,让我们拭目以待!

4. 小结

在快速发展的技术环境中,Go语言以其独特的演进方式和灵活的开发计划,吸引了越来越多的开发者。本文介绍了如何及时有效地跟踪Go的演进的方法,包括关注大版本开发计划、Proposal Project看板和关键的issue,帮助Gopher及时了解语言的新特性与设计决策。通过参与讨论和关注Go团队的动态,开发者不仅能掌握最新的语言更新,还能深入理解Go的设计哲学和发展方向。希望每位Gopher都能抓住这些资源,与Go语言共同成长,提升自己的开发技能。


Gopher部落知识星球在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时,我们也会加强代码质量和最佳实践的分享,包括如何编写简洁、可读、可测试的Go代码。此外,我们还会加强星友之间的交流和互动。欢迎大家踊跃提问,分享心得,讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落,享受coding的快乐! 欢迎大家踊跃加入!

img{512x368}
img{512x368}

img{512x368}
img{512x368}

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻) – https://gopherdaily.tonybai.com

我的联系方式:

  • 微博(暂不可用):https://weibo.com/bigwhite20xx
  • 微博2:https://weibo.com/u/6484441286
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • Gopher Daily归档 – https://github.com/bigwhite/gopherdaily
  • Gopher Daily Feed订阅 – https://gopherdaily.tonybai.com/feed

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats