本文转载自微信公众号《董泽润的技术笔记》,作者董泽润。转载本文请联系董泽润技术笔记公众号。我们知道Go没有继承的概念。接口结构经常使用组合。许多开源产品或源代码都嵌入了大量用于特殊用途的字段。NoCopypackagemainimport("sync")functest(wgsync.WaitGroup){deferwg.Done()wg.Add(1)}funcmain(){varwgsync.WaitGroupwg.Add(1)gotest(wg)wg.Wait()}这是一个很经典的案例,程序执行报错allgoroutinesaresleep-deadlock!,解决方法也很简单,将wg从传值改成指针类型即可。本质是WaitGroup内部维护了count,不允许copy变量,sync.Mutex锁也不允许copy。解决方法很简单。当需要CI时,它会被linter检测到。最好有运行时的检测机制。讨论请参考issue8005[1]zerun.dong$govetaaa.go#command-line-arguments./aaa.go:7:14:testpasseslockbyvalue:sync.WaitGroupcontainssync.noCopy./aaa.go:15:10:calloftestcopieslockvalue:sync.WaitGroupcontainssync.noCopy这是govet的结果,错误已经很明显了typenoCopystruct{}noCopy定义很简单,空结构,零大小不占空间(前提是非最后一个字段struct,否则必须有8字节空间开销)sync.WaitGroup[2]内嵌noCopy字段,防止Cond变量被复制.//Forthisreasonon32bitarchitecturesweneedtocheckinstate()//ifstate1isalignedornot,动态“交换”thefieldorderif//needed.state1uint64state2uint32}的上面是sync.WaitGroup结构体的定义,注意noCopy在源码中是不可导出的定义。如果用户代码也想实现NoCopy怎么办?可以参考grpcDoNotCopy[3]//DoNotCopycanbeddedinstructtohelppreventshallowcopies.//这不是Go语言的特性,而是一种特殊情况//withinthevetchecker.typeDoNotCopy[0]sync.Mutex很简单,Mutex是一个零长度数组,不占用空间。由于vetchecker会检测Mutex,所以相当于给我们实现了noCopy函数。DoNotCompareGolangSepcComparison_operators[4]官方文档描述了常见类型比较操作的结果(==!=><<=>=)。详见官方文档https://go.dev/ref/spec#Comparison_operators在任何比较中,第一个操作数必须可赋值给第二个操作数的类型,反之亦然。相等运算符==和!=适用于可比较的操作数。排序运算符<、<=、>和>=适用于已排序的操作数。如果它们的所有字段都具有可比性,则结构值是可比的。如果两个结构值对应的非空白字段相等,则它们相等。slice、map、function的值是没有可比性的。但是,作为一种特殊情况,可以将切片、映射或函数值与预先声明的标识符nil进行比较。指针、通道和接口值与nil的比较也是允许的,遵循上面的一般规则。对于struct,只有所有字段都可以比较(无论大小写是否导出),那么结构才能进行比较比较同时只比较非空白字段,例如:typeTstruct{namestringageint_float64}funcmain(){x:=[...]float64{1.1,2,3.14}fmt.Println(x==[...]float64{1.1,2,3.14})//true:=[1]T{{"foo",1,0}}fmt.Println(y==[1]T{{"foo",1,1}})//true}运行后结果为trueSlice、Map、Function都没有可比性,只判断是否为nil。所以我们可以利用这两个特性,嵌入函数实现不可比较,参考protobufDoNotCompare[5]//DoNotComparecanbeembeddedinstructtopreventcomparability.typeDoNotCompare[0]func()如果比较会报错typeDoNotCompare[0]func()typeTstruct{namestringageintDoNotCompare}funcmain(){//./cmp.go:13:21:invalidoperation:T{}==T{}(structcontainingDoNotComparecannotbecompared)fmt.Println(T{}==T{})}初始化方法有两种NoUnkeyedLiterals结构体:指定字段名,或者按顺序列出所有字段,不指定名称typeUserstruct{AgeintAddressstring}u:=&User{21,"beijing"}问题很大,如果新增字段会不兼容withtypeUserstruct{AgeintAddressstringMoneyint}funcmain(){//./struct.go:11:15:toofewvaluesinUser{...}_=&User{21,"beijing"}}在上面的例子中,报错是可以接受的在编译时。如果改变同类型的顺序,就叫作弊...所以NoUnkeyedLiterals[6]//此时不需要UnkeyedLiteralscanbeembeddedinstructtopreventunkeyedliterals.typeNoUnkeyedLiteralsstruct{}很简单,就是一个空结构体,这是Protobuf的实现很多时候我们使用空结构占位符来实现typeUserstruct{_struct{}AgeintAddressstring}funcmain(){//./struct.go:10:11:cannotuse21(typeint)astypestruct{}infieldvalue//./struct。go:10:15:cannotuse"beijing"(typeuntypedstring)astypeintinfieldvalue//./struct.go:10:15:toofewvaluesinUser{...}_=&User{21,"beijing"}}错误很明显,字段类型没有不匹配,有人会说写struct{}初始化不行吗?_=&User{struct{}{},21,"beijing"}确实可以,但是占位符_的字段不能导出。所以导入其他包的NoUnkeyedLiterals结构也会报错。Copier库最后推荐一个非常实用的copier[7]库。CRUDBoy经常会调用结构,比如dto,dao,或者dao等,如果修改了dao结构,记得修改其他的转换逻辑,非常繁琐{Name:"dj",Age:18}employee:=Employee{Role:"admin"}copier.Copy(&employee,&user)//main.Employee{Name:"dj",Age:18,Role:"admin"}fmt.Printf("%#v\n",employee)}打印Employee发现name和age字段都被赋值了。这是非常容易使用。有兴趣的可以去官网看看,支持很多进阶玩法。注意:这是隐含的。有些人喜欢显示所有字段的分配。你怎么认为?
