为什么 Rust 的 std::iter::zip 似乎具有内部可变性?

wha*_*0xx 6 rust

据我了解,我不应该能够对不可变的数据进行可变引用。因此,如果我想创建一些基于改变内部状态struct的实现Iterator,那么我必须将其任何实例声明struct为可变的,以便能够创建所需的可变引用。

为了进行演示,我构建了一个简单的示例,如下所示:

struct MyIter {
    internal_state: u32
}

impl MyIter {
    fn new() -> MyIter {
        MyIter{ internal_state: 0 }
    }
}

impl Iterator for MyIter {
    type Item = u32;
    fn next(&mut self) -> Option<u32> {
        self.internal_state += 1;
        Some(self.internal_state)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::iter::zip;

    #[test]
    fn mutable_declaration_no_zip() {
        let mut my_iter = MyIter::new();
        assert_eq!(my_iter.next(), Some(1));
        assert_eq!(my_iter.next(), Some(2));
        assert_eq!(my_iter.next(), Some(3));
    }
}
Run Code Online (Sandbox Code Playgroud)

这可以正常编译并通过,而如果我将测试函数更改为具有不可变的声明my_iter,如下所示,则它拒绝使用预期消息进行编译error[E0596]: cannot borrow 'my_iter' as mutable, as it is not declared as mutable

#[test]
fn immutable_declartaion_no_zip() {
    let my_iter = MyIter::new();
    assert_eq!(my_iter.next(), Some(1));
    assert_eq!(my_iter.next(), Some(2));
    assert_eq!(my_iter.next(), Some(3));
}
Run Code Online (Sandbox Code Playgroud)

我的问题涉及我基本上与上面相同但用于std::iter::zip执行迭代的情况。现在发生的情况是,即使我声明my_iter为不可变,测试也会编译并通过,这意味着在幕后zip已经改变了我传递的不可变变量的状态!

#[test]
fn immutable_declaration_zip() {
    let my_iter = MyIter::new();
    let nums: [u32;10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    for (i, num) in zip(my_iter, nums.iter()) {
        assert_eq!(i, *num);
    }
}
Run Code Online (Sandbox Code Playgroud)

那么,有人可以解释一下为什么zip允许这样做吗?如何zip调用该next()方法,从而生成对我声明为不可变的数据的可变引用?

Sil*_*olo 17

可变性注释(mut或缺少可变性注释)跟随变量,而不是值。

let mut my_iter = MyIter::new();
Run Code Online (Sandbox Code Playgroud)

在这里,可变的不是它的my_iter。就是它my_iter本身。只要my_iter拥有该值,它就是可变的。所以你可以调用my_iter.next(),这需要一个可变引用。

let my_iter = MyIter::new();
Run Code Online (Sandbox Code Playgroud)

相同的论点,但没有mut. 只要my_iter拥有迭代器,它就是不可变的,因此不允许您对其进行可变引用。

然而,这里的关键词是“只要my_iter拥有迭代器”。

zip(my_iter, nums.iter())
Run Code Online (Sandbox Code Playgroud)

签名是std::iter::zip

pub fn zip<A, B>(
    a: A,
    b: B
) -> Zip<<A as IntoIterator>::IntoIter, <B as IntoIterator>::IntoIter>
where
    A: IntoIterator,
    B: IntoIterator,
Run Code Online (Sandbox Code Playgroud)

它按值获取两个参数。当您调用 时zip,该函数就获得了该值的所有权。这意味着当前没有人拥有对该值的引用(可变或不可变),并且zip可以自由决定它是否可变,因为它现在是唯一所有者。