Pre-RFC: Move references

If it is possible to make it so you can put a PIT-move-reference in a type, have it sound, and have it ergonomic, then it should be done. We actually can think of a few ways to make it possible but they're all kinda weird/"alien"? Conceptually, the change of states is a part of function signatures, not types... but due to types also being part of function signatures there's nothing that should prevent merging the two it's just that it's gonna make things even weirder. But then again, functions are already a weird type...

(Also yeah, &mut T->T is not &mut T because one is actually a move reference. That's why we see value in having move references, they make a good first step for eventually having PITs with an unambiguous syntax. If specced correctly, they can add the ability to pass partially initialized values around in a forward-compatible way with PITs. This is, again, not gonna help with DerefMove (both because you can put things back into the Box, but more importantly also because you can Drop/panic after taking some things (but not others) out of the Box.), at least not on its own, but it seems valuable.)

But what if the panic is catched by the caller? I remember discussing that possibility in this or the PITs' thread, but there's nothing related to that in the RFC draft.

Also, that seems an overly complicated machinery. Can't you just delegate all the responsibility for dropping on panics to the callee, avoiding the need for shared drop flags?

Note that arbitrary pointers just can't be cast to references, even in unsafe code. They can however be dereferenced and the reborrowed, which is an unsafe operation.

What happens if a receiver thread got an obligated move reference, moved out and then did a panic? We just get one more source of broken obligations. Scoped threads have already run into similar issues.

Thanks for being watchful - I'll update coercion rules. They should be coercible only if underlying T is safe to move.

This has nothing to do with threads, though. catch_unwind terminates an unwind with no Send bound involved.

Is there really a need for restricting the coercion rules? Can't you just add a <P as DerefMove>::Output: Unpin bound on the DerefMove impl for Pin<P> just like the impls of Deref and DerefMut?

we have to ask: what's the point of DerefMove on Pin?

Ofc, we can and probably should. But the point of !Unpin types is that their values shouldn't be moved (right?), we can enforce it for these references.

They can't be moved after being pinned. Pin already prevents them from being moved, there's no reason to add additional constraints.

The Great Plan ™ was like:

  • forbid moving values of a !Unpin types from a references;
  • add an implementation of DerefMoves for Pin<P>;
  • add a way of safe creation of such values. (&move in...);
  • and have the references (this proposal) that allow natural work with unmovable values (generators, futures, more to come) that wouldn't need Pin<P> at all;
  • forbid moving !Unpin values at all, like not only via references.

The last thing is not backward compatible and breakage might be big.

That seems undoable in practice due to backwards compatibility. And relitigating Pin is unlikely to win any support.

Pin is due to be 'relitigated' to some extent, someday, so that it can be a real part of the language rather than relying on pin_mut and pin_project macros. But backward compatibility breaks are not the way to do it.

(And now is arguably not the time to do it. Though you could say that about everything being proposed here: if RFCed, it will just go to postpone purgatory. Doesn't mean we can't theorize.)

1 Like

Sorry if this is obvious, but why do we need to be able to move temporarly out of a reference? I don't understand what &move inout provides. Can't we just have an &mut reference instead of &move inout? With &mut we can observe its content (instad of de-initilisating it), then write a new value (instead of re-initilisating it)?

I don't understand what's the end goal here... Remove Pin? It would make sense if you would provide something with more value than Pin, but all you're doing is giving another name to Pin, which won't solve anything. All of this at the cost of creating useless differences between references. What would this allow you to do that a Pin<&move T> doesn't?

Edit: What about wrappers? You can wrap an !Unpin type in a wrapper and implement Unpin for the wrapper. Given that you can project an &move T to a field, this means that it's possible to get an &move T to the !Unpin field, which asserts it can't be moved, however you can later move the wrapper, thus moving the !Unpin type too and breaking the pin guarantees.

Sometimes you want to move out of it, do some work and then put back the value. The problem is that if a panic occurs when doing work then there's no value to put back.

I understand the drawbacks, but not the advantages. Why observing (and mutating) an &mut isn't sufficient? Why do you need to move out? (become moving back into it)

For example if you have a function that has signature fn(T) -> T you won't be able to use it to map the value pointed by a &mut T.

2 Likes

Then what advantage does the RFC offer over

&mut Option<T>

Option does the necessary bookkeeping, if a panic occurs by having the drop flag be part of its type. If the RFC implementation does what Option does, but hides it, isn't that more of an argument against rather than in favor of the new reference type?

2 Likes
  • &mut Option<T> is not always possible to use.
  • One of the aims of move references is making Box less special (or rather, allowing other types to be as special as Box) and that's not possible using a &mut Option<T>.

Decided to go with an auto-trait: rev. 5.

1 Like

unsafe { self.ptr as &move T! }

feels a bit weird, shouldn't it be

unsafe { &move *(self.ptr as *move T!)! }

1 Like