Golang实现不被复制的结构体的方法

目录
  • 不允许复制的结构体
  • 实现原理
  • 结论

不允许复制的结构体

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这个分析器会检查实现了LockUnlock方法的结构体是否被复制。

copylock Analysercmd/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内包含了两个方法: LockUnlock, 只有实现了这两个方法的结构体才是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会检测传入的参数是否实现了LockUnlock方法,如果是则返回类型的信息。而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都会自动帮你运行),你就能通过这个机制来提醒他人,尽量避免复制结构体。

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

定义一个实现了LockUnlock的结构体

type noCopy struct{}

func (*noCopy) Lock()   {}
func (*noCopy) Unlock() {}

将其放入你的结构体中:

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

或直接让你的结构体实现LockUnlock方法:

type Foo struct {
    // ...
}

func (*Foo) Lock()   {}
func (*Foo) Unlock() {}

这样别人在尝试复制Foo的时候,就会得到IDE的警告信息了。

到此这篇关于Golang实现不被复制的结构体的方法的文章就介绍到这了,更多相关Golang结构体内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • golang中结构体嵌套接口的实现

    在golang中结构体A嵌套另一个结构体B见的很多,可以扩展A的能力. A不仅拥有了B的属性,还拥有了B的方法,这里面还有一个字段提升的概念. 示例: package main import "fmt" type Worker struct {     Name string     Age int     Salary } func (w Worker) fun1() {     fmt.Println("Worker fun1") } type Salary s

  • 一文教你如何快速学会Go的struct数据类型

    目录 什么是结构体 创建结构体 创建匿名结构体 访问结构体字段 结构体零值 结构体指针 匿名字段 结构体嵌套 字段升级 结构体导出 结构体比较 什么是结构体 结构是表示字段集合的用户定义类型.它可以用于将数据分组为单个单元而不是将每个数据作为单独的值的地方. 例如,员工有firstName.lastName和age.将这三个属性分组到一个名为Employee. type Employee struct { firstName string lastName string age int } 上面

  • Go语言中Struct与继承与匿名字段和内嵌结构体全面详解

    目录 定义 内嵌结构体 在golang中,采用匿名结构体字段来模拟继承关系.这个时候,可以说 Student 是继承自 Person . type Person struct { name string age int sex string } func (Person) SayHello(){ fmt.Println("this is from Person") } type Student struct { Person school string } func main() {

  • Go Struct结构体的具体实现

    目录 什么是结构体 1. 基本实例化(方法1) 2. new实例化(方法2) 3. 键值对初始化(方法3 结构体能够使用指针就使用指针) 结构体方法和接收者 encoding-json包 1. struct与json 2. struct tag 什么是结构体 Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念.(继承,多态,封装) Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性. 1. 基本实例化(方法1) 只有当结构体实例化时,才会真正地分配内存(其实也就

  • Go语言中的Struct结构体

    一.Struct结构体 Go语言中没有像C#.Java一样的Class,只有Struct这样的结构体.Go语言使用type关键字来定义一个类型. 如下: type User struct { Name string Age int32 Sex int16 AvatarUrl string } 二.Struct的声明以及初始化 初始化方法一:直接定义,通过变量名.成员名的方式赋值 //初始化方法1 var user1 User user1.Name = "BigOrange" user1

  • Go语言类型内嵌和结构体内嵌的具体使用

    目录 内嵌结构体 结构内嵌特性 结构体可以包含一个或多个匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型也就是字段的名字.匿名字段本身可以是一个结构体类型,即结构体可以包含内嵌结构体. 可以粗略地将这个和面向对象语言中的继承概念相比较,随后将会看到它被用来模拟类似继承的行为.Go语言中的继承是通过内嵌或组合来实现的,所以可以说,在Go语言中,相比较于继承,组合更受青睐. 考虑如下的程序: package main import "fmt" type inn

  • Golang实现不被复制的结构体的方法

    目录 不允许复制的结构体 实现原理 结论 不允许复制的结构体 sync包中的许多结构都是不允许拷贝的,比如sync.Cond,sync.WaitGroup,sync.Pool, 以及sync包中的各种锁,因为它们自身存储了一些状态(比如等待者的数量),如果你尝试复制这些结构体: var wg1 sync.WaitGroup wg2 := wg1 // 将 wg1 复制一份,命名为 wg2 // ... 那么你将在你的 IDE 中看到一个醒目的警告: assignment copies lock

  • Go语言指针访问结构体的方法

    本文实例讲述了Go语言指针访问结构体的方法.分享给大家供大家参考.具体分析如下: Go有指针,但是没有指针运算. 结构体字段可以通过结构体指针来访问.通过指针间接的访问是透明的. 复制代码 代码如下: package main import "fmt" type Vertex struct {     X int     Y int } func main() {     p := Vertex{1, 2}     q := &p     q.X = 1e9     fmt.P

  • python中定义结构体的方法

    Python中没有专门定义结构体的方法,但可以使用class标记定义类来代替结构体,其成员可以在构造函数__init__中定义,具体方法如下. 复制代码 代码如下: class item:    def __init__(self):        self.name = ''     # 名称        self.size = 10     # 尺寸        self.list = []     # 列表 a = item() # 定义结构对象a.name = 'cup'a.size

  • Go语言基础语法之结构体及方法详解

    结构体类型可以用来保存不同类型的数据,也可以通过方法的形式来声明它的行为.本文将介绍go语言中的结构体和方法,以及"继承"的实现方法. 结构体类型 结构体类型(struct)在go语言中具有重要地位,它是实现go语言面向对象编程的重要工具.go语言中没有类的概念,可以使用结构体实现类似的功能,传统的OOP(Object-Oriented Programming)思想中的继承在go中可以通过嵌入字段的方式实现. 结构体的声明与定义: // 使用关键字 type 和 struct 定义名字

  • Go语言之结构体与方法

    目录 一.结构体 1.结构体的定义与使用 2.定义并赋初值 3.匿名结构体(只使用一次,没有名字) 4.结构体的零值 5.结构体的指针 6.匿名字段(字段没有名字,只有类型) 7.嵌套结构体(结构体中套结构体) 8.字段提升 9.结构体相等性 二.方法 1.方法的定义和使用 2.有了函数为啥还需要方法? 3.指针接收器与值接收器 5.匿名字段的方法(方法提升) 6.在方法中使用值接收器 与 在函数中使用值参数 7.在方法中使用指针接收器 与 在函数中使用指针参数 8.非结构体上绑定方法 一.结构

  • Go语言学习之结构体和方法使用详解

    目录 1. 结构体别名定义 2. 工厂模式 3. Tag 原信息 4. 匿名字段 5. 方法 1. 结构体别名定义 变量别名定义 package main import "fmt" type integer int func main() { //类型别名定义 var i integer = 1000 fmt.Printf("值: %d, 类型: %T\n", i, i) var j int = 100 j = int(i) //j和i不属于同一类型,需要转换 fm

  • Go语言同步等待组sync.WaitGroup结构体对象方法详解

    目录 sync.WaitGroup结构体对象 WaitGroup的结构体 Add()方法 Done()方法 Wait()方法 Add().Done().Wait()三者对比 sync.WaitGroup使用示例 sync.WaitGroup结构体对象 在Go语言中,sync.WaitGroup结构体对象用于等待一组线程的结束:WaitGroup是go并发中最常用的工具,我们可以通过WaitGroup来表达这一组协程的任务是否完成,以决定是否继续往下走,或者取任务结果: WaitGroup的结构体

  • golang中使用匿名结构体的方法

    目录 转化为map 定义具名结构体 定义匿名结构体 在一些项目中, 我们会使用json 来将字符串转为结构体,但是很多时候,这种结构体只会用一次,基本上只会用于反序列化, 对于这种只用到一次的结构体, 我们可以使用匿名结构体. 在gin 接收参数的时候会非常有用, 如我们将要接收的json 参数为 { "opt": "left", "phoneinfo": [ {"device_id": 64, "sn":

  • golang 如何用反射reflect操作结构体

    背景 需要遍历结构体的所有field 对于exported的field, 动态set这个field的value 对于unexported的field, 通过强行取址的方法来获取该值(tricky?) 思路 下面的代码实现了从一个strct ptr对一个包外结构体进行取值的操作,这种场合在笔者需要用到反射的场合中出现比较多 simpleStrtuctField 函数接受一个结构体指针,因为最后希望改变其值,所以传参必须是指针.然后解引用. 接下来遍历结构体的每个field, exported字段是

  • 详解C++程序中定义struct结构体的方法

    什么是结构体? 简单的来说,结构体就是一个可以包含不同数据类型的一个结构,它是一种可以自己定义的数据类型,它的特点和数组主要有两点不同,首先结构体可以在一个结构中声明不同的数据类型,第二相同结构的结构体变量是可以相互赋值的,而数组是做不到的,因为数组是单一数据类型的数据集合,它本身不是数据类型(而结构体是),数组名称是常量指针,所以不可以做为左值进行运算,所以数组之间就不能通过数组名称相互复制了,即使数据类型和数组大小完全相同. 结构体的定义 定义结构体使用struct修饰符,例如: struc

随机推荐