Any remotely safe way to mutate a &T?

Don't worry, I'm not going to do this in production code.

But say I have an immutable reference which I happen to know is definitely unique. Is there unsafe code I could write to mutate the value without introducing UB?

For example, the following:

unsafe {
  let value = Value::new();
  let a = &value;
  let b: &mut Value = std::mem::transmute(a);
  b.mut_method();
}

Appears to work for me, but doesn't compile without adding an #[allow(mutable_transmutes)].

Is there a way of doing this without wrapping the type in UnsafeCell or similar?

Use RefCell or Mutex. Note that Mutex-es are cheap if there's no contention, usually(if not always) a single atomic load.

I know that's generally the right way to do interior mutability - but just to clarify, I specifically want to mutate the target of an immutable reference, without changing the type at all.

For more context: the reason I want to do this is that I want a mechanism for debugging something that I will not compile into the release-mode version of the code. I don't want to complicate any of the types I use, or complicate the reading of the value at other call sites.

If there really is no better way than a transmute, I'll probably just do that - but I was curious if there was a slightly better way.

If you just want to explore, and intend to only risk potential execution derailment in debugging, then sure. The optimizer in turn is free to assume that a program that modifies a value behind an immutable reference is nonsensical, so you'll get unpredictable behavior depending on how hard you poke the values in question.

1 Like

It is always UB, the abstract machine does not allow it. Just lucky that a particular compile appears to work, (practically impossible to reason with the logic the compiler performs; or easier to just check the machine code after.)

What is wrong with using extra thread local or static for you debugging?

I would be more likely to write &mut *(a as *const _ as *mut _) but still UB so not something you want to keep if others will see it.

No. Transmuting a &T to a &mut T is undefined behavior. If you want to mutate an immutable reference for debug-only code, then one approach might be to use conditional compilation to use an UnsafeCell<T> in debug mode, and &T otherwise.

4 Likes

The only sound way to mutate behind a shared reference (&T) is by using UnsafeCell. Atomic and other thread synchronization primitives use UnsafeCell internally to allow mutation.

1 Like

From The Nomicon:

8 Likes

Haha, alright well it sounds like it's literally always UB, I'm OK with that.

I did try a few condition-compilation based methods, but the problem is that I really don't want to complicate the sites where the value is read, with closures or lock guards or whatever. When the code runs in release mode, these values are functionally const, and I like having that guarantee in the final executable.

So I'll risk some horrible debug-mode UB side-effects in the meantime. I promise I'll come back when it all goes wrong and leave a warning for future optimists :slight_smile:

3 Likes

I had another go at @BurntSushi's idea after remembering about the Deref trait existing, and this at least does not use transmute: playground link

I'm not sure it's any better because I don't fully understand the rules, but it seems to work?

The Sync attribute is a unsound because refresh can cause a data-race if called from multiple threads

1 Like

This, and to solve it I would suggest annotating refresh as unsafe since you said earlier that you are certain that only one thread has the borrow. This makes the unsafety clearer in the code that uses it.

(as for why I'm calling this the "solution:" generally speaking, we consider the module to be the boundary for safety, so if something that can be used unsafely is visible to code outside the module, it must be marked unsafe)

You should document that refresh must not be called while a borrow from deref exists.

3 Likes

Noted, thank you! This is fine, since refresh() should never really be called in release mode anyway.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.