In the code below, I use unsafe code to convert an immutable reference into a mutable pointer and then I try to edit the inner value by means of this mutable pointer.
fn main() {
#[repr(transparent)]
struct Item(isize);
impl Item {
#[inline]
fn ptr(&self) -> *mut isize {
self as *const Item as *mut isize
}
fn increment(&self) {
let val = self.0 + 1;
unsafe {std::ptr::write(self.ptr(), val)}
}
}
let item = Item(22);
println!("before = {}", item.0);
item.increment();
println!("after = {}", item.0);
}
When I compile this under debug mode, the results are as expected and the value is indeed incremented. However, under release mode,the value is not incremented at all although that section of the code is run. Also, the following other types of mutating the value don't seem to work either
unsafe {*&mut *(self.ptr()) += 1;}
std::mem::replace()
std::mem::swap()
Rust does not allow you to mutate a value obtained from an immutable reference, ever, even if you cast it to into a mutable reference or mutable raw pointer. The compiler is allowed to assume that a value that's passed around using an immutable reference will never change, and it can optimize with this in mind. Breaking this assumption is undefined behavior, and can break your program in very unexpected ways. This is probably what happens in your example: the compiler sees that increment
takes an immutable reference, and therefore item
must be the same before and after the call, and it can optimize the code as if that's the case.
There's only one exception to this: UnsafeCell
. UnsafeCell
is a special type that allows for interior mutability, letting you get a &mut T
from an &UnsafeCell<T>
(with unsafe code), and telling the compiler that it can't assume that the content is unchanged even if you have an immutable reference to the cell.
The problem with UnsafeCell
though is that you still need to uphold Rust's borrowing guarantees (e.g. mutable references must be unique while they exists), but the compiler will rely on you as a programmer to make sure these are upheld. For that reason, it's generally recommended that you instead use safe types, such as Cell
, RefCell
, Mutex
, RwLock
or atomic types. These all have different trade-offs, but they're all built as safe wrappers around UnsafeCell
to allow for interior mutability while having some checks for the borrowing guarantees (some at compile time, some at runtime).
Cell
, for example, could work for your example:
fn main() {
#[repr(transparent)]
struct Item(Cell<isize>);
impl Item {
fn increment(&self) {
let val = self.0.get() + 1;
self.0.set(val);
}
}
let item = Item(Cell::new(22));
println!("before = {}", item.0.get());
item.increment();
println!("after = {}", item.0.get());
}