Golang如何实现不被复制的结构体

其他教程   发布日期:2024年11月01日   浏览次数:246

这篇文章主要介绍“Golang如何实现不被复制的结构体”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Golang如何实现不被复制的结构体”文章能帮助大家解决问题。

不允许复制的结构体

sync包中的许多结构都是不允许拷贝的,比如

  1. sync.Cond
,
  1. sync.WaitGroup
,
  1. sync.Pool
, 以及sync包中的各种锁,因为它们自身存储了一些状态(比如等待者的数量),如果你尝试复制这些结构体:
  1. var wg1 sync.WaitGroup
  2. wg2 := wg1 // 将 wg1 复制一份,命名为 wg2
  3. // ...

那么你将在你的 IDE 中看到一个醒目的警告:

  1. assignment copies lock value to wg2: sync.WaitGroup contains sync.noCopy

IDE是如何实现这一点的呢?我们自己又能否利用这一机制来告诉别人,不要拷贝某个结构体呢?

(懒得看原理,只想知道怎么用,可以直接下划至结论部分)

实现原理

大部分编辑器/IDE都会在你的代码上运行

  1. go vet
,vet是Go官方提供的静态分析工具,我们刚刚得到的提示信息就是vet分析代码后告诉我们的。vet的实现在Go源码的
  1. cmd/vet
中,里面注册了很多不同类型的分析器,其中
  1. copylock
这个分析器会检查实现了
  1. Lock
  1. Unlock
方法的结构体是否被复制。

  1. copylock Analyser
  1. cmd/vet
中注册,具体实现代码在
  1. golang.org/x/tools/go/analysis/passes/copylock/copylock.go
中, 这里只摘抄部分核心代码进行解释:
  1. var lockerType *types.Interface
  2. func init() {
  3. //...
  4. methods := []*types.Func{
  5. types.NewFunc(token.NoPos, nil, "Lock", nullary),
  6. types.NewFunc(token.NoPos, nil, "Unlock", nullary),
  7. }
  8. // Locker 结构包括了 Lock 和 Unlock 两个方法
  9. lockerType = types.NewInterface(methods, nil).Complete()
  10. }

  1. init
函数中把包级别的全局变量
  1. lockerType
进行了初始化,
  1. lockerType
内包含了两个方法:
  1. Lock
  1. Unlock
, 只有实现了这两个方法的结构体才是
  1. copylock Analyzer
要处理的对象。
  1. // lockPath 省略了参数部分,只保留了最核心的逻辑,
  2. // 用来检测某个类型是否实现了Locker接口(Lock和Unlock方法)
  3. func lockPath(...) typePath {
  4. // ...
  5. // 如果传进来的指针类型实现了Locker接口, 就返回这个类型的信息
  6. if types.Implements(types.NewPointer(typ), lockerType) && !types.Implements(typ, lockerType) {
  7. return []string{typ.String()}
  8. }
  9. // ...
  10. }

  1. lockPath
会检测传入的参数是否实现了
  1. Lock
  1. Unlock
方法,如果是则返回类型的信息。而vet会在AST上每个需要检查的节点上调用
  1. lockPath
函数(如赋值、函数调用等场景)。如果在这些会导致复制的场景中,发现了锁结构体的复制,则会报告给用户:
  1. func run(pass *analysis.Pass) (interface{}, error) {
  2. // ...
  3. // 需要检查的节点
  4. switch node := node.(type) {
  5. // range语句
  6. case *ast.RangeStmt:
  7. checkCopyLocksRange(pass, node)
  8. // 函数声明
  9. case *ast.FuncDecl:
  10. checkCopyLocksFunc(pass, node.Name.Name, node.Recv, node.Type)
  11. // 函数字面量(匿名函数)
  12. case *ast.FuncLit:
  13. checkCopyLocksFunc(pass, "func", nil, node.Type)
  14. // 调用表达式(Foo(xxx))
  15. case *ast.CallExpr:
  16. checkCopyLocksCallExpr(pass, node)
  17. // 赋值语句
  18. case *ast.AssignStmt:
  19. checkCopyLocksAssign(pass, node)
  20. // 通用声明(import/const/type/var)
  21. case *ast.GenDecl:
  22. checkCopyLocksGenDecl(pass, node)
  23. // 复合常量({a,b,c})
  24. case *ast.CompositeLit:
  25. checkCopyLocksCompositeLit(pass, node)
  26. // return语句
  27. case *ast.ReturnStmt:
  28. checkCopyLocksReturnStmt(pass, node)
  29. // ...
  30. }
  31. // checkCopyLocksAssign 检查赋值操作是否复制了一个锁
  32. func checkCopyLocksAssign(pass *analysis.Pass, as *ast.AssignStmt) {
  33. for i, x := range as.Rhs {
  34. // 如果等号右边的结构体里有字段实现了Lock/Unlock的话,就输出警告信息
  35. if path := lockPathRhs(pass, x); path != nil {
  36. pass.ReportRangef(x, "assignment copies lock value to %v: %v", analysisutil.Format(pass.Fset, as.Lhs[i]), path)
  37. }
  38. }
  39. }

上面只列出了赋值操作的实现代码,其它类型的检查这里就不一一解释了,感兴趣的同学可以自行查看源码。

结论

只要你的IDE会帮你运行

  1. go vet
(目前主流的VSCode和GoLand都会自动帮你运行),你就能通过这个机制来提醒他人,尽量避免复制结构体。

如果你的结构体也因为某些原因,不希望使用者复制,你也可以使用该机制来警告使用者:

定义一个实现了

  1. Lock
  1. Unlock
的结构体
  1. type noCopy struct{}
  2. func (*noCopy) Lock() {}
  3. func (*noCopy) Unlock() {}

将其放入你的结构体中:

  1. // Foo 代表你不希望别人复制的结构体
  2. type Foo struct {
  3. noCopy noCopy
  4. // ...
  5. }

或直接让你的结构体实现

  1. Lock
  1. Unlock
方法:
  1. type Foo struct {
  2. // ...
  3. }
  4. func (*Foo) Lock() {}
  5. func (*Foo) Unlock() {}

这样别人在尝试复制

  1. Foo
的时候,就会得到IDE的警告信息了。

以上就是Golang如何实现不被复制的结构体的详细内容,更多关于Golang如何实现不被复制的结构体的资料请关注九品源码其它相关文章!