Mutating the immutable

So I create some object, that is wrapped in a Box and sent to a tokio channel as a Box<dyn AsAny + Send> Like so:

let (tx1, mut rx) = mpsc::channel(10);
...
let j1 = tokio::spawn(async move {
        loop {
            let cat = Cat { lives: 1 };
            let cat = Box::new(cat);
            let message: Box<dyn AsAny + Send> = cat;
            tx1.send(message).await.unwrap();
            sleep(Duration::from_millis(1000)).await;
        }
    });

There is nothing mutable about the object or the Box it's in or the channel sender here.

That object is received from the tokio channel downcast to an its concrete type and then mutated. Note that here the rx end of the channel is mut. Like so:

    let j3 = tokio::spawn(async move {
        loop {
            if let Some(mut message) = rx.recv().await {
                let message: &mut dyn Any = message.as_mut().as_any_mut();
                if let Some(cat) = message.downcast_mut::<Cat>() {
                    cat.lives = 3;
                    println!("Meow");
                }
            }
        }
    });

So some how my immutable object became mutable on passing through the channel.

Now I have heard of "interior mutability" but it seems a bit odd that a channel can have immutable things going in and mutable things coming out.

How should I be thinking about this?

I guess from the point of view of the sending end it does not matter if the object became mutable, it never sees it again after dumping it in the channel.

Complete code is here:

The current owner of a value determines whether it is mutable. As long as the value is not currently borrowed, the owner can always give it away to a different owner, potentially with different mutability:

let x = String::from("hi"); // value is owned by immutable binding `x`. 
let mut y = x; // now it is owned by mutable binding `y`.

Note that the immutable x binding can only be assigned once and can't be used to mutate the string. But this is a property of the binding (i.e., the variable named x), not the value that is bound.

Yes, this is a good way of looking at it.

11 Likes

Note that Rust, unlike C++, doesn't have the concept of an immutable object. In C++, there are complex rules which specify when an object starts to live in a region of memory, what can you do with it during its lifetime (in particular, you generally cannot just blindly transmute it into a different object), and when the object's lifetime ends.

None of that applies to Rust. Rust doesn't have typed memory, and doesn't have any notion of objects (the colloquial usage can be replaced with "stuff" or "doodad" without any semantic loss). Nothing like a C++ "const object" exists, and types don't carry any immutability guarantees either.

Mutability applies only to values, and the owner of a value unilaterally decides whether to treat it as mutable or immutable.

The best approximation to true immutability is &T reference, because you generally can't mutate its referent or anything transitively reachable. Even that immutability is limited: interior mutability allows you to violate that rule and mutate stuff behind a &T reference. However, interior mutability is strictly limited to data contained in an UnsafeCell or behind a raw pointer coming from FFI.

4 Likes

How should I be thinking about this?

I like the real-life analogy here:

Alice takes really good care of her books. She doesn't break the spines, doesn't highlighter them, etc. They're immutable to her.

Sometimes, though, she sells them. "Transfers ownership" of them, you might say. "Moves" them, you might say.

Bob buys books from Alice sometimes. Bob doesn't have any bookmarks, and dog-ears things all the time, and sometimes reads them by the pool where they get wet. The books are mutable to Bob.

Alice doesn't get to care that Bob is going to change the book, though. She sold it. Bob can do what he wants, no matter how Alice thought about it.

(Really, how can she even know? She'd have to be spying on him -- "debugging", you might say -- to find out that he changed the book, since after she relinquished ownership she doesn't even know where it is any more.)

7 Likes

This thread reminded me that for an embarrassingly long time I couldn't understand what the Rust books I read meant by "[variable] binding". It was when I encountered something quite similar to what OP asked about that it eventually clicked for me.

1 Like

There's no such thing as an "immutable type" or a "mutable type". Mutability is a property of bindings, not values or types.

You can mutate anything you own. The channel is a red herring. Your example is no different from

let immut_str = String::from("foo");
let mut mut_str = immut_str; // no magic, you own it
mut_str.push_str("bar");
assert_eq!(mut_str, "foobar");

Recent internals thread notwithstanding...