Gopher 需要知道的幾個結構體騷操作
我們知道 Go 沒有繼承的概念,接口結構體多使用組合,很多開源產(chǎn)品或是源代碼都有大量的內嵌 (embeded field) 字段,用于特殊目的
NoCopy
package?main
import?(
?"sync"
)
func?test(wg?sync.WaitGroup)?{
?defer?wg.Done()
?wg.Add(1)
}
func?main()?{
?var?wg?sync.WaitGroup
?wg.Add(1)
?go?test(wg)
?wg.Wait()
}
這是非常經(jīng)典的 case, 程序執(zhí)行報錯 all goroutines are asleep - deadlock!, 解決也很簡單,把 wg 由值傳遞變成指針類型即可。本質是 WaitGroup 內部維護了計數(shù),不允許 copy 變量,還有 sync.Mutex 鎖也是不允許 copy 的
解決辦法很簡單,需要 CI 時由 linter 檢測出來,最好運行時也能有檢測機制,這方面的討論請參考issue 8005[1]
zerun.dong$?go?vet?aaa.go
#?command-line-arguments
./aaa.go:7:14:?test?passes?lock?by?value:?sync.WaitGroup?contains?sync.noCopy
./aaa.go:15:10:?call?of?test?copies?lock?value:?sync.WaitGroup?contains?sync.noCopy
這是 go vet 結果,報錯己經(jīng)很明顯了
type?noCopy?struct{}
noCopy 定義非常簡單,空結構體,zero size 不占用空間(前提是非結構體的最后一個字段,否則還要是有 8 byte 空間開銷)
sync.WaitGroup[2] 內嵌 noCopy 字段,防止 Cond 變量被復制
type?WaitGroup?struct?{
?noCopy?noCopy
?//?64-bit?value:?high?32?bits?are?counter,?low?32?bits?are?waiter?count.
?//?64-bit?atomic?operations?require?64-bit?alignment,?but?32-bit
?//?compilers?only?guarantee?that?64-bit?fields?are?32-bit?aligned.
?//?For?this?reason?on?32?bit?architectures?we?need?to?check?in?state()
?//?if?state1?is?aligned?or?not,?and?dynamically?"swap"?the?field?order?if
?//?needed.
?state1?uint64
?state2?uint32
}
上面是 sync.WaitGroup 結構體的定義,同時注意 noCopy 是源碼中不可導出的定義。如果用戶代碼也想實現(xiàn) NoCopy 呢?可以參考 grpc DoNotCopy[3]
//?DoNotCopy?can?be?embedded?in?a?struct?to?help?prevent?shallow?copies.
//?This?does?not?rely?on?a?Go?language?feature,?but?rather?a?special?case
//?within?the?vet?checker.
type?DoNotCopy?[0]sync.Mutex
非常簡單,Mutex 零長數(shù)組,不占用空間。由于 vet checker 會檢測 Mutex,相當于替我們實現(xiàn)了 noCopy 功能
DoNotCompare
Golang Sepc Comparison_operators[4] 官方文檔描述常見類型比較運算( == != > < <= >=)的結果,詳細內容看官方文檔 https://go.dev/ref/spec#Comparison_operators
In any comparison, the first operand must be assignable to the type of the second operand, or vice versa.
The equality operators == and != apply to operands that are comparable. The ordering operators <, <=, >, and >= apply to operands that are ordered.
Struct values are comparable if all their fields are comparable. Two struct values are equal if their corresponding non-blank fields are equal.
Slice, map, and function values are not comparable. However, as a special case, a slice, map, or function value may be compared to the predeclared identifier nil. Comparison of pointer, channel, and interface values to nil is also allowed and follows from the general rules above.
對于 struct 來講,只有所有字段全部 comparable 的(不限大小寫是否導出),那么結構體才可以比較。同時只比較 non-blank 的字段,舉個例子:
type?T?struct?{
????name?string
????age?int
????_?float64
}
func?main()?{
???x?:=?[...]float64{1.1,?2,?3.14}
???fmt.Println(x?==?[...]float64{1.1,?2,?3.14})?//?true
???y?:=?[1]T{{"foo",?1,?0}}
???fmt.Println(y?==?[1]T{{"foo",?1,?1}})?//?true
}
運行后,結果均為 true
Slice, Map, Function 均是不可比較的,只與判斷是否為 nil. 所以我們可以利用這兩個特性,內嵌函數(shù)來實現(xiàn)不可比較,參考 protobuf DoNotCompare[5]
//?DoNotCompare?can?be?embedded?in?a?struct?to?prevent?comparability.
type?DoNotCompare?[0]func()
如果比較會報錯
type?DoNotCompare?[0]func()
type?T?struct?{
????name?string
????age?int
????DoNotCompare
}
func?main()?{
//?./cmp.go:13:21:?invalid?operation:?T{}?==?T{}?(struct?containing?DoNotCompare?cannot?be?compared)
????fmt.Println(T{}?==?T{})
}
NoUnkeyedLiterals
結構體初始化有兩種:指定字段名稱,或者按順序列出所有字段,不指定名稱
type?User?struct{
????Age?int
????Address?string
}
u?:=?&User{21,?"beijing"}
這樣寫的問題非常大,如果新增字段會不兼容
type?User?struct{
????Age?int
????Address?string
????Money?int
}
func?main(){
//?./struct.go:11:15:?too?few?values?in?User{...}
??_?=?&User{21,?"beijing"}
}
上面的例子,能在編譯期報錯還是可接受的,如果同類型的調換順序,那才叫坑爹... 所以這時需要 NoUnkeyedLiterals[6]
//?NoUnkeyedLiterals?can?be?embedded?in?a?struct?to?prevent?unkeyed?literals.
type?NoUnkeyedLiterals?struct{}
很簡單,就是一個空結構體,這是 Protobuf 的實現(xiàn)。很多時候我們都用空的結構體占位符實現(xiàn)
type?User?struct{
????_?struct{}
????Age?int
????Address?string
}
func?main(){
//?./struct.go:10:11:?cannot?use?21?(type?int)?as?type?struct?{}?in?field?value
//?./struct.go:10:15:?cannot?use?"beijing"?(type?untyped?string)?as?type?int?in?field?value
//?./struct.go:10:15:?too?few?values?in?User{...}
_?=?&User{21,?"beijing"}
}
報錯很明顯了,字段類型不匹配,有人會說初始化寫上 struct{} 不就可以了?
_?=?&User{struct{}{},?21,?"beijing"}
這樣確實可以工作,但是占位符 _ 的字段是不可導出的,所以 import 其它包的 NoUnkeyedLiterals 結構體同樣會報錯
Copier 庫
最后推薦一個非常實用的 copier[7] 庫,CRUD Boy 經(jīng)常結構體轉來轉去的,比如 dto, dao 互轉,或是 dao 與其它互轉,如果修改了 dao 結構體,還要記得修改其它轉換邏輯,非常繁瑣
package?main
import?(
??"fmt"
??"github.com/jinzhu/copier"
)
type?User?struct?{
??Name?string
??Age??int
}
type?Employee?struct?{
??Name?string
??Age??int
??Role?string
}
func?main()?{
??user?:=?User{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 發(fā)現(xiàn) name, age 字段己經(jīng)賦值了,非常好用。感興趣的可以查看官網(wǎng),支持非常多的高級玩法
注意:這里是隱式的,有人偏好所有字段顯示賦值,大家怎么看?
小結
寫文章不容易,如果對大家有所幫助和啟發(fā),請大家?guī)兔c擊在看,點贊,分享 三連
關于 struct 騷操作 大家有什么看法,歡迎留言一起討論,大牛多留言 ^_^
參考資料
nocopy issue 8005: https://github.com/golang/go/issues/8005,
[2]sync.WaitGroup noCopy: https://github.com/golang/go/blob/master/src/sync/waitgroup.go#L212,
[3]pprotobuf DoNotCopy: https://pkg.go.dev/google.golang.org/[email protected]/internal/pragma#DoNotCopy,
[4]官方文檔Comparison_operators: https://go.dev/ref/spec#Comparison_operators,
[5]DoNotCompare: https://pkg.go.dev/google.golang.org/[email protected]/internal/pragma#DoNotCompare,
[6]protobuf NoUnkeyedLiterals: https://github.com/protocolbuffers/protobuf-go/blob/v1.27.1/internal/pragma/pragma.go#L12,
[7]jinzhu copier: https://github.com/jinzhu/copier,
推薦閱讀
