Simplex
Simplex

Reputation: 1000

Is is possible to have a mutable container of immutable elements?

Below, my TestStruct instance is wrapped in two containers: a Box, then a Vec. From the perspective of a new Rust user it's surprising that moving the Box into the Vec makes both the Box and the TestStruct instance mutable.

struct TestStruct { value: i32 }

fn test_fn() {
    let immutable_instance = TestStruct{value: 123};
    let immutable_box = Box::new(immutable_instance);
    let mut mutable_vector = vec!(immutable_box);

    mutable_vector[0].value = 456;
}

Is there a similar construct whereby the boxed value is immutable, but the container of boxes is mutable? More generally, is it possible to have multiple "layers" of containers without the whole tree being either mutable or immutable?

Upvotes: 1

Views: 282

Answers (1)

Masklinn
Masklinn

Reputation: 42492

Is there a similar construct whereby the boxed value is immutable, but the container of boxes is mutable? More generally, is it possible to have multiple "layers" of containers without the whole tree being either mutable or immutable?

Not really. You could easily create one (just create a wrapper object which implements Deref but not DerefMut), but the reality is that Rust doesn't really see (im)mutability that way, because its main concern is controlling sharing / visibility.

After all, for an external observer what difference is there between

mutable_vector[0].value = 456;

and

mutable_vector[0] = Box::new(TestStruct{value: 456});

?

None is the answer, because Rust's ownership system means it's not possible for an observer to have kept a handle on the original TestStruct, thus they can't know whether that structure was replaced or modified in place[1][2].

If you want to secure your internal state, use visibility instead: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=8a9346072b32cedcf2fccc0eeb9f55c5

mod foo {
    pub struct TestStruct { value: i32 }
    impl TestStruct {
        pub fn new(value: i32) -> Self { Self { value } }
    }
}

fn test_fn() {
    let immutable_instance = foo::TestStruct{value: 123};
    let immutable_box = Box::new(immutable_instance);
    let mut mutable_vector = vec!(immutable_box);

    mutable_vector[0].value = 456;
}

does not compile because from the point of view of test_fn, TestStruct::value is not accessible. Therefore test_fn has no way to mutate a TestStruct unless you add an &mut method on it.


[1]: technically they could check the address in memory and that might tell them, but even then it's not a sure thing (in either direction) hence pinning being a thing.

[2]: this observability distinction is also embraced by other languages, for instance the Clojure language largely falls on the "immutable all the things" side, however it has a concept of transients which allow locally mutable objects

Upvotes: 6

Related Questions