rust-lang / rfcs

RFCs for changes to Rust
https://rust-lang.github.io/rfcs/
Apache License 2.0
5.89k stars 1.56k forks source link

`mem::write`, a safe `ptr::write` #1304

Open reem opened 8 years ago

reem commented 8 years ago

Now that mem::forget is safe, it would be useful to have a function like:

pub fn write<T>(target: &mut T, value: T) {
    unsafe { ptr::write(target, value) }
}

probably in std::mem, alongside swap and replace.

Gankra commented 8 years ago

Holy shit my brain what

reem commented 8 years ago

"proof" that it's safe (you can write it with safe code):

pub fn write<T>(target: &mut T, value: T) {
    mem::forget(mem::replace(target, value));
}

but this is less efficient since you read the old value - this might be highly undesirable for large structs, so the more efficient unsafe implementation should be used.

bluss commented 8 years ago

docs for ptr::write are not up to date: “Beyond accepting a raw pointer, this operation is unsafe because it does not drop the contents of dst.” (Edit: This is now fixed)

Gankra commented 8 years ago

Yeah I suppose it's literally only unsafe because it derefs a raw pointer now.

SimonSapin commented 8 years ago

Maybe call it overwrite or erase_with or something, to suggest that it’s different from *target = value which runs the destructor of the previous value?

ghost commented 8 years ago

Can you provide a use case that isn't dealing with unsafe code anyway? I see no point in having a function of dubious value advertised within the standard library when almost all use cases will be either dealing with unsafe code anyway (and there is a perfectly fine function already for unsafe code, which is at least as ergonomic to use) or highly likely to be a bug. The only thing I can think of is overwriting large, POD structs. I do not find it a compelling use case myself, and it seems like something an optimizer should be able handle when doing just plain *target = val; (I don't know if this is the case though).

reem commented 8 years ago

Generally speaking I don't think there's a good reason for people to introduce a raw pointer dereference into their own code where there is no need for one. Even if the context is unsafe, you can still make your life a tiny bit easier by isolating the places where unsafety can originate from.

There are also the cases where you want to do this for pure efficiency reasons, and there's no reason to then introduce unsafe where none is really needed.

Gankra commented 8 years ago

@reem &mut coerces to *mut so ptr::write already works for that. That's why it's a free fn.

reem commented 8 years ago

@Gankro right, except ptr::write is unsafe to call, and this function would not be. I personally prefer not to have to coerce to a raw pointer if I can stay in &-land.

SimonSapin commented 8 years ago

But of course mem::write does coerce to a raw pointer internally. You can always have this one-liner function in your own crate.

I actually think this is a good pattern: write simple functions like this to tighten bounds on or "shape" of generic types. As opposed to e.g. using transmute without type annotation, relying on inference, which could be dangerous if some signature elsewhere changes and causes the meaning of that transmute to change. Or in this case, the signature of this mem::write function ensures that the raw pointer given to ptr::write is valid.

The function doesn’t need to be in std to achieve this.

codyps commented 8 years ago

It may not need to be in std, but if it is a good pattern, why shouldn't it be included?

abonander commented 8 years ago

I think generally useful, if trivial, safe wrappers around unsafe functions need a home somewhere, either in the stdlib or a well vetted or blessed external crate.

It's a big relief not having to have any unsafe blocks in your crate because you don't have to spend brain cycles on that niggling worry about undefined behavior--regardless of how well founded those concerns actually are.

I think it's doing Rust's users and design goals a great disservice to say, "Yeah, it's a useful wrapper around an unsafe function, but it's trivial so just write it yourself."

It's also giving a bit of a mixed message because the community at large tends to discourage more complex solutions using unsafe when a safe one exists, even when the latter is clunkier or less performant. You could argue that the difference between the two situations is the complexity of the solution itself, but that isn't immediately apparent.

A straightforward and not uncommon example is getting two mutable references to nonequal indices in a slice. Since slices don't provide this method directly, it's up to the user to figure out how to implement it or try to find a crate that's done it already. The split_at_mut() solution is only one line longer than the unsafe version in the way I implemented it, but it comes with a good bit of additional cognitive overhead. It also requires an extra comparison and branch which only exist to facilitate the workaround.