go compile 支持 _ 作为零值 起因 很久以前就有类似的 proposal, 并且官方也有回复, 以下是链接:
个人认为官方还是太过于保守了, 至少目前 go1 的语法下, 在 return
表达式中引入 _
用于返回错误上还是有一些编写上的方便的(小声 bb).
DIY compile 我的 go 版本 1.10.2, 修改 $GOROOT/src/cmd/compile/internal/gc
下 3 个文件:
增加 hackgc.go
修改 subr.go
修改 typecheck.go
1. 增加 hackgc.go
: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 package gcimport ( "cmd/compile/internal/types" "os" ) var isnilable [NTYPE]bool func init () { isnilable[TPTR32] = true isnilable[TPTR64] = true isnilable[TINTER] = true isnilable[TMAP] = true isnilable[TCHAN] = true isnilable[TFUNC] = true isnilable[TSLICE] = true isnilable[TUNSAFEPTR] = true } var ( hackgcblank = os.Getenv("HACKBLANK" ) != "" ) func convblanklit (n *Node, t *types.Type) *Node { if t == nil { return n } nn := *n n = &nn var zero interface {} et := t.Etype switch { case isInt[et]: p := &Mpint{} p.Rune = (t == types.Runetype || t.Orig == types.Runetype) zero = p case isnilable[et]: zero = (*NilVal)(nil ) case et == TSTRING: zero = "" case et == TBOOL: zero = false case isFloat[et]: zero = newMpflt() case isComplex[et]: zero = newMpcmplx() case et == TARRAY: n.Op = OARRAYLIT n.Type = t return n case et == TSTRUCT: n.Op = OSTRUCTLIT n.Type = t return n default : goto bad } n.Op = OLITERAL n.Type = t n.SetVal(Val{zero}) bad: return n } func hackblank (n *Node, t *types.Type) *Node { lno := setlineno(n) n = convblanklit(n, t) lineno = lno return n }
这一步主要是定义了一个由环境变量 HACKBLANK
提供的开关 hackgcblank
; 和一个函数 hackblank
, 用于把 _
转化为一个零值 literal.
2. 修改 subr.go
在 subr.go:958
修改为:
1 2 3 4 5 if hackgcblank && isblank(n) { n = hackblank(n, t) } else { n = defaultlit(n, t) }
这一步的 assignconvfn
函数提供了一个赋值时检查和转换右值 n
的 Type
到左值类型 t
的功能, 所以这里对 _
做一个特殊处理, 如果一个右值 n
是 _
, 那么将它转为左值类型 t
的零值 literal.
3. 修改 typecheck.go
在 typecheck.go:310
修改为
1 if !hackgcblank && isblank(n) {
这一步是检查右值类型是发生的, 我们希望在开关打开时忽略这个检查.
验证 替换编译器:
1 2 3 cd $GOROOT/src/cmd/compile go clean -cache && go build -i -v mv compile ../../../pkg/tool/linux_amd64/compile
使用一段代码验证我们的修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport ( "errors" "fmt" ) type S struct { A int B bool } func foo () (int , bool , float64 , complex128 , string , map [int ]int , [2 ]bool , interface {}, S, error ) { return _, _, _, _, _, _, _, _, _, errors.New("oops" ) } func main () { fmt.Println(foo()) }
It works!
是不是引入了新的问题 看一段代码
1 2 3 4 5 6 7 8 package mainfunc main () { n := _ a, b := _ _ = n _, _ = a, b }
这段代码编译器会 crash, 因为我们的做法是将左值的类型赋予右值的 _
, 但这里左值的类型未知, 我的做法是禁止右值 _
用于赋值语句.
修复代码 修改 typecheck.go:3265
1 n.Right = typecheck(n.Right, Erv)
修改为
1 2 3 4 5 n.Right = typecheck(n.Right, Erv) if hackgcblank && isblank(n.Right) && n.Right.Op == ONAME { n.Right.Type = nil yyerror("cannot assign _ to left value" ) }
修改 typecheck.go:3317
1 typecheckslice(n.Rlist.Slice(), Erv)
修改为
1 2 3 4 5 6 7 8 9 10 typecheckslice(n.Rlist.Slice(), Erv) if hackgcblank { for _, rv := range n.Rlist.Slice() { if isblank(rv) && rv.Op == ONAME { rv.Type = nil yyerror("cannot assign _ to left value" ) break } } }
检查赋值语句中是否存在 _
, 如果存在则认为是编译错误.
结束 到此修改完成, 示例中的代码理论可以运行, 具体还带来哪些编译影响未知, 并可能存在代码导致编译器 crash, 这里只是做了一个简单的修改, 真的对这个功能有需求, 还是应该通过 issue/proposal 向官方提议.