- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 4.2k
Introduce MovingPtr as a safer alternative to moving typed values by raw pointer #20877
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a quick pass over the safety comments and they mostly seem reasonable. partial_move and move_field are a bit complex to reason about though and I need to think about them a bit more.
| /// # Safety | ||
| /// The fields moved out of in `f` must not be accessed or dropped after this function returns. | ||
| #[inline] | ||
| pub unsafe fn partial_move( | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where do you need this nasty function in the other pr?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other PR splits the bundle in two: components without effects to be inserted and related components (i.e. SpawnRelated) that only have effects. The latter would need to operate on the partially moved instance of the bundle, and hence why it returns a MovingPtr<'a, MaybeUninit<T>>.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added an example for both this and move_field on how to use this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the goal here is to destructure a struct without moving the parts. So ideally this is only done on !Drop structs, just like normal destructuring. Seems like it would be pretty hard to make this safer without a macro. You could return an iterator of OwningPtr's from a iterator of offsets, but since this is typed I can't think of a way to make it work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. The original intent was to use this as a building block for a macro like the one shown here: #20877 (comment), but given that we're already primarily using this in a derive macro/all_tuples tower, I wanted to eschew that extra compilation overhead.
The point about !Drop is pretty strong. In their current construction derive(Bundle) types that implement Drop may access values that were already moved out of.
EDIT: It looks like it's currently a compile failure. #20772 would allow B: Drop bundles to be derived, but also force them to be forgotten.
        
          
                crates/bevy_ptr/src/lib.rs
              
                Outdated
          
        
      | /// - `self` should not be accesesed or dropped as if it were a complete value. | ||
| /// Other fields that have not been moved out of may still be accessed or dropped separately. | ||
| #[inline] | ||
| pub unsafe fn move_field<U>(&self, byte_offset: usize) -> MovingPtr<'a, U, A> { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is 'a the correct lifetime here or should it be tied to &self and be '_?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 'a lifetime is accurate. This function's use is meant for decomposition, and I'm not actually sure how to best structure this. This needs to not take ownership of self, as to not drop whole value, but needs to extract all fields as MovingPtrs and then immediately forget the top level MovingPtr before any of the fields are used. Ideally this would be done without extra traits and yet another all_tuples impl tower. Example:
let field_a = parent.move_field::<FieldAType>(offset_of!(Self, field_a));
let field_b = parent.move_field::<FieldBType>(offset_of!(Self, field_b));
let field_c = parent.move_field::<FieldCType>(offset_of!(Self, field_c));
mem::forget(parent);
insert(field_a);
insert(field_b);
insert(field_c);This must be done in this order or else we risk double-dropping already moved fields if an intermediate call to the hypothetical insert panics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, we should have a macro like the following to simplify the above to:
decompose_moving_ptr!(parent, 
   field_a: FieldAType => insert(field_a),
   field_b: FieldBType => insert(field_b),
   field_c: FieldCType => insert(field_c),
);There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added an example for both this and partial_move on how to use this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To try to make this a bit easier, I added a macro via deconstruct_moving_ptr to make sure its usage patterns are obeyed.
…ns instead of constants
| 
 To try to make this a bit easier, I added a macro via  Otherwise, I think this is in a good enough spot for use in #20772 now. | 
| /// | ||
| /// [`assign_to`]: MovingPtr::assign_to | ||
| #[macro_export] | ||
| macro_rules! deconstruct_moving_ptr { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this macro should have something like this to force the user to declare every field, though it might be overkill for fields that are !Drop
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to suggest that this would be handled separately. The derive(Bundle) types might need something like this, but the all_tuples impls wouldn't require this assertion.
Co-authored-by: Sandor <alexaegis@pm.me>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a small nit
Objective
Make moving potentially large values, like those seen in #20571 and those seen by #20772, safer and easier to review.
Solution
Introduce
MovingPtr<'a, T>as a wrapper aroundNonNull<T>. This type:Box<T>that does not own the allocation it points to. It will drop the value it points to when it's dropped, but will not deallocate when it's dropped.OwningPtrin that it owns the values it points to and has an associated lifetime, but it has a concrete type.CloneorCopy.MovingPtrs of the value's fields.ManuallyDrop<T>andMaybeUninit<T>.MovingPtrthat copies the value into a target pointer or reads it onto the stack.MovingPtr<'a, MaybeUninit<T>>in its stead.OwningPtrfor use cases like Introduce Command::apply_raw #20593.Fromimpl for converting toOwningPtrto type erasure without loss of the lifetime or alignment requirements.TryFromimpl to attempt to convert an unaligned instance into a aligned one. Can be combined withDebugCheckedUnwrapto assert that the conversion is sound.deconstruct_moving_ptrprovides a less error-prone way of decomposing aMovingPtrinto separateMovingPtrfor its fields.This design is loosely based on the outptr proposal for in-place construction, but currently eschews the requirements for a derive macro.
Testing
CI, new doc tests pass.