不允许仿制的结构体
sync包中的许多结构都是不允许拷贝的,比方sync.Cond
,sync.WaitGroup
,sync.Pool
, 以及sync包中的各种锁,因为它们本身存储了一些状态(比方等待者的数量),假如你测验仿制这些结构体:
var wg1 sync.WaitGroup
wg2 := wg1 // 将 wg1 仿制一份,命名为 wg2
// ...
那么你将在你的 IDE 中看到一个醒目的正告:
assignment copies lock value to wg2: sync.WaitGroup contains sync.noCopy
IDE是怎么完成这一点的呢?咱们自己又能否运用这一机制来告知别人,不要拷贝某个结构体呢?
(懒得看原理,只想知道怎么用,能够直接下划至结论部分)
完成原理
大部分编辑器/IDE都会在你的代码上运转go vet
,vet是Go官方供给的静态剖析工具,咱们刚刚得到的提示信息就是vet剖析代码后告知咱们的。vet的完成在Go源码的cmd/vet
中,里边注册了很多不同类型的剖析器,其中copylock
这个剖析器会查看完成了Lock
和Unlock
办法的结构体是否被仿制。
copylock Analyser
在cmd/vet
中注册,具体完成代码在golang.org/x/tools/go/analysis/passes/copylock/copylock.go
中, 这儿只摘录部分中心代码进行解说:
var lockerType *types.Interface
func init() {
//...
methods := []*types.Func{
types.NewFunc(token.NoPos, nil, "Lock", nullary),
types.NewFunc(token.NoPos, nil, "Unlock", nullary),
}
// Locker 结构包括了 Lock 和 Unlock 两个办法
lockerType = types.NewInterface(methods, nil).Complete()
}
init
函数中把包级别的全局变量lockerType
进行了初始化,lockerType
内包含了两个办法: Lock
和Unlock
, 只要完成了这两个办法的结构体才是copylock Analyzer
要处理的目标。
// lockPath 省略了参数部分,只保留了最中心的逻辑,
// 用来检测某个类型是否完成了Locker接口(Lock和Unlock办法)
func lockPath(...) typePath {
// ...
// 假如传进来的指针类型完成了Locker接口, 就回来这个类型的信息
if types.Implements(types.NewPointer(typ), lockerType) && !types.Implements(typ, lockerType) {
return []string{typ.String()}
}
// ...
}
lockPath
会检测传入的参数是否完成了Lock
和Unlock
办法,假如是则回来类型的信息。而vet会在AST上每个需要查看的节点上调用lockPath
函数(如赋值、函数调用等场景)。假如在这些会导致仿制的场景中,发现了锁结构体的仿制,则会报告给用户:
func run(pass *analysis.Pass) (interface{}, error) {
// ...
// 需要查看的节点
switch node := node.(type) {
// range句子
case *ast.RangeStmt:
checkCopyLocksRange(pass, node)
// 函数声明
case *ast.FuncDecl:
checkCopyLocksFunc(pass, node.Name.Name, node.Recv, node.Type)
// 函数字面量(匿名函数)
case *ast.FuncLit:
checkCopyLocksFunc(pass, "func", nil, node.Type)
// 调用表达式(Foo(xxx))
case *ast.CallExpr:
checkCopyLocksCallExpr(pass, node)
// 赋值句子
case *ast.AssignStmt:
checkCopyLocksAssign(pass, node)
// 通用声明(import/const/type/var)
case *ast.GenDecl:
checkCopyLocksGenDecl(pass, node)
// 复合常量({a,b,c})
case *ast.CompositeLit:
checkCopyLocksCompositeLit(pass, node)
// return句子
case *ast.ReturnStmt:
checkCopyLocksReturnStmt(pass, node)
// ...
}
// checkCopyLocksAssign 查看赋值操作是否仿制了一个锁
func checkCopyLocksAssign(pass *analysis.Pass, as *ast.AssignStmt) {
for i, x := range as.Rhs {
// 假如等号右边的结构体里有字段完成了Lock/Unlock的话,就输出正告信息
if path := lockPathRhs(pass, x); path != nil {
pass.ReportRangef(x, "assignment copies lock value to %v: %v", analysisutil.Format(pass.Fset, as.Lhs[i]), path)
}
}
}
上面只列出了赋值操作的完成代码,其它类型的查看这儿就不一一解说了,感兴趣的同学能够自行查看源码。
结论
只要你的IDE会帮你运转go vet
(目前主流的VSCode和GoLand都会自动帮你运转),你就能通过这个机制来提醒他人,尽量避免仿制结构体。
假如你的结构体也因为某些原因,不期望运用者仿制,你也能够运用该机制来正告运用者:
界说一个完成了Lock
和Unlock
的结构体
type noCopy struct{}
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
将其放入你的结构体中:
// Foo 代表你不期望别人仿制的结构体
type Foo struct {
noCopy noCopy
// ...
}
或直接让你的结构体完成Lock
和Unlock
办法:
type Foo struct {
// ...
}
func (*Foo) Lock() {}
func (*Foo) Unlock() {}
这样别人在测验仿制Foo
的时候,就会得到IDE的正告信息了。