rust使用vec在遍历时删除元素

需求: 工作中有一个地方需要用到在遍历时把不符合条件的元素删除掉,

比如一个vec中是原始数据是 vec![1,2,3,3,4,5] ,然后我要在遍历中把等于c的元素删除掉,目的是得到vec![1,2,4,5]

第一次错误尝试

由于最开始只知道移除元素用remove方法,所以最开始是这样写的

let mut items:Vec<&str> = vec!["a", "b", "c", "c", "d", "e"];
    println!("before items is {:?}", items);
    for (index, item) in items.iter().enumerate() {
        if *item == "c" {
            items.remove(index);
        }
    }
    println!("then items is {:?}", items);

但是报错了,报错提示为

error[E0502]: cannot borrow `items` as mutable because it is also borrowed as immutable
 --> src/main.rs:8:13
  |
6 |     for (index, item) in items.iter().enumerate() {
  |                          ------------------------
  |                          |
  |                          immutable borrow occurs here
  |                          immutable borrow later used here
7 |         if *item == "c" {
8 |             items.remove(index);
  |             ^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

For more information about this error, try `rustc --explain E0502`.

提示很明显,iter()是不可变的引用,但是调用remove的时候删除一个元素得时候,对items是可变的引用了,所以一个变量不能既是可变引用又是不可变引用,所以报错了。

第二次错误尝试

然后我进行了第二次尝试,
代码改成了,不在遍历中进行移除,把需要移除的id保存起来,在遍历结束之后,再把需要移除的元素给移除掉。

let mut items:Vec<&str> = vec!["a", "b", "c", "c", "d", "e"];
    println!("before items is {:?}", items);
    let mut remove_indexs: Vec<usize> = Vec::new();
    for (index, item) in items.iter().enumerate() {
        if *item == "c" {
            remove_indexs.push(index);
        }
    }
    println!("remove indexs is {:?}", remove_indexs);
    for i in remove_indexs {
        items.remove(i);
    }
    println!("then items is {:?}", items);

打印出的结果是

before items is ["a", "b", "c", "c", "d", "e"]
remove indexs is [2, 3]
then items is ["a", "b", "c", "e"]

不报错了,但是结果不对啊,预计是[“a”,"b","d","e"]的,怎么变成了["a", "b", "c", "e"]了。

原因是:

第二个循环需要移除索引为2和3的两个元素。所以有两次循环:

  • 第一次:在移除索引为2的元素之后。items变成了[“a”,"b","c","d","e"],后面的元素补上去了。这时我们的目标索引3的index从3变成了2.
  • 第二次:按照代码把索引为3的元素移除,但是索引3的元素却成c变成了d,这段程序也就错了。

正确方法

上面方法失败之后,觉得vec应该有处理这种情况的方法,所以看了源码,找到了答案。
vec有两个方法可以实现我想要的。

vec.retain 和 vec.drain_filter

vec.retain

vec.retain 很简单,retain的意思是保留,所以这个方法的意思就是接收一个回调函数,然后回调函数里面返回true进行保留,返回false的就移除。
示例:

let mut vec = vec![1, 2, 3, 4];
    vec.retain(|&x| x % 2 == 0);
    assert_eq!(vec, [2, 4]);

所以用vec.retain来实现的话,就是这样

let mut items:Vec<&str> = vec!["a", "b", "c", "c", "d", "e"];
    println!("before items is {:?}", items);
    items.retain(|item| if *item == "c" {false } else { true });
    println!("then items is {:?}", items);

vec.drain_filter

drain的意思是 排出 的意思,所以这个函数就是排出过滤器,接收一个回调函数,然后把回调函数里面返回true的元素就会排出,自然也就从原本的vec里面删除掉了。然后有需要的话还可以搜集排出的元素。
示例:

let mut numbers = vec![1, 2, 3, 4, 5, 6, 8, 9, 11, 13, 14, 15];
    
    let evens = numbers.drain_filter(|x| *x % 2 == 0).collect::<Vec<_>>();
    let odds = numbers;
    
    assert_eq!(evens, vec![2, 4, 6, 8, 14]);
    assert_eq!(odds, vec![1, 3, 5, 9, 11, 13, 15]);

所以用vec.drain_filter来实现的话,就是这样

let mut items:Vec<&str> = vec!["a", "b", "c", "c", "d", "e"];
    println!("before items is {:?}", items);
    items.retain(|item| if *item == "c" {false } else { true });
    let removed_items = items.drain_filter(|item| if *item == "c" { true} else { false}).collect::<Vec<_>>();
    println!("then items is {:?}", items);
    println!("removed item is {:?}", removed_items);

但是报错了,提示:

error[E0658]: use of unstable library feature 'drain_filter': recently added
 --> src/main.rs:7:31
  |
7 |     let removed_items = items.drain_filter(|item| if *item == "c" { true} else { false}).collect::<Vec<_>>();
  |                               ^^^^^^^^^^^^
  |
  = note: see issue #43244 <https://github.com/rust-lang/rust/issues/43244> for more information

For more information about this error, try `rustc --explain E0658`.

这个函数是属于不稳定的特性的,所以需要使用的话是有条件的
使用unstable feature的条件和步骤:

  1. 只有nightly才可以使用unstable
  2. 找到unstable feature的名字

![feature(xxx)]启用这个feature