前言

在golang中提供了for range 语法糖帮忙咱们不便的遍历数据或切片,也能够遍历map和channel;在rust中则提供了 for in 语法糖帮忙咱们不便的遍历Array,Vector或切片,也能够遍历map。本文将会通过多种类型的例子介绍两种语言中这些语法糖背地的机制以及使用不当可能遇到的陷阱。

遍历形式比照

Golang

咱们先来看Golang中的三种遍历形式:

arr := []int{2, 4, 6}    // 只有索引    for i := range arr {        fmt.Println(i)    }    // 索引和数值    for i, v := range arr {        fmt.Println(i, v)    }    // 只有数值    for _, v := range arr {        fmt.Println(v)    }

输入
0
1
2
0 2
1 4
2 6
2
4
6

Rust

首先咱们要理解在rust中遍历arr有上面四种不同的办法,实质都是将以后arr转为了相应的iterator。

let arr = vec![1, 2, 3];    for a in arr {        println!("{}", a);    }    let arr = vec![1, 2, 3];    for a in arr.into_iter() {        println!("{}", a);    }    let arr = vec![1, 2, 3];    for a in arr.iter() {        println!("{}", a);    }    let mut arr = vec![1, 2, 3];    for a in arr.iter_mut() {        println!("{}", a);    }

其中 for a in arr 等价于for a in arr.into_iter() ,这种遍历形式会把以后arr中的变量 move 掉,执行遍历后,arr无奈再应用。

for a in arr.iter() 返回arr中每一项的不可变借用,for a in arr.iter_mut() 返回arr中每一项的可变借用,遍历后arr能够持续应用。

如果还须要索引,那么就要应用iterator的enumerate办法,这个办法将以后迭代器封装成迭代元素是蕴含索引和元素的元组的迭代器,如下:

let arr = vec![1, 2, 3];    for (i, v) in arr.into_iter().enumerate() {        println!("{} {}", i, v);    }    let arr = vec![1, 2, 3];    for (i, v) in arr.iter().enumerate() {        println!("{} {}", i, v);    }    let mut arr = vec![1, 2, 3];    for (i, v) in arr.iter_mut().enumerate() {        println!("{} {}", i, v);    }

在Rust中还有一个罕用的只返回索引的遍历办法:

for i in 1..4{        println!("{}",i);    }

输入:

1
2
3

能够看到 beg..end 是一个左闭右开的区间,不蕴含end。

并发工作散发

在理论我的项目开发中,常常会须要将一组工作数据分发给不同的goroutine或thread来并发执行。

Golang

咱们先来看在Golang中常见的谬误写法

func slice_trap_wrong() {    in := []int{1, 2, 3}    for _, b := range in {        go func() {            fmt.Println("job", b)        }()    }}func main() {    slice_trap_wrong()    select {}}

运行失去后果:

job 3job 3job 3fatal error: all goroutines are asleep - deadlock!goroutine 1 [select (no cases)]:main.main()    /home/repl/904b2209-3e69-479f-a530-1954e1cf59cd/main.go:25 +0x25exit status 2** Process exited - Return Code: 1 **

为了保障程序中的goroutine都能齐全执行,所以main主goroutine不能提前退出,这里咱们应用 select {}永恒阻塞以后main函数,所以最终会报fatal error: all goroutines are asleep - deadlock!的谬误。

咱们疏忽最初的error信息,能够看到三个goroutine执行do工作后,打印进去的数据都是3。起因在于for _, b := range in作用域中的b是同一个变量,所以程序中新创建的三个goroutine中闭包所关联的b都是同一个变量。依据GPM模型,这三个goroutine在创立后并不一定会立即执行,等for _, b := range in遍历完后,此时b的数值是3,等这三个goroutine真正开始执行的时候,因为它们执行的是同一个b,所以失去的就是b最初的数值3.

晓得谬误起因后也好解决,有上面两种形式:

  1. 将工作数据作为入参传入,因为golang中参数传递都是按值传递的,所以进入到goroutine中的b曾经是复制之后的一个新的变量

    func slice_trap_right() { in := []int{1, 2, 3} for _, b := range in { go func(b int) { fmt.Println("job", b) }(b) } }

2.在传入goroutine的闭包之前手动进行一次复制

gofunc slice_trap_right() {    in := []int{1, 2, 3}    for _, b := range in {        b:=b        go func() {            fmt.Println("job", b)        }()    }}

Rust

接下来咱们看下Rust中谬误的写法:

let arr = vec![1, 2, 3];    let mut t_arr = vec![];    for a in arr {        t_arr.push(thread::spawn(|| println!("{}", a)));    }    // 期待所有线程执行完结    for t in t_arr {        t.join().unwrap();    }

编译器间接报错,因为thread的生命周期超过了 for a in arr中 a 的生命周期 :

error[E0373]: closure may outlive the current function, but it borrows `a`, which is owned by the current function  --> src/main.rs:16:34   |16 |         t_arr.push(thread::spawn(|| println!("{}", a)));   |                                  ^^                - `a` is borrowed here   |                                  |   |                                  may outlive borrowed value `a`   |note: function requires argument type to outlive `'static`  --> src/main.rs:16:20   |16 |         t_arr.push(thread::spawn(|| println!("{}", a)));   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^help: to force the closure to take ownership of `a` (and any other referenced variables), use the `move` keyword   |16 |         t_arr.push(thread::spawn(move || println!("{}", a)));   |                                  ++++For more information about this error, try `rustc --explain E0373`.error: could not compile `channel` due to previous error

Rust谬误提醒中给出了解决办法,应用move关键字,让闭包获取a的所有权:

let arr = vec![1, 2, 3];    let mut t_arr = vec![];    for a in arr {        println!("{:p}", &a);        t_arr.push(thread::spawn(move || println!("{} {:p}", a, &a)));    }    for t in t_arr {        t.join();    }

输入后果

0x7ffe3a4398d40x7ffe3a4398d41 0x7f68b4b87b8c0x7ffe3a4398d42 0x7f68b4986b8c3 0x7f68b4785b8c

能够看到最终thread中运行的 a 和for a in arr中的 a 不是同一个,而且不同thread中的 a 也都是不同的变量。

咱们再把arr中成员的原始地址打印进去:

let arr = vec![1, 2, 3];    let mut t_arr = vec![];    for a in arr.iter(){        println!("{:p}",a);    }    for a in arr {        println!("{:p}", &a);        t_arr.push(thread::spawn(move || println!("{} {:p}", a, &a)));    }    for t in t_arr {        t.join();    }

失去输入:

0x5633d313ead00x5633d313ead40x5633d313ead80x7ffdbb7d2cc40x7ffdbb7d2cc41 0x7fee6d482b8c0x7ffdbb7d2cc42 0x7fee6d281b8c3 0x7fee6d080b8c

能够晓得arr中的成员首先转移给了a,而后在执行move后再次进行了复制。

咱们再试下构造体,不 derive Copy trait。

`pub struct People {

pub age: i32,

}`

编写测试代码如下:

let arr = vec![People { age: 1 }, People { age: 2 }, People { age: 3 }];    let mut t_arr = vec![];    for a in arr.iter() {        println!("{:p}", a);    }    for a in arr {        println!("{:p}", &a);        t_arr.push(thread::spawn(move || println!("{} {:p}", a.age, &a)));    }    for t in t_arr {        t.join();    }

失去输入

0x555c64ec9ad00x555c64ec9ad40x555c64ec9ad80x7ffdc57664740x7ffdc57664741 0x7f0b6f4b1b8c0x7ffdc57664742 0x7f0b6f2b0b8c3 0x7f0b6f0afb8c

能够看到arr中的People变量也是执行了两次挪动。

如果我的项目中数据结构体比拟大,执行屡次挪动不可承受的话,能够应用上面形式通过Arc进行优化。

Arc优化

Arc 是一种线程平安的援用计数指针,能够平安的在线程之前传递。

let arr = vec![        Arc::new(People { age: 1 }),        Arc::new(People { age: 2 }),        Arc::new(People { age: 3 }),    ];    let mut t_arr = vec![];    for a in arr.iter() {        // 执行复制,理论只是减少了strong援用计数        let a = Arc::clone(a);        t_arr.push(thread::spawn(move || {            // 通过 Arc::strong_count 能够失去Arc中的strong援用计数            println!("in thread {} count:{}", a.age, Arc::strong_count(&a))        }));    }    for t in t_arr {        t.join().unwrap();    }    // 线程执行完结后,外部的a被drop掉,理论只是缩小了strong援用计数    for a in arr {        println!("final: {} count:{}", a.age, Arc::strong_count(&a));    }

输入

in thread 1 count:2
in thread 3 count:2
in thread 2 count:2
final: 1 count:1
final: 2 count:1
final: 3 count:1

循环永动机

如果咱们在遍历数组的同时批改数组的元素,是否失去一个永远都不会进行的循环呢?

咱们先来看golang版本

Golang

这个例子来自 Go 语言for 和 range 实现

func main() {    arr := []int{1, 2, 3}    for _, v := range arr {        arr = append(arr, v)    }    fmt.Println(arr)}$ go run main.go1 2 3 1 2 3

上述代码的输入意味着循环只遍历了原始切片中的三个元素,咱们在遍历切片时追加的元素不会减少循环的执行次数,所以循环最终还是停了下来。那么为什么没有始终执行上来呢?golang对于 for range 在真正生成机器码之前进行了转换,会先获取输出arr的长度保存起来,而后执行经典的三段式for循环,具体能够看 Go 语言for 和 range 实现 里dravness大佬的解说,这里就不再细说。

Rust

咱们看下rust中要实现下面的循环永动机:

let mut arr= vec![1,2,3];    for a in arr.iter() {        println!("{}", a);        arr.push(4);    }

rust报错,因为rust的所有权平安机制,同时只能有一个mut的借用,并且此时也不能有其它的只读借用。

error[E0502]: cannot borrow `arr` as mutable because it is also borrowed as immutable  --> src/main.rs:41:9   |39 |     for a in arr.iter() {   |              ----------   |              |   |              immutable borrow occurs here   |              immutable borrow later used here40 |         println!("{}", a);41 |         arr.push(4);   |         ^^^^^^^^^^^ mutable borrow occurs here

如果在实在我的项目中有这种基于以后Vector中数据生成新数据的需要,例如在DFS或BFS遍历的过程中,就要依据把以后父节点的子节点退出到遍历的数组中,能够这么写

let mut arr = vec![1, 2, 3];    let mut i = 0;    let len = arr.len();    while i < len {        arr.push(arr[i]);        i += 1;    }

留神下面的代码中也是事后计算以后arr的长度,如果应用上面的写法就会变成有限循环。

let mut arr = vec![1, 2, 3];    let mut i = 0;    while i < arr.len() {        arr.push(arr[i]);        i += 1;    }

神奇的指针
Golang
Golang例子同样来自于 Go 语言for 和 range 实现

func main() {    arr := []int{1, 2, 3}    newArr := []*int{}    for _, v := range arr {        newArr = append(newArr, &v)    }    for _, v := range newArr {        fmt.Println(*v)    }}$ go run main.go3 3 3

很多人认为返回的是1,2,3,起因在于他们认为for range 中的v是在每个循环外部长期生成的,他们认为是这样的

for i:=0;i<len(arr);i++{    v := arr[i];    newArr = append(newArr, &v)}

实际上是

v:=0for i:=0;i<len(arr);i++{    v = arr[i];    newArr = append(newArr, &v)}

具体的能够去看原文以及上面的评论,大家也能够把arr中和newArr中的遍历的地址打印进去就明确了。

Rust
咱们应用 for a in arr.iter() 间接获取arr中每一个遍历的不可变借用,所以程序中new_arr能够依照预期失常打印

let arr: Vec<i32> = vec![1, 2, 3];    let mut new_arr: Vec<&i32> = vec![];    for a in arr.iter() {        new_arr.push(a);    }    for a in new_arr {        println!("{}", *a);    }

输入
1
2
3

须要留神的是,如果应用上面的形式编写,会编译报错

let arr: Vec<i32> = vec![1, 2, 3];    let mut new_arr: Vec<&i32> = vec![];    for a in arr {        new_arr.push(&a);    }    for a in new_arr {        println!("{}", *a);    }

起因在于for a in arr 执行完结后,a就被drop掉了,这里的a的申明周期不足以撑持到 for a in new_arr:

error[E0597]: `a` does not live long enough  --> src/main.rs:60:22   |60 |         new_arr.push(&a);   |                      ^^ borrowed value does not live long enough61 |     }   |     - `a` dropped here while still borrowed62 |     for a in new_arr {   |              ------- borrow later used here

另外咱们能够再做个测试样例看for a in arr中的a是不是在每次迭代中创立了一个新变量还是复用一个变量:

let arr: Vec<i32> = vec![1, 2, 3];    for a in arr.iter(){        println!("{:p}",a);    }    for a in arr {        println!("{:p}",&a);    }

咱们首先通过 arr.iter()失去arr中每个元素的不变借用,也就是元素地址打印进去,而后再把 for a in arr 中的每次迭代中的a的地址打印进去,如下:

0x563932220b100x563932220b140x563932220b180x7fff5e40c2e40x7fff5e40c2e40x7fff5e40c2e4

for a in arr中的打印看到地址都一样,阐明复用了同一个变量,跟golang中的 for range 相似。

遍历 map
这里简略比照下两种语言的遍历形式,更加具体的map的比照会有专门一期文章来讲。

Golang

scores := make(map[string]int)    scores["Yello"] = 50    scores["Blue"] = 10    for k, v := range scores {        fmt.Println(k, v)    }

Rust

use std::collections::HashMap;    let mut scores = HashMap::new();    scores.insert(String::from("Blue"), 10);    scores.insert(String::from("Yellow"), 50);    for (key, value) in &scores {        println!("{}: {}", key, value);    }

参考

Go 语言for 和 range 实现 https://draveness.me/golang/d...