3

I'm trying to understand Rust pointer types and their relation to mutability. Specifically, the ways of declaring a variable which holds the pointer and is itself mutable -- i.e. can be pointed to some other memory, and declaring that the data itself is mutable -- i.e. can be changed through the value of the pointer variable.

This is how I understand plain references work:

let mut a = &5; // a is a mutable pointer to immutable data
let b = &mut 5; // b is an immutable pointer to mutable data

So a can be changed to point to something else, while b can't. However, the data to which b points to can be changed through b, while it can't through a. Do I understand this correctly?

For the second part of the question -- why does Box::new seem to behave differently? This is my current understanding:

let mut a = Box::new(5); // a is a mutable pointer to mutable data
let c = Box::new(7); // c is an immutable pointer to immutable data

new should return a pointer to some heap-allocated data, but the data it points to seems to inherit mutability from the variable which holds the pointer, unlike in the example with references where these two states of mutability are independent! Is that how Box::new is supposed to work? If so, how can I create a pointer value to mutable data on the heap that is stored in an immutable variable?

corazza
  • 31,222
  • 37
  • 115
  • 186

1 Answers1

5

First, you do understand how references behave correctly. mut a is a mutable variable (or, more correctly, a mutable binding), while &mut 5 is a mutable reference pointing to a mutable piece of data (which is implicitly allocated on the stack for you).

Second, Box behaves differently from references because it is fundamentally different from references. Another name for Box is owning/owned pointer. Each Box owns the data it holds, and it does so uniquely, therefore mutability of this data is inherited from mutability of the box itself. So yes, this is exactly how Box should work.

Another, probably more practical, way to understand it is to consider Box<T> exactly equivalent to just T, except of fixed size and allocation method. In other words, Box provides value semantics: it is moved around just like any value and its mutability depends on the binding it is stored in.

There are several ways to create a pointer to a mutable piece of data on the heap while keeping the pointer immutable. The most generic one is RefCell:

use std::cell::RefCell;

struct X { id: u32 }
let x: Box<RefCell<X>> = Box::new(RefCell::new(X { id: 0 }));
x.borrow_mut().id = 1;

Alternatively, you can use Cell (for Copy types):

let x: Box<Cell<u32>> = Box::new(Cell::new(0));
x.set(1);

Note that the above examples are using so-called "internal mutability" which should better be avoided unless you do need it for something. If you want to create a Box with mutable interior only to keep mutability properties, you really shouldn't. It isn't idiomatic and will only result in a syntactic and semantic burden.

You can find a lot of useful information here:

In fact, if you have a question about such fundamental things as mutability, it is probably already explained in the book :)

Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • Thank you for the answer, I'm still going through it, but I've just asked another similar question which you could probably answer: http://stackoverflow.com/questions/31568177/how-does-rust-know-which-types-own-resources. – corazza Jul 22 '15 at 15:57
  • 1
    @jco, regarding your edit. `&mut` references do not *own* the data. There is a reason why references, both `&` and `&mut`, are often called *borrowed*. `&mut` borrows the data uniquely but it does not own it. – Vladimir Matveev Jul 23 '15 at 07:53
  • OK thank you. Is borrowing just syntactic sugar for returning ownership to the previous/calling owner? – corazza Jul 23 '15 at 07:56
  • No. Borrowing is exactly what it means as a regular word - it is a way to provide access to the value you own to someone else without giving up ownership of it. Borrowing is achieved through references. You can find a great explanation in the official Rust book - corresponding links are present in the answer. – Vladimir Matveev Jul 23 '15 at 11:35
  • OK, could we then say that borrowing is 'hardcoded' in the compiler through references, distinct from ownership, then? – corazza Jul 23 '15 at 14:14
  • Well, yes, rules of borrow checking are built into the compiler, that's one of the main strengths and selling points of Rust. There is nothing like it, say, in C++. I wouldn't say that borrowing is entirely distinct from ownership - they are closely linked via the concept of lifetimes. – Vladimir Matveev Jul 23 '15 at 17:32