Slide 1

Slide 1 text

Move Constructors: Is It Possible? Miguel Young de la Sota @DrawsMiguel - mcyoung.xyz

Slide 2

Slide 2 text

Self-Referential Types

Slide 3

Slide 3 text

struct Cycle { x: i32, ptr_to_x: &mut i32, } impl Cycle { }

Slide 4

Slide 4 text

error[E0106]: missing lifetime specifier --> :3:15 | 3 | ptr_to_x: &mut i32, | ^ expected named lifetime parameter | help: consider introducing a named lifetime parameter | 1 ~ struct Cycle<’a> { 2 | x: i32, 3 ~ ptr_to_x: &’a mut i32, |

Slide 5

Slide 5 text

error[E0106]: missing lifetime specifier --> :3:15 | 3 | ptr_to_x: &mut i32, | ^ expected named lifetime parameter | help: consider introducing a named lifetime parameter | 1 ~ struct Cycle { 2 | x: i32, 3 ~ ptr_to_x: &’??? mut i32, |

Slide 6

Slide 6 text

struct Cycle { x: i32, ptr_to_x: *mut i32, } impl Cycle { }

Slide 7

Slide 7 text

struct Cycle { x: i32, ptr_to_x: *mut i32, } impl Cycle { } let mut cycle = Cycle { x: 42, ptr_to_x: null_mut(), }; x: 42 p: 0x00000000 cycle @ 0x00000000

Slide 8

Slide 8 text

x: 42 p: 0x00000000 cycle @ 0x00000000 struct Cycle { x: i32, ptr_to_x: *mut i32, } impl Cycle { } let mut cycle = Cycle { x: 42, ptr_to_x: null_mut(), }; cycle.ptr_to_x = &mut cycle.x; x: 42 p: 0x7fc55012 cycle @ 0x7fc55012

Slide 9

Slide 9 text

x: 42 p: 0x00000000 cycle @ 0x00000000 struct Cycle { x: i32, ptr_to_x: *mut i32, } impl Cycle { } let mut cycle = Cycle { x: 42, ptr_to_x: null_mut(), }; cycle.ptr_to_x = &mut cycle.x; x: 42 p: 0x7fc55012 cycle @ 0x7fc55012

Slide 10

Slide 10 text

struct Cycle { x: i32, ptr_to_x: *mut i32, } impl Cycle { fn get(&self) -> Option { if self.ptr_to_x.is_null() { return None; } unsafe { Some(*self.ptr_to_x) } } } let mut cycle = Cycle { x: 42, ptr_to_x: null_mut(), }; cycle.ptr_to_x = &mut cycle.x; let y = cycle.get(); x: 42 p: 0x7fc55012 cycle @ 0x7fc55012

Slide 11

Slide 11 text

x: 42 p: 0x7fc55012 cycle @ 0x7fc55012 INVARIANT! ptr_to_x is always either null or points to x in the same struct. x: 42 p: 0x00000000 cycle @ 0x7fc55012 OK!

Slide 12

Slide 12 text

x: 42 p: 0x7fc55012 cycle @ 0x7fc55012 INVARIANT! ptr_to_x is always either null or points to x in the same struct. x: 42 p: 0x00000000 cycle @ 0x7fc55012 x: 42 p: 0x7fc55012 cycle @ 0x7fc5501a OK! Not allowed!

Slide 13

Slide 13 text

struct Cycle { x: i32, ptr_to_x: *mut i32, } impl Cycle { fn new(x: i32) -> Self { let mut cyc = Cycle { x, ptr_to_x: null_mut(), }; cyc.ptr_to_x = &mut cyc.x; cyc } fn get(&self) -> Option { /* ... */ } } let mut cycle = Cycle { x: 42, ptr_to_x: null_mut(), }; cycle.ptr_to_x = &mut cycle.x; let cycle = Cycle::new(42); let y = cycle.get(); x: 42 p: 0x7fc55012 cycle @ 0x7fc55012

Slide 14

Slide 14 text

struct Cycle { x: i32, ptr_to_x: *mut i32, } impl Cycle { /* ... */ } let mut cycle = Cycle { x: 42, ptr_to_x: null_mut(), }; cycle.ptr_to_x = &mut cycle.x; let cycle = Cycle::new(42); let y = cycle.get(); x: 42 p: 0x7fc55012 cycle @ 0x7fc55000 x: 42 p: 0x7fc55012 cycle @ 0x7fc55012 Callee Stack Caller Stack

Slide 15

Slide 15 text

struct Cycle { x: i32, ptr_to_x: *mut i32, } impl Cycle { /* ... */ } let mut cycle = Cycle { x: 42, ptr_to_x: null_mut(), }; cycle.ptr_to_x = &mut cycle.x; let cycle = Cycle::new(42); let y = cycle.get(); // Memory error! x: 42 p: 0x7fc55012 cycle @ 0x7fc55000 x: 42 p: 0x7fc55012 cycle @ 0x7fc55012 Callee Stack Caller Stack

Slide 16

Slide 16 text

Rust moves are just bytewise copies. Self-referential types’ invariants break if they get moved.

Slide 17

Slide 17 text

Pin

it!

Slide 18

Slide 18 text

● A Pin

wraps a pointer type P (e.g., &mut T); unsafe code can assume its address is fixed forever, making it a “pinned pointer”.

Slide 19

Slide 19 text

● A Pin

wraps a pointer type P (e.g., &mut T); unsafe code can assume its address is fixed forever, making it a “pinned pointer”. ● Pin

stops safe code from moving out of it.

Slide 20

Slide 20 text

● A Pin

wraps a pointer type P (e.g., &mut T); unsafe code can assume its address is fixed forever, making it a “pinned pointer”. ● Pin

stops safe code from moving out of it. ● Unpin types (i.e. most types) can be moved out of a Pin

; non-Unpin types cannot, because they are “address-sensitive”.

Slide 21

Slide 21 text

● A Pin

wraps a pointer type P (e.g., &mut T); unsafe code can assume its address is fixed forever, making it a “pinned pointer”. ● Pin

stops safe code from moving out of it. ● Unpin types (i.e. most types) can be moved out of a Pin

; non-Unpin types cannot, because they are “address-sensitive”. ● Pin

is implemented as a library, unlike builtin types like &[T] and *mut i32.

Slide 22

Slide 22 text

struct Cycle { x: i32, ptr_to_x: *mut i32, } impl Cycle { fn new(x: i32) -> Self { let mut cyc = Cycle { x, ptr_to_x: null_mut(), }; cyc.ptr_to_x = &mut cyc.x; cyc } fn get(&self) -> Option { /* ... */ } } Danger!

Slide 23

Slide 23 text

struct Cycle { x: i32, ptr_to_x: *mut i32, } impl Cycle { fn new(x: i32) -> Pin> { let mut cyc = Box::new(Cycle { x, ptr_to_x: null_mut(), }); cyc.ptr_to_x = &mut cyc.x; Box::into_pin(cyc) } fn get(&self) -> Option { /* ... */ } } Less Danger

Slide 24

Slide 24 text

struct Cycle { x: i32, ptr_to_x: *mut i32, _pin: PhantomPinned, } impl Cycle { fn new(x: i32) -> Pin> { let mut cyc = Box::new(Cycle { x, ptr_to_x: null_mut(), _pin: PhantomPinned, }); cyc.ptr_to_x = &mut cyc.x; Box::into_pin(cyc) } fn get(&self) -> Option { /* ... */ } }

Slide 25

Slide 25 text

struct Cycle { x: i32, ptr_to_x: *mut i32, _pin: PhantomPinned, } impl Cycle { fn new(x: i32) -> Pin> { let mut cyc = Box::new(Cycle { x, ptr_to_x: null_mut(), _pin: PhantomPinned, }); cyc.ptr_to_x = &mut cyc.x; Box::into_pin(cyc) } fn get(&self) -> Option { /* ... */ } } let cycle = Cycle::new(42); let y = cycle.get(); // Ok!

Slide 26

Slide 26 text

struct Cycle { x: i32, ptr_to_x: *mut i32, _pin: PhantomPinned, } impl Cycle { fn new(x: i32) -> Pin> { let mut cyc = Box::new(Cycle { x, ptr_to_x: null_mut(), _pin: PhantomPinned, }); cyc.ptr_to_x = &mut cyc.x; Box::into_pin(cyc) } fn get(&self) -> Option { /* ... */ } } let cycle = Cycle::new(42); let y = cycle.get(); // Ok! // error: Can’t move out of Pin! // let bad = *cycle;

Slide 27

Slide 27 text

Pin

lets unsafe code assume values won’t ever move. Pin

stops safe code from accidentally moving them.

Slide 28

Slide 28 text

struct Cycle { x: i32, ptr_to_x: *mut i32, _pin: PhantomPinned, } impl Cycle { fn new(x: i32) -> Pin> { let mut cyc = Box::new(Cycle { x, ptr_to_x: null_mut(), _pin: PhantomPinned, }); cyc.ptr_to_x = &mut cyc.x; Box::into_pin(cyc) } fn get(&self) -> Option { /* ... */ } } let cycle = Cycle::new(42); let y = cycle.get(); // Ok! // error: Can’t move out of Pin! // let bad = *cycle;

Slide 29

Slide 29 text

struct Cycle { x: i32, ptr_to_x: *mut i32, _pin: PhantomPinned, } impl Cycle { fn new(x: i32) -> Pin> { let mut cyc = Box::new(Cycle { x, ptr_to_x: null_mut(), _pin: PhantomPinned, }); cyc.ptr_to_x = &mut cyc.x; Box::into_pin(cyc) } fn get(&self) -> Option { /* ... */ } } let cycle = Cycle::new(42); let y = cycle.get(); // Ok! // error: Can’t move out of Pin! // let bad = *cycle; Need a pointer to Pin in the first place!

Slide 30

Slide 30 text

● Can’t return a stack-pinned data, need to Box it. ● Can’t directly pin data in collections like Vec and HashMap; need to Box them. “Address-sensitive” types feel like second-class citizens in Rust. They’re not zero cost.

Slide 31

Slide 31 text

C++

Slide 32

Slide 32 text

C++ (No, this isn’t a talk about C++.)

Slide 33

Slide 33 text

x: 42 p: 0x7fc55012 cycle @ 0x7fc55012 INVARIANT! ptr_to_x is always either null or points to x in the same struct. x: 42 p: 0x00000000 cycle @ 0x7fc55012 x: 42 p: 0x7fc55012 cycle @ 0x7fc5501a OK! Not allowed! Recall...

Slide 34

Slide 34 text

class Cycle { private: int x_; int* ptr_to_x_; };

Slide 35

Slide 35 text

class Cycle { public: Cycle(int x) { this->x_ = x; this->ptr_to_x_ = &this->x_; } private: int x_; int* ptr_to_x_; }; x: 42 p: 0xTBD *this @ 0xTBD

Slide 36

Slide 36 text

class Cycle { public: Cycle(int x) { this->x_ = x; this->ptr_to_x_ = &this->x_; } private: int x_; int* ptr_to_x_; }; auto cycle = Cycle(42); x: 42 p: 0x7fc55012 cycle @ 0x7fc55012 x: 42 p: 0xTBD *this @ 0xTBD Ctor

Slide 37

Slide 37 text

class Cycle { public: Cycle(int x) { this->x_ = x; this->ptr_to_x_ = &this->x_; } private: int x_; int* ptr_to_x_; }; char alloc[sizeof(Cycle)]; new (&alloc) Cycle(42); auto cycle = (Cycle*) &alloc; x: 42 p: 0x7fc55012 cycle @ 0x7fc55012 x: 42 p: 0xTBD *this @ 0xTBD Ctor “raw” constructor

Slide 38

Slide 38 text

Values in C++ are always constructed “in place”. Therefore, C++ values are always implicitly Pinned.

Slide 39

Slide 39 text

What would a “raw constructor” look like in Rust?

Slide 40

Slide 40 text

class Cycle { public: Cycle(int x) { this->x_ = x; this->ptr_to_x_ = &this->x_; } /* ... */ }; char alloc[sizeof(Cycle)]; new (&alloc) Cycle(42); auto cycle = (Cycle*) &alloc; struct Cycle { /* ... */ } let mut alloc = MaybeUninit::uninit(); ???.new(&mut alloc); let cycle = unsafe { &*alloc.as_ptr().cast::() }; C++ Rust

Slide 41

Slide 41 text

class Cycle { public: Cycle(int x) { this->x_ = x; this->ptr_to_x_ = &this->x_; } /* ... */ }; char alloc[sizeof(Cycle)]; new (&alloc) Cycle(42); auto cycle = (Cycle*) &alloc; struct Cycle { /* ... */ } unsafe trait New { type Output; fn new(self, this: &mut MaybeUninit, ); } let mut alloc = MaybeUninit::uninit(); ???.new(&mut alloc); let cycle = unsafe { &*alloc.as_ptr().cast::() }; C++ Rust

Slide 42

Slide 42 text

class Cycle { public: Cycle(int x) { this->x_ = x; this->ptr_to_x_ = &this->x_; } /* ... */ }; char alloc[sizeof(Cycle)]; new (&alloc) Cycle(42); auto cycle = (Cycle*) &alloc; struct Cycle { /* ... */ } unsafe trait New { type Output; fn new(self, this: Pin<&mut MaybeUninit>, ); } let mut alloc = MaybeUninit::uninit(); ???.new(&mut alloc); let cycle = unsafe { &*alloc.as_ptr().cast::() }; C++ Rust

Slide 43

Slide 43 text

class Cycle { public: Cycle(int x) { this->x_ = x; this->ptr_to_x_ = &this->x_; } /* ... */ }; char alloc[sizeof(Cycle)]; new (&alloc) Cycle(42); auto cycle = (Cycle*) &alloc; struct Cycle { /* ... */ } struct NewCycle(i32); unsafe impl New for NewCycle { type Output = Cycle; fn new(self, this: /* ... */) { this.x = self.0; this.ptr_to_x = &this.x; } } let mut alloc = MaybeUninit::uninit(); NewCycle(42).new(&mut alloc); let cycle = unsafe { &*alloc.as_ptr().cast::() }; C++ Rust

Slide 44

Slide 44 text

class Cycle { public: Cycle(int x) { this->x_ = x; this->ptr_to_x_ = &this->x_; } /* ... */ }; char alloc[sizeof(Cycle)]; new (&alloc) Cycle(42); auto cycle = (Cycle*) &alloc; struct Cycle { /* ... */ } struct NewCycle(i32); unsafe impl New for NewCycle { type Output = Cycle; fn new(self, this: /* ... */) { this.x = self.0; this.ptr_to_x = &this.x; } } let mut alloc = MaybeUninit::uninit(); NewCycle(42).new(&mut alloc); let cycle = unsafe { &*alloc.as_ptr().cast::() }; C++ Rust

Slide 45

Slide 45 text

Types which implement New represent construction operations. New::new() is Rust’s “raw constructor” operator.

Slide 46

Slide 46 text

● Still can’t return a stack-pinned data; need to Box it. ● Still can’t directly pin data in collections like Vec and HashMap; need to Box them. “Address-sensitive” types feel like second-class citizens in Rust. Even with Rust constructors! And yet...

Slide 47

Slide 47 text

Return Slots

Slide 48

Slide 48 text

class Cycle { /* ... */ }; Cycle SquareCycle(int z) { return Cycle(z * z); } x: z * z p: 0xTBD @ 0xTBD

Slide 49

Slide 49 text

class Cycle { /* ... */ }; Cycle SquareCycle(int z) { return Cycle(z * z); } auto cycle = SquareCycle(5); *cycle.ptr_to_x /= 5; // Ok! x: 42 p: 0x7fc55012 cycle @ 0x7fc55012 x: z * z p: 0xTBD @ 0xTBD SC(5)

Slide 50

Slide 50 text

class Cycle { /* ... */ }; void SquareCycle(int z, Cycle* out) { new (out) Cycle(z * z); } auto cycle; SquareCycle(5, &cycle); *cycle.ptr_to_x /= 5; // Ok! x: 42 p: 0x7fc55012 cycle @ 0x7fc55012 x: z * z p: 0xTBD @ 0xTBD SC(5)

Slide 51

Slide 51 text

class Cycle { /* ... */ }; void SquareCycle(int z, Cycle* out) { new (out) Cycle(z * z); } // NOTE: Rust has a hidden // return slot, too! auto cycle; SquareCycle(5, &cycle); *cycle.ptr_to_x /= 5; // Ok! x: 42 p: 0x7fc55012 cycle @ 0x7fc55012 x: z * z p: 0xTBD @ 0xTBD SC(5)

Slide 52

Slide 52 text

What if we ignore Rust’s return slots and build our own?

Slide 53

Slide 53 text

struct Slot<’a, T> { data: &’a mut MaybeUninit, } 0x7fc55012 Slot @ 0x7fc5501a [u8] @ 0xsomewhere

Slide 54

Slide 54 text

struct Slot<’a, T> { data: &’a mut MaybeUninit, } impl<’a, T> Slot<’a, T> { fn emplace( self, n: impl New, ) -> Pin<&’a mut T>; } 0x7fc55012 Slot @ 0x7fc5501a [u8] @ 0x7fc55012

Slide 55

Slide 55 text

fn new_cycle<’a>( ret: Slot<’a, Cycle>, ) -> Pin<&’a mut Cycle>> { ret.emplace(NewCycle(42)) } struct Slot<’a, T> { data: &’a mut MaybeUninit, } impl<’a, T> Slot<’a, T> { fn emplace( self, n: impl New, ) -> Pin<&’a mut T>; } 0x7fc55012 Slot @ 0x7fc5501a [42, 00, ..] [u8] @ 0x7fc55012 0x7fc55012 &mut Cycle @ 0x7fc55024 emplace(..)

Slide 56

Slide 56 text

● Still can’t return a stack-pinned data; need to Box it. ● Still can’t directly pin data in collections like Vec and HashMap; need to Box them. “Address-sensitive” types feel like second-class citizens in Rust. Even with constructors and slots! Better...

Slide 57

Slide 57 text

class Cycle { public: Cycle(int x) { this->x_ = x; this->ptr_to_x_ = &this->x_; } private: int x_; int* ptr_to_x_; }; auto cycle = Cycle(42); x: 42 p: 0x7fc55012 cycle @ 0x7fc55012 x: 42 p: 0xTBD *this @ 0xTBD Ctor

Slide 58

Slide 58 text

class Cycle { public: Cycle(int x) { this->x_ = x; this->ptr_to_x_ = &this->x_; } private: int x_; int* ptr_to_x_; }; auto cycle = Cycle(42); auto copy = cycle; x: 42 p: 0x7fc55012 cycle @ 0x7fc55012 x: 42 p: 0xTBD *this @ 0xTBD Cycle(int) x: 42 p: 0x7fc55012 copy @ 0x7fc5501a Copy Copy Ctor

Slide 59

Slide 59 text

class Cycle { public: Cycle(int x) { this->x_ = x; this->ptr_to_x_ = &this->x_; } private: int x_; int* ptr_to_x_; }; auto cycle = Cycle(42); auto copy = cycle; *copy.ptr_to_x_ *= 2; // Modified `cycle`, not `copy`! x: 42 p: 0x7fc55012 cycle @ 0x7fc55012 x: 42 p: 0xTBD *this @ 0xTBD Cycle(int) x: 42 p: 0x7fc55012 copy @ 0x7fc5501a Copy Ctor INVARIANT VIOLATED!

Slide 60

Slide 60 text

class Cycle { public: Cycle(int x) { this->x_ = x; this->ptr_to_x_ = &this->x_; } Cycle(const Cycle& that) { this->x_ = that.x_; this->ptr_to_x_ = &this->x_; } /* ... */ }; auto cycle = Cycle(42); auto copy = cycle; *copy.ptr_to_x_ *= 2; x: 42 p: 0x7fc55012 cycle @ 0x7fc55012 x: 42 p: 0xTBD *this @ 0xTBD Cycle(int) x: 42 p: 0x7fc5501a copy @ 0x7fc5501a Copy Ctor INVARIANT VIOLATED!

Slide 61

Slide 61 text

C++ variables do not move; you always construct a new one! Objects are always notified of copy operations.

Slide 62

Slide 62 text

What would a “copy constructor” look like in Rust?

Slide 63

Slide 63 text

class Cycle { public: Cycle(const Cycle& that) { this->x_ = that.x; this->ptr_to_x_ = &this->x_; } /* ... */ }; auto cycle = Cycle(42); auto copy = cycle; struct Cycle { /* ... */ } let mut alloc1 = MaybeUninit::uninit(); NewCycle(42).new(&mut alloc1); let cycle = &*alloc1.cast(); let mut alloc2 = MaybeUninit::uninit(); /* copy? */.new(&mut alloc2); let copy = &*alloc2.cast(); C++ Rust

Slide 64

Slide 64 text

class Cycle { public: Cycle(const Cycle& that) { this->x_ = that.x; this->ptr_to_x_ = &this->x_; } /* ... */ }; auto cycle = Cycle(42); auto copy = cycle; struct Cycle { /* ... */ } let mut alloc1 = MaybeUninit::uninit(); NewCycle(42).new(&mut alloc1); let cycle = &*alloc1.cast(); let mut alloc2 = MaybeUninit::uninit(); CloneNew::clone(&cycle, &mut alloc2); let copy = &*alloc2.cast(); C++ Rust

Slide 65

Slide 65 text

class Cycle { public: Cycle(const Cycle& that) { this->x_ = that.x; this->ptr_to_x_ = &this->x_; } /* ... */ }; auto cycle = Cycle(42); auto copy = cycle; struct Cycle { /* ... */ } unsafe trait CloneNew { fn clone(that: &Self, this: Pin<&mut MaybeUninit>, ); } let mut alloc1 = MaybeUninit::uninit(); NewCycle(42).new(&mut alloc1); let cycle = &*alloc1.cast(); let mut alloc2 = MaybeUninit::uninit(); CloneNew::clone(&cycle, &mut alloc2); let copy = &*alloc2.cast(); C++ Rust

Slide 66

Slide 66 text

class Cycle { public: Cycle(const Cycle& that) { this->x_ = that.x; this->ptr_to_x_ = &this->x_; } /* ... */ }; auto cycle = Cycle(42); auto copy = cycle; struct Cycle { /* ... */ } unsafe impl CloneNew for Cycle { fn clone(that, this) { this.x = that.x; this.ptr_to_x = &this.x; } } let mut alloc1 = MaybeUninit::uninit(); NewCycle(42).new(&mut alloc1); let cycle = &*alloc1.cast(); let mut alloc2 = MaybeUninit::uninit(); CloneNew::clone(&cycle, &mut alloc2); let copy = &*alloc2.cast(); C++ Rust

Slide 67

Slide 67 text

CloneNew is like a fusion of Clone and New. But in Rust, we prefer to move, not copy!

Slide 68

Slide 68 text

fn new_cycle<’a>( ret: Slot<’a, Cycle>, ) -> Pin<&’a mut Cycle>> { ret.emplace(NewCycle(42)) } struct Slot<’a, T> { data: &’a mut MaybeUninit, } impl<’a, T> Slot<’a, T> { fn emplace( self, n: impl New, ) -> Pin<&’a mut T>; } 0x7fc55012 Slot @ 0x7fc5501a [42, 00, ..] [u8] @ 0x7fc55012 0x7fc55012 &mut Cycle @ 0x7fc55024 emplace(..)

Slide 69

Slide 69 text

fn new_cycle<’a>( ret: Slot<’a, Cycle>, ) -> Pin<&’a mut Cycle>> { ret.emplace(NewCycle(42)) } struct MoveRef<’a, T>(&’a mut T); struct Slot<’a, T> { data: &’a mut MaybeUninit, } impl<’a, T> Slot<’a, T> { fn emplace( self, n: impl New, ) -> Pin<&’a mut T>; } 0x7fc55012 Slot @ 0x7fc5501a [42, 00, ..] [u8] @ 0x7fc55012 0x7fc55012 &mut Cycle @ 0x7fc55024 emplace(..)

Slide 70

Slide 70 text

fn new_cycle<’a>( ret: Slot<’a, Cycle>, ) -> Pin<&’a mut Cycle>> { ret.emplace(NewCycle(42)) } struct MoveRef<’a, T>(&’a mut T); impl Drop for MoveRef<’a, T> { fn drop(&mut self) { ptr::drop_in_place(self.0); } } struct Slot<’a, T> { data: &’a mut MaybeUninit, } impl<’a, T> Slot<’a, T> { fn emplace( self, n: impl New, ) -> Pin<&’a mut T>; } 0x7fc55012 Slot @ 0x7fc5501a [42, 00, ..] [u8] @ 0x7fc55012 0x7fc55012 MoveRef @ 0x7fc55024 emplace(..)

Slide 71

Slide 71 text

fn new_cycle<’a>( ret: Slot<’a, Cycle>, ) -> Pin> { ret.emplace(NewCycle(42)) } struct MoveRef<’a, T>(&’a mut T); struct Slot<’a, T> { data: &’a mut MaybeUninit, } impl<’a, T> Slot<’a, T> { fn emplace( self, n: impl New, ) -> Pin>; } 0x7fc55012 Slot @ 0x7fc5501a [42, 00, ..] [u8] @ 0x7fc55012 0x7fc55012 MoveRef @ 0x7fc55024 emplace(..)

Slide 72

Slide 72 text

MoveRef is like Box: it owns its referent. MoveRef does not own its storage, like &mut T.

Slide 73

Slide 73 text

unsafe trait CloneNew { fn clone( that: &Self, this: Pin<&mut ...>, ); } unsafe impl CloneNew for Cycle { fn clone(that, this) { this.x = that.x; this.ptr_to_x = &this.x; } } Copies

Slide 74

Slide 74 text

unsafe trait MoveNew { fn mov( that: Pin>, this: Pin<&mut ...>, ); } unsafe trait CloneNew { fn clone( that: &Self, this: Pin<&mut ...>, ); } unsafe impl CloneNew for Cycle { fn clone(that, this) { this.x = that.x; this.ptr_to_x = &this.x; } } Copies Moves

Slide 75

Slide 75 text

unsafe trait MoveNew { fn mov( that: Pin>, this: Pin<&mut ...>, ); } unsafe impl MoveNew for Cycle { fn mov(that, this) { this.x = that.x; this.ptr_to_x = &this.x; } } unsafe trait CloneNew { fn clone( that: &Self, this: Pin<&mut ...>, ); } unsafe impl CloneNew for Cycle { fn clone(that, this) { this.x = that.x; this.ptr_to_x = &this.x; } } Copies Moves

Slide 76

Slide 76 text

unsafe trait MoveNew { fn mov( that: Pin>, this: Pin<&mut ...>, ); } unsafe impl MoveNew for U where U: Unpin { fn mov(that, this) { *this = *that; } } unsafe trait CloneNew { fn clone( that: &Self, this: Pin<&mut ...>, ); } unsafe impl CloneNew for U where U: Unpin { fn clone(that, this) { *this = that.clone(); } } Copies Moves

Slide 77

Slide 77 text

T: MoveNew means we can move from one pinned MoveRef into another! It’s generic, so we can build collections that leverage the trait.

Slide 78

Slide 78 text

● Still can’t return a stack-pinned data; need to Box it. ● Still can’t directly pin data in collections like Vec and HashMap; need to Box them. New + Slot + MoveRef let us move “address-sensitive” types whenever we need to! Yes, it’s possible!

Slide 79

Slide 79 text

Ok, but the ergonomics are terrible!

Slide 80

Slide 80 text

let mut alloc = MaybeUninit::uninit(); NewCycle(42).new(&mut alloc); let cycle = unsafe { &*alloc.as_ptr().cast::() };

Slide 81

Slide 81 text

let mut alloc = MaybeUninit::uninit(); NewCycle(42).new(&mut alloc); let cycle = unsafe { &*alloc.as_ptr().cast::() }; slot!(alloc: Cycle);

Slide 82

Slide 82 text

let mut alloc = MaybeUninit::uninit(); NewCycle(42).new(&mut alloc); let cycle = unsafe { &*alloc.as_ptr().cast::() }; slot!(alloc: Cycle); let cycle = alloc.emplace(NewCycle(42));

Slide 83

Slide 83 text

let mut alloc = MaybeUninit::uninit(); NewCycle(42).new(&mut alloc); let cycle = unsafe { &*alloc.as_ptr().cast::() }; slot!(alloc: Cycle); let cycle = alloc.emplace(NewCycle(42)); slot!(alloc: Cycle); let copy = alloc.clone_from(&cycle); slot!(alloc: Cycle); let moved = alloc.move_from(cycle);

Slide 84

Slide 84 text

let mut alloc = MaybeUninit::uninit(); NewCycle(42).new(&mut alloc); let cycle = unsafe { &*alloc.as_ptr().cast::() }; emplace! { let cycle = NewCycle(42); let copy = Clone(&cycle); let moved = Move(cycle); } Possible macro syntax.

Slide 85

Slide 85 text

Although constructors are a pure-library feature, we can use macros to make them feel like part of the language.

Slide 86

Slide 86 text

What’s Next? ● Implementing this in a crate… already done! Check out `moveit` on crates.io! ● Building Ctor and MoveRef friendly analogues of Vec, HashMap, etc (WIP). ● Flesh out concrete pure-Rust and mixed-C++ examples for `moveit`. ● Revamp the Pin

docs to better express what a Rust “move” is. ● QoL features from C++.

Slide 87

Slide 87 text

Acks