What is the difference between `std::ptr::write(ptr, value)` and `*ptr = value`?

Hello all,
I'm a Rust beginner and I'm writing something that has to use raw pointers.
I use std::alloc::alloc() to allocate a block of memory, and then try to write data to it. I found that when I use *ptr = value, I get a segmentation error (malloc: *** error for object 0x7000000010060468: pointer being freed was not allocated) in some cases . But using std::ptr::write(ptr, value) works fine. I would like to know the difference between them.

Assignment drops the old value before writing the new one. (It has to, otherwise it would leak.) If you have an uninitialized value (that you just allocated), then of course dropping it is an error (UB).

3 Likes

Thanks, so *ptr = value is equivalent to ptr.drop_in_place(); ptr.write(value);?

That's roughly correct, but the value is evaluated before the pointer and the drop_in_place. So a slightly closer version would be something like this:

{
    let value = value_expr();
    let ptr = ptr_expr();
    ptr.drop_in_place();
    ptr.write(value);
}
4 Likes

I understand, Thanks you!

Note also that the ptr.write(value) happens even if drop_in_place panics.

That's a good point. So one might think of it as

{
    let value = value_expr(); // can panic
    let ptr = ptr::addr_of_mut!(place_expr()); // can panic
    let prev = ptr.read(); // always succeeds
    ptr.write(value); // always succeeds
    drop(prev); // can panic
}

Or, if you prefer, that's essentially

{ mem::replace(&mut PLACE_EXPR, VALUE_EXPR); }

(But that's way more subtle -- the ; is very important -- and not quite the same in every detail.)

Well, no. The value is not moved before it gets dropped. This is important if the value is pinned. (See the implementation of Pin::set.)

An exact implementation of what *ptr = value; does would be the following:

use std::mem::ManuallyDrop;

struct DropGuard<T> {
    ptr: *mut T,
    value: ManuallyDrop<T>,
}

impl<T> Drop for DropGuard<T> {
    fn drop(&mut self) {
        std::ptr::write(self.ptr, ManuallyDrop::take(&mut self.value));
    }
}

{
    let value = value_expr();
    let ptr = ptr_expr();

    let guard = DropGuard {
        value: ManuallyDrop::new(value),
        ptr: ptr,
    }
    std::mem::drop_in_place(guard.ptr);
    
    // destructor of guard runs here
}
5 Likes