Use a `mov!` macro, and delete the `Move<P>` type. (Same for "const moves".)
This nearly completely finishes implementing move semantics for C++.
Where, before, we had not finished hooking together `Move<P>` with the
`RvalueReference<T>` in bindings, it now Just Works. Correspondingly, I've
added a handful of direct calls to `mov!(x)` to tests, which would not
have compiled before this change.
This also means that the linked bug is effectively done: the only remaining significant work item that I'm aware of is making it accept `Ctor` on the inside, which is optional.
The newly introduced `mov!` macro itself is very simple, and nearly
identical to C++'s `std::move` in what it does. The abbreviated form is:
```rs
macro_rules! mov {
($p:expr) => {
RvalueReference( {$p}.as_mut() )
};
}
```
In other words, it rust-moves its argument (which is a `Pin<P>`) to a temporary,
and then evaluates to a `RvalueReference<T>` pointing at the pointee of that temporary.
Just like the original mov function, this means that you can automatically prevent
use-after-move if you pass by value:
```rs
foo(mov!(x));
foo(mov!(x)); // fails to compile
```
... but if you call `.as_mut()` you can also choose to use-after-move on purpose:
```rs
foo(mov!(x.as_mut())); // moves x
// `x` still exists, but refers to a moved-from object.
foo(mov!(x)); // succeeds
```
---
This approach is better in nearly every way:
1. By having exactly one rvalue reference type, not two families of types, we eliminate trait implementation issues: we have a single monomorphized type, which is easy to deal with when defining traits which accept rvalue references.
Consider the naive trait implementation for `From` that supports two different rvalue reference types. This is broken:
```rs
impl<P: DerefMut<Target=A>> From<Move<P>> for S {...}
impl<P: DerefMut<Target=B>> From<Move<P>> for S {...}
```
Correctly designing traits that deal with Move is a challenge, and an apparently unnecessary one.
2. By having exactly one rvalue reference type, we simplify the model. There's just plain less code now. `mov` works in the "obvious" way, and is easy to explain.
The downsides:
1. `RvalueReference` is now a strange type (e.g. `!Unpin`). I think this is OK, and anyway I think we should work on fixes to this that make it behave in line with `&T`. (In particular, I believe we should stop using `Unpin` as a `SelfCtor` trait, and define a new auto trait for it... but that's the future, and this is the present, and I still think this is an acceptable loss in the here and now.)
2. More macros. (I believe this is more than outweighed by the simplicity gain.)
3. It's no longer possible to store a `Move<P>` durably, e.g. a `Move<Box<T>>`.
However, since such APIs don't exist in C++ anyway, this is no major loss.
---
### Why wasn't it done this way from the start?
This really is the "obvious" implementation in many ways. But I didn't realize you
could do this at the time, only now that I am revisiting move semantics for
implementing them on traits.
When I originally wrote `mov()`, I thought about making it a macro, but the form I had thought of was something like:
```rs
($p:expr) => {RvalueReference($p.as_mut())};
```
... which would not have disallowed use-after-move. I had thought, at the time, that adding a move here would break callers and not work:
```rs
($p:expr) => {RvalueReference({$p}.as_mut())};
```
... but it actually works just fine. You can store the object as a temporary, which lives for the duration of the enclosing statement. This means that mov!(x) is fine as a parameter. You just can't do something like `let x = mov!(y);`, because the lifetime of the temporary will end and you can't use `x`.
(I learned this "make a temporary, then take a mut reference to it" trick when implementing the `emplace!(x)` expression macro, which does exactly this to materialize temporaries.)
This behavior up exactly with C++, though for completely different reasons. In C++, the return value of `std::move()` must be used in the current statement in order to retain rvalue status. If you assign it to a variable, it becomes an lvalue, and you'd need to move it again. So people used to coding in existing C++ patterns will not feel weirded out by the shape in which one uses the Rust `mov!` macro.
### Example use-after-move-error
Just to show this really does work, I added a `mov!()` by value in a test to see how it explodes. As so:
```
error[E0382]: use of moved value: `x`
--> third_party/crubit/rs_bindings_from_cc/support/ctor.rs:1624:18
|
1621 | let x: Pin<Box<S>> = Box::pin(S);
| - move occurs because `x` has type `std::pin::Pin<Box<S>>`, which does not implement the `Copy` trait
1622 | fn takes_rvalue_reference(_: RvalueReference<S>) {}
1623 | takes_rvalue_reference(mov!(x));
| - value moved here
1624 | let _x = x; // fails to compile: x is moved!
| ^ value used here after move
error: aborting due to previous error
```
PiperOrigin-RevId: 465265577
diff --git a/rs_bindings_from_cc/support/ctor.rs b/rs_bindings_from_cc/support/ctor.rs
index 56cc88f..2ad1d22 100644
--- a/rs_bindings_from_cc/support/ctor.rs
+++ b/rs_bindings_from_cc/support/ctor.rs
@@ -35,9 +35,9 @@
//!
//! ```
//! fn swap(mut x: Pin<&mut CxxClass>, mut y: Pin<&mut CxxClass>) {
-//! emplace!{ let mut tmp = mov(x.as_mut()); }
-//! x.assign(mov(y.as_mut()));
-//! y.assign(mov(tmp));
+//! emplace!{ let mut tmp = mov!(x.as_mut()); }
+//! x.assign(mov!(y.as_mut()));
+//! y.assign(mov!(tmp));
//! }
//! ```
//!
@@ -103,7 +103,7 @@
use std::marker::PhantomData;
use std::mem::{ManuallyDrop, MaybeUninit};
-use std::ops::{Deref, DerefMut};
+use std::ops::Deref;
use std::pin::Pin;
pub use ctor_proc_macros::*;
@@ -223,8 +223,16 @@
// DerefMut based move construction
// ================================
+/// Rvalue Reference (move-reference) type.
+///
+/// This creates a new `T` by moving -- either move-construction (construction
+/// from `RvalueReference(&*P)`), or move-assignment (assignment from
+/// `RvalueReference(&*P)`).
+///
+/// Note: this does not actually move until it is used.
+#[must_use = must_use_ctor_assign!("RvalueReference")]
#[repr(transparent)]
-pub struct RvalueReference<'a, T>(Pin<&'a mut T>);
+pub struct RvalueReference<'a, T>(pub Pin<&'a mut T>);
impl<T> RvalueReference<'_, T> {
pub fn as_const(&self) -> ConstRvalueReference<'_, T> {
@@ -246,36 +254,33 @@
// reborrowed lifetime of the pin, or else it would violate the aliasing
// rules by coexisting with my_pin. Thus, get_ref must return &T, not
// &'a T. (For the same reason, as_const returns a ConstRvalueReference
- // whose lifetime is bound by self, not 'a.)
+ // whose lifetime is bound by self, not 'a.)
&*self.0
}
}
-/// Move type.
-///
-/// This creates a new `P::Target` by moving -- either move-construction
-/// (construction from `RvalueReference(&*P)`), or move-assignment (assignment
-/// from `RvalueReference(&*P)`).
-///
-/// Note: this does not actually move `P` until it is used.
-#[must_use = must_use_ctor_assign!("Move")]
-pub struct Move<P: DerefMut>(Pin<P>);
-
-impl<Output: for<'a> CtorNew<RvalueReference<'a, Output>>, P: DerefMut<Target = Output>> Ctor
- for Move<P>
+impl<'a, T> Ctor for RvalueReference<'a, T>
+where
+ T: CtorNew<Self>,
{
- type Output = Output;
+ type Output = T;
- unsafe fn ctor(mut self, dest: Pin<&mut MaybeUninit<Output>>) {
- Output::ctor_new(RvalueReference(self.0.as_mut())).ctor(dest);
+ unsafe fn ctor(self, dest: Pin<&mut MaybeUninit<T>>) {
+ T::ctor_new(self).ctor(dest);
}
}
-/// !Unpin to override the blanket Ctor impl.
-impl<P> !Unpin for Move<P> {}
+/// !Unpin to override the blanket `Ctor` impl.
+impl<'a, T> !Unpin for RvalueReference<'a, T> {}
+/// Const rvalue reference (move-reference) type. Usually not very helpful.
+///
+/// This implicitly converts to `T` by const-moving -- either
+/// const-move-construction (construction from `ConstRvalueReference(&x)`), or
+/// const-move-assignment (assignment from `ConstRvalueReference(&x)`).
+#[must_use = must_use_ctor_assign!("ConstRvalueReference")]
#[repr(transparent)]
-pub struct ConstRvalueReference<'a, T>(&'a T);
+pub struct ConstRvalueReference<'a, T>(pub &'a T);
impl<'a, T> ConstRvalueReference<'a, T> {
pub fn get_ref(&mut self) -> &'a T {
@@ -283,42 +288,43 @@
}
}
-/// Const-move type. Usually not very helpful.
-///
-/// This implicitly converts to `P::Target` by const-moving -- either
-/// const-move-construction (construction from `ConstRvalueReference(&*P)`), or
-/// const-move-assignment (assignment from `ConstRvalueReference(&*P)`).
-#[must_use = must_use_ctor_assign!("ConstMove")]
-pub struct ConstMove<P: Deref>(P);
-
-impl<Output: for<'a> CtorNew<ConstRvalueReference<'a, Output>>, P: Deref<Target = Output>> Ctor
- for ConstMove<P>
+impl<'a, T> Ctor for ConstRvalueReference<'a, T>
+where
+ T: CtorNew<Self>,
{
- type Output = Output;
+ type Output = T;
- unsafe fn ctor(self, dest: Pin<&mut MaybeUninit<Output>>) {
- Output::ctor_new(ConstRvalueReference(&*self.0)).ctor(dest);
+ unsafe fn ctor(self, dest: Pin<&mut MaybeUninit<T>>) {
+ T::ctor_new(self).ctor(dest);
}
}
-/// !Unpin to override the blanket Ctor impl.
-impl<P> !Unpin for ConstMove<P> {}
+/// !Unpin to override the blanket `Ctor` impl.
+impl<'a, T> !Unpin for ConstRvalueReference<'a, T> {}
/// Creates a "to-be-moved" pointer for `src`.
///
-/// In other words, this is analogous to C++ `std::move`, except that the
-/// pointer can be owned.
+/// In other words, this is analogous to C++ `std::move`, except that this can
+/// directly create an `RvalueReference<T>` out of e.g. a `Pin<Box<T>>`. The
+/// resulting `RvalueReference` has the lifetime of a temporary, after which the
+/// parameter is destroyed.
///
-/// The resulting `Move` can be used as a `CtorNew` or `Assign` source, or as a
-/// `Ctor` directly.
+/// The resulting `RvalueReference` can be used as a `CtorNew` or `Assign`
+/// source, or as a `Ctor` directly.
///
/// Note: this does not actually move the parameter until it is used.
-pub fn mov<P: DerefMut>(src: Pin<P>) -> Move<P> {
- Move(src)
+#[macro_export]
+macro_rules! mov {
+ ($p:expr) => {
+ $crate::RvalueReference(::std::pin::Pin::as_mut(&mut { $p }))
+ };
}
-pub fn const_mov<P: Deref>(src: P) -> ConstMove<P> {
- ConstMove(src)
+#[macro_export]
+macro_rules! const_mov {
+ ($p:expr) => {
+ $crate::ConstRvalueReference(&*{ $p })
+ };
}
// =============
@@ -1006,23 +1012,6 @@
}
}
-/// Assignment from a Move desugars to an assignment from an RvalueReference.
-impl<T: for<'a> Assign<RvalueReference<'a, T>>, P: DerefMut<Target = T>> Assign<Move<P>> for T {
- fn assign(self: Pin<&mut Self>, mut src: Move<P>) {
- self.assign(RvalueReference(src.0.as_mut()));
- }
-}
-
-/// Assignment from a ConstMove desugars to an assignment from a
-/// ConstRvalueReference.
-impl<T: for<'a> Assign<ConstRvalueReference<'a, T>>, P: Deref<Target = T>> Assign<ConstMove<P>>
- for T
-{
- fn assign(self: Pin<&mut Self>, src: ConstMove<P>) {
- self.assign(ConstRvalueReference(&*src.0));
- }
-}
-
// TODO(jeanpierreda): Make these less repetitive.
impl<'a, T: Unpin + CtorNew<&'a T>> Assign<&'a T> for T {
@@ -1619,10 +1608,22 @@
let notify_tester = DropCtorLogger {log};
let new_value = DropCtorLogger {log};
}
- notify_tester.assign(mov(new_value));
+ notify_tester.assign(mov!(new_value));
assert_eq!(*log.borrow(), vec!["move ctor", "drop"]);
}
+ /// Non-obvious fact: you can mov() an owned reference type! Moving anything
+ /// also performs a rust move, but the resulting rvalue reference is
+ /// still valid for a temporary's lifetime.
+ #[test]
+ fn test_mov_box() {
+ struct S;
+ let x: Pin<Box<S>> = Box::pin(S);
+ fn takes_rvalue_reference(_: RvalueReference<S>) {}
+ takes_rvalue_reference(mov!(x));
+ // let _x = x; // fails to compile: x is moved!
+ }
+
#[test]
fn test_ctor_then() {
emplace! {