Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Rust's std::iter::zip seem to have interior mutability?

Tags:

rust

As I understand, I shouldn't be able to have a mutable reference to an immutable piece of data. As such, if I want to create some struct that implements Iterator based on mutating an internal state, then I have to declare any instance of that struct as mutable in order to be able to create the mutable references needed.

To demonstrate, I constructed a simple example, below:

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));
    }
}

This compiles fine and passes, whereas if I change the test function to instead have an immutable declaration for my_iter, as below, then it refuses to compile with the expected message of 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));
}

My question concerns the case where I do basically the same as above but use std::iter::zip to perform the iteration. What happens now is that even though I declare my_iter as immutable, the test compiles and passes, meaning that under the hood zip has mutated the state of the immutable variable that I passed it!

#[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);
    }
}

So, could someone please explain why zip was set up to be allowed to do this? How can zip call the next() method and hence generate mutable references to data that I have declared as immutable?

like image 540
whatf0xx Avatar asked Aug 28 '26 12:08

whatf0xx


1 Answers

Mutability annotations (mut or lack thereof) follow the variable, not the value.

let mut my_iter = MyIter::new();

Here, it's not the value of my_iter that's mutable. It's my_iter itself. As long as my_iter owns that value, it's mutable. So you can call my_iter.next(), which requires a mutable reference.

let my_iter = MyIter::new();

Same argument but without the mut. As long as my_iter owns the iterator, it's immutable, so you aren't permitted to make a mutable reference to it.

However, the key phrase here is "As long as my_iter owns the iterator".

zip(my_iter, nums.iter())

The signature for std::iter::zip is

pub fn zip<A, B>(
    a: A,
    b: B
) -> Zip<<A as IntoIterator>::IntoIter, <B as IntoIterator>::IntoIter>
where
    A: IntoIterator,
    B: IntoIterator,

It takes both arguments by value. The moment you call zip, that function takes ownership of the value. That means that nobody currently has a reference (either mutable or immutable) to the value, and zip is free to decide if it's mutable or not, since it's now the exclusive owner.

like image 63
Silvio Mayolo Avatar answered Aug 30 '26 07:08

Silvio Mayolo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!