闽公网安备 35020302035485号
for i := range 10 {
fmt.Println(i)
}
这个大家都已经知道了,其实对应的提案中还有一个隐藏的功能,就是可以 range 一个函数,比如下面的代码(摘自官方代码库internal/trace/v2/event.go[1]):// Frames is an iterator over the frames in a Stack.
func (s Stack) Frames(yield func(f StackFrame) bool) bool {
if s.id == 0 {
return true
}
stk := s.table.stacks.mustGet(s.id)
for _, f := range stk.frames {
sf := StackFrame{
PC: f.pc,
Func: s.table.strings.mustGet(f.funcID),
File: s.table.strings.mustGet(f.fileID),
Line: f.line,
}
if !yield(sf) {
return false
}
}
return true
}
就少有介绍了。| Range 表达式 | 第一个值 | 第二个值 |
|---|---|---|
| array or slice a [n]E, *[n]E, or []E | index i int | a[i] E |
| string s string type | index i int | see below rune |
| map m map[K]V | key k K | m[k] V |
| channel c chan E, <-chan E | element e E |
|
| integer n integer type | index i int |
|
| function, 0 values f func(func()bool) bool |
|
|
| function, 1 value f func(func(V)bool) bool | value v V |
|
| function, 2 values f func(func(K, V)bool) bool | key k K | v V |
for x, y := range f { ... }
for x, _ := range f { ... }
for _, y := range f { ... }
for x := range f { ... }
for range f { ... }
下面是一个例子: var fn = func(yield func(k int, v byte) bool) {
for i := 0; i < 26; i++ {
if !yield(i, byte('a'+i)) {
return
}
}
}
// 堆代码 duidaima.com
for k, v := range fn {
fmt.Printf("%d: %c\n", k, v)
}
运行可以看到结果符合预期,我们遍历了 26 个小写字母,注意 range 的数据类型是我们的函数:
var fn = func(yield func(v byte) bool) {
for i := 0; i < 26; i++ {
if !yield(byte('a' + i)) {
return
}
}
}
for v := range fn {
fmt.Printf("%c\n", v)
}

package main
import "fmt"
func main() {
var fn = func(yield func() bool) {
for i := 0; i < 26; i++ {
if !yield() {
return
}
}
}
var count int
for range fn {
count++
}
fmt.Println(count)
}
如果不使用 for-range 函数的形式,我们可以进行改写,比如两个参数的列子: var fn = func(yield func(k int, v byte) bool) {
for i := 0; i < 26; i++ {
if !yield(i, byte('a'+i)) {
return
}
}
}
fn(func(k int, v byte) bool {
fmt.Printf("%d: %c\n", k, v)
return true
})
注意yield参数名称不是一个关键字,它只是一个普通的参数名称,可以随便取名字,但是为了模仿和其它语言中的generator,使用了yield这样一个名称,以至于代码更加易读。type Point struct {
X float64 `sql:"x"`
Y float64 `sql:"y"`
}
for p, err := range sqlrange.Query[Point](db, `select x, y from points` "Point") {
if err != nil {
...
}
...
}
遍历查询和 ORM 一气呵成。这里的资源管理是自动的,底层的*sql.Rows遍历完会自动关闭。
参考资料
[1]internal/trace/v2/event.go: https://github.com/golang/go/blob/97daa6e94296980b4aa2dac93a938a5edd95ce93/src/internal/trace/v2/event.go#L262
[2]Rangefunc Experiment: https://go.dev/wiki/RangefuncExperiment
[3]#56413: https://github.com/golang/go/discussions/56413
[4]#61405: https://github.com/golang/go/issues/61405
[5]rangefunc/rewrite: https://go.googlesource.com/go/+/refs/changes/41/510541/7/src/cmd/compile/internal/rangefunc/rewrite.go
[6]sqlrange: https://github.com/achille-roussel/sqlrange