翻译自 https://blog.logrocket.com/whats-new-in-go-1-16/ 发本文时距go1.16 release已经快两周了,今天也得空来翻一翻 学习下。
最新的Go版本1.16版在Go 1.15之后六个月到货。它的大部分更改是在工具链,运行时和库的实现中进行的。与往常一样,该版本保留了Go 1兼容性的承诺。作为Go编程语言的第17个主要版本Go 1.16。这是一项重大升级,为语言带来了许多期待已久的功能和改进。默认情况下,启用模块感知模式,Apple silicon
支持实时可用,静态资源可以嵌入到二进制文件中,并且io / ioutil软件包中的方法已重新组织,因此现在具有逻辑意义。在本文中,我们将介绍此版本中的一些重点内容。
自成立以来,Go一直优先考虑在不同操作系统和体系结构之间的可移植性,这体现在Go对各种操作系统和体系结构组合的支持上。具体可以参见(https://golang.org/doc/install/source#environment)
在过去的几个月中,由于Apple在CPU,GPU和电池性能方面的惊人飞跃,其首款64位ARM Mac的发布一直是开发人员中最主要的话题之一。Go项目迅速做出反应,通过添加GOOS = darwin和GOARCH = arm64环境变量对ARM Mac的本机进行支持。
如果你拥有M1 Mac,则现在可以在计算机上本地构建和运行Go程序;如果你使用的是其他操作系统或基于Intel的Mac,需要针对ARM Mac为你的程序构建二进制文件时,可以使用下面命令:
GOARCH=arm64 GOOS=darwin go build myapp
关于使用Go最好的事情之一就是可以将已编译程序作为单个不需要依赖的二进制文件进行分发和执行。当程序依赖于静态文件,比如HTML模板,数据库迁移文件,Web程序资源(例如JavaScript或图像文件等文件)时,通常必须将它们与二进制文件一起分发,除非将它们嵌入到二进制文件中,否则这种优势会有所抵消。
之前我们或许通过第三方软件包(例如pkger或packr)的帮助可以完成这项任务。但随着Go 1.16的发布,现在可以通过新的embed包原生得将静态文件本地包含在Go二进制文件中。
这里我们举个栗子来演示一下这个功能。假设您有一个sample.txt文件,其内容如下所示:
Hello from text file
并在同一目录中的main.go
文件包含以下内容:
package main
import (
_ "embed"
"fmt"
)
//go:embed sample.txt
var text string
func main() {
fmt.Print(text)
}
放在变量text
上方的go:embed
指令指示编译器将sample.txt
文件的内容作为字符串嵌入到text
变量中。如果使用go build
生成程序并将生成的二进制文件移动到其他位置,则会注意到执行该程序仍然会将嵌入式文件的内容打印到标准输出中。这是因为sample.txt
文件的所有内容都已包含在二进制文件中,因此可以按原样分发:
$ mv main /tmp
$ cd /tmp
$ ./main
Hello from text file
举一个更现实的例子,假设我们有一个具有以下目录结构的Web应用程序项目:
.
├── assets
│ ├── css
│ │ └── style.css
│ └── js
│ └── script.js
├── go.mod
├── index.html
├── main.go
└── random
我们可以将assets
文件夹中所有文件和index.html
文件嵌入二进制文件,如下所示
package main
import (
"embed"
"net/http"
)
//go:embed assets/*
var assets embed.FS
//go:embed index.html
var html []byte
func main() {
fs := http.FileServer(http.FS(assets))
http.Handle("/assets/", fs)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/html")
w.Write(html)
})
http.ListenAndServe(":8080", nil)
}
像上面的示例一样,FS
类型对于嵌入文件树(例如Web服务的资源文件)很有用。对于嵌入单个文件(如index.html),最好使用字符串或[] byte类型的变量。如果您构建并执行该程序,并导航至http:// localhost:8080,则将看到正确的静态的HTML文件的内容:
$ go version
go version go1.16rc1 linux/amd64
$ go build -o main
$ mv main /tmp
$ cd /tmp && ./main
browser
你必须先导入embed包,然后才能使用// go:embed
指令。否则你将得到一个错误:
$ go run main.go
# command-line-arguments
./main.go:8:3: //go:embed only allowed in Go files that import "embed"
如果你不直接使用嵌入的任何导出标识,请确保在导入语句前添加下划线:
import (
_ "embed"
)
要注意的另一件事是//go:embed
仅适用于程序包级变量。如果你尝试在函数中使用它,则代码将无法编译:
package main
import (
_ "embed"
"fmt"
)
func main() {
//go:embed index.html
var html string
fmt.Println(html)
}
输出
$ go run main.go
# command-line-arguments
./main.go:9:4: go:embed cannot apply to var inside func
Go 1.11中Go模块的引入预示着将移除GOPATH
语义用于依赖管理的转变。在该初始发行版和Go 1.12中,模块仍处于实验阶段,必须使用环境变量GO111MODULE = on
激活。Go 1.13确保只要当前工作目录或父目录中存在go.mod文件,即使该目录位于GOPATH中,模块模式都会自动激活,而Go 1.14和1.15仍然如此。
随着Go 1.16的发布,GO111MODULE
变量现在默认为on
,这意味着默认情况下启用了模块模式,而不管当前目录中是否存在go.mod
文件。如果要还原为以前的行为,请将GO111MODULE
设置为auto
。
在其他相关更改中,默认情况下,go build
和go test
将不再修改go.mod
和go.sum
文件。相反,如果需要添加或更新模块要求或校验和,将会报告错误。然后,您可以使用go mod tidy
或go get
来相应地调整需求。
go install
命令现在也可以识别模块,这意味着它不会影响当前目录或任何父目录(如果有)中的go.mod文件。而且,现在可以将版本号作为后缀。例如:
$ go install github.com/example@v3.5.0
在Go 1.16中,不赞成使用go get
生成和安装软件包,而建议使用go install
。在将来的发行版中,go get将不再能够构建和安装软件包,但将在启用-d标志的情况下按当前方式运行,这意味着它将在不构建软件包的情况下调整当前模块的依赖性。-insecure或-i标志也已被弃用。
从Go 1.16开始,go.mod
文件中将提供新的收回指令。这使软件包作者可以将较旧的软件包版本标记为不安全或已损坏,或者将版本标记为无意中发布。使用方法如下:
module example
go 1.16
retract v1.1.1 // retract single version
retract [v1.1.1, v1.3.2] // closed interval, so anything between v1.1.1 and v1.3.2
现在,Go 1.16中已弃用整个ioutil
软件包,其功能已移至其他软件包。需要明确的是,利用此程序包的现有代码将继续起作用,但是建议你迁移到io
和os
程序包中的新定义。
使用ioutil
进行代码迁移应该很简单。此包中的一种流行方法是ReadAll()
,该方法通常用于将整个请求正文从HTTP请求读取到一个字节片。该方法已移至io
包:
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
// old way: body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
导出的io/ioutil
方法的新位置的完整列表如下所示:
添加了io/fs
和testing/testfs
软件包,因此不排除对Go标准库的改进。这些新软件包使在测试中抽象文件系统变得更加容易,这使它们无论在哪个操作系统上运行都更易于重现。访问文件的速度也将大大提高,之后您无需清理临时文件。
在Go 1.16
之前,模拟文件系统的任务通常落在流行的afero
软件包中,该软件包提供了实现真实或模拟文件系统必须满足的接口类型。它还提供了一些提供此接口的常用实现,例如afero.MemMapFs
,这是一个内存支持的文件系统,可用于测试中的模拟。
与afero的Fs
接口在撰写本文时定义13种方法不同,io/fs
包提供的FS
接口非常简单:
type FS interface {
Open(name string) (File, error)
}
实现此接口所需要做的只是一个Open
方法,该方法可以在路径中打开文件,并返回一个实现fs.File
接口的对象,如下所示:
type File interface {
Stat() (FileInfo, error)
Read([]byte) (int, error)
Close() error
}
你会从上面的接口中注意到的一件事是缺少允许你修改文件的方法。那是因为io / fs软件包仅提供文件系统的只读接口,而Afero
则在这方面更加完善。做出此决定的原因是,与涉及更多内容的写相比,读更容易抽象。
All this is to say I think the design decision of limiting this proposal to read-only operations is a good one. In fact it was the key insight (by @robpike) that unlocked years of being stuck and let us make any progress defining this interface at all.
现在,当在测试或基准测试期间创建的goroutine
,对test.T
或testing.B
的Fatal
,Fatalf
或FailNow
方法进行的调用无效时,vet
工具会发出警告。这是因为这些方法退出了goroutine,而不是test
或benchmark
函数:
package main
import "testing"
func TestFoo(t *testing.T) {
go func() {
if true {
t.Fatal("Test failed") // exits the goroutine instead of TestFoo
}
}()
}
fatal
可以调用t.Error()
方法替换上面的方法来发出测试失败的信号,并使用return
语句来退出goroutine
:
package main
import "testing"
func TestFoo(t *testing.T) {
go func() {
if true {
t.Error("Test failed.")
return
}
}()
}
除了上述更新之外,还对标准库软件包进行了一些较小的更新和修复。完整的更改列表可在发行说明中找到(https://tip.golang.org/doc/go1.16#minor_library_changes)。
如果要浏览此版本中包含的错误修复和功能的完整列表,建议你查看GitHub
上Go 1.16
里程碑中已解决的问题列表。
最后祝大家猿宵节快乐~