[Rust] std::vec::splice 函数是如何工作的?

文档给的 splice 用例是这样的:

let mut v = vec![1, 2, 3];
let new = [7, 8];
let u: Vec<_> = v.splice(..2, new.iter().cloned()).collect();
assert_eq!(v, &[7, 8, 3]);
assert_eq!(u, &[1, 2]);

但是看了源码之后我反而糊涂了:

#[inline]
#[stable(feature = "vec_splice", since = "1.21.0")]
pub fn splice<R, I>(&mut self, range: R, replace_with: I) -> Splice<'_, I::IntoIter>
    where R: RangeBounds<usize>, I: IntoIterator<Item=T>
{
    Splice {
        drain: self.drain(range),
        replace_with: replace_with.into_iter(),
    }
}
  • self.drain(range) 改变的是 self 本身,也就是该句执行过后,内容变为 [3],对吗?

  • Splice {...} 这句是返回一个结构体, 参数 replace_with 也未作用于 self。那么它是如何/何时改变 self、把 [7, 8] 追加到其中,使得最后 self 变成 [7, 8, 3]

1 个赞

SpliceDrain 都是迭代器,不驱动它干活的话它什么都不会做。

1 个赞

顺带一提,Rust死灵书(nomicon)里有一节专门讲如何在Rust里实现一个Vec,有兴趣的可以看一看

https://doc.rust-lang.org/nomicon/vec.html

Rust里map,filter,reduce这些操作都要先转换成iterator才能做,不能直接对具体的数据结构(Vec,LinkedList)操作,我猜splice返回一个iterator也是有这种方便上的考虑。

3 个赞

这里也有Rust 的朋友,我正在看 Rust 编程知道,好多新概念

collect的时候吧

和楼上说的类似,我的理解,iterator是定好了一种访问这个集合的顺序/方式,而要让它真的跑,要用.next(),要让它把所在的集合变成类型/大小不同的另一个,要用.collect()

let x = &[1, 2, 4];
let mut iterator = x.iter();

assert_eq!(iterator.next(), Some(&1));
assert_eq!(iterator.next(), Some(&2));
assert_eq!(iterator.next(), Some(&4));
assert_eq!(iterator.next(), None);

https://doc.rust-lang.org/book/ch08-03-hash-maps.html#creating-a-new-hash-map

use std::collections::HashMap;

let teams  = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];

let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();

collect 作用于 v.splice() 返回值 Splice,而不是 v 本身。

如果我把用例代码改一下,排除掉 collect 的干扰:

let mut v = vec![1, 2, 3];
let new = [7, 8];
{
    let u = v.splice(..2, new.iter().cloned());
    println!("{:?}", u);
}
println!("{:?}", v);

// =>
// Splice { drain: Drain([1, 2]), replace_with: Cloned { it: Iter([7, 8]) } }
// [7, 8, 3]

输出的第一行是 v.splice() 的返回值,第二行是 v 本身。但是从 splice 函数的源代码看,并没有看到把 new 追加到 v 的过程,我的疑惑主要在这里。

splice 函数到的实现,如果拆成以下形式,是不是跟原来仍然等效?

pub fn splice<R, I>(&mut self, range: R, replace_with: I) -> Splice<'_, I::IntoIter>
    where R: RangeBounds<usize>, I: IntoIterator<Item=T>
{
    let d = self.drain(range);
    let s = Splice {
        drain: d,
        replace_with: replace_with.into_iter(),
    };
    return s;
}

以上代码我并没有执行成功,因为不知如何去覆盖原来的函数,所以通过一个自定义的 MySplice 来进行观察:

use std::vec::Drain;

#[derive(Debug)]
struct MySplice<'a, I: Iterator + 'a> {
    drain: Drain<'a, I::Item>,
    replace_with: I,
}

let mut v = vec![1, 2, 3];
let new = [7, 8];
{
    let y = &mut v;
    let u = MySplice {
        drain: y.drain(..2),
        replace_with: new.iter().cloned().into_iter(),
    };
    println!("{:?}", u);
}
println!("{:?}", v);

// =>
// : MySplice { drain: Drain([1, 2]), replace_with: Cloned { it: Iter([7, 8]) } }
// : [3]

输出的第一行跟 v.splice() 结果相符,第二行输出只剩 [3] 符合我的预期,但是跟前面 splice() 之后的 v 不一致。

其實是在 drop 裡實現的: https://doc.rust-lang.org/src/alloc/vec.rs.html#2687-2725

我覺得是個很有趣的實現,配合 Iterator, borrow checker 等可以讓人感覺不到 Splice 的存在。

2 个赞