5

I am investigating possible reasons that prevent the Rust compiler from optimizing certain code pieces. I found this comment in an issue in rust-lang that alerts me.

We must not optimize away storage of locals that are mutably borrowed, because as @matthewjasper notes in #61430, it isn't decided that the following is UB:

let mut x = String::new();
let p = &mut x as *mut String;
let y = x;
p.write(String::new());

I thought that the lifetime of x ends when it is moved to y. p is dangling while being .write() through. But why this is not decided as UB?

Zhiyao
  • 4,152
  • 2
  • 12
  • 21

1 Answers1

4

In the same thread, a bit further, there is this other comment by cramertj that I think explains a bit this issue. The code exemplified in this other comment is:

let mut x = String::new();
let addr_x: *const String = reference_to_pointer(&x);
drop(x);
ptr::write(addr_x as *mut String, String::new());

The basic idea of this snippet is to be able to drop a local value to run its destructor and reuse its memory allocation to store a new value. This is a pattern that some think it may be useful, but others think it should be UB.

Remember that drop() is not a special function in any way, it just moves the x into its own scope that finishes immediately, so it is more or less equivalent to your original code. You could even use forget() or push the value into a container and the example still holds.

Note that this idiom is actually valid if you use allocated dynamic memory (it is what Vec does under the hood). The issue here is whether it is also valid for stack allocated automatic memory.

rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • Good explanation. It's hard to imagine why this is allowed, though, even if the snippet appears meaningful at first glance. `x` was _moved_, so writing into the dead storage (even if legal) means that its `drop` will never run. How is that ever useful, except to write a version of `mem::forget` that allows you to keep referring to the object? `Vec` will run `ptr::drop_in_place()`, not `drop()`, so it's not exactly the same idiom. (Also, `Vec` will make sure that `ptr::write()` is followed by appropriate adjustment to len, so that the stored value is dropped at the right time.) – user4815162342 May 13 '21 at 10:42
  • 1
    @user4815162342: Local variables are tricky. For example, `let mut a = String::new(); drop(a); a = String::new();`, it is guaranteed that the latter `a` has the same memory address as the former? And if I get a raw pointer to the first, can still be used? Maybe it is just crazy and it should be UB (use `MaybeUninit`) or maybe it has some legitimate uses. Futures or generators, maybe? – rodrigo May 13 '21 at 12:14
  • *For example, `let mut a = String::new(); drop(a); a = String::new();`, it is guaranteed that the latter a has the same memory address as the former?* - Sneaky question! I don't think there should be such a guarantee, I think moving from `a` should invalidate both the location and its contents. But I am beginning to see how the question is non-trivial. – user4815162342 May 14 '21 at 08:41