Virtual Classes
A pointer to a virtual class cannot be a Ptr<T>. Ptr<T> requires T: Sized,
the implicit bound on every generic parameter, and a virtual class is translated
as a Rust trait, whose trait object dyn T is unsized and cannot satisfy that
bound. The runtime provides a dedicated PtrDyn<dyn T> type, declared with
T: ?Sized, for these pointers, kept separate so the generic Ptr pays no cost
for dynamic dispatch.
A PtrDyn is created at the point where C++ converts a derived pointer to a
base pointer. Ptr::to_dyn takes the weak reference out of the Ptr<Derived>
and applies Rust’s unsized coercion to it, turning a Weak<RefCell<Derived>>
into a Weak<RefCell<dyn Base>>, without ever upgrading it. The coercion itself
is written by the code generator as the closure |w| w, whose return type
selects the target trait object.
Note
This coercion is why the pointee cell is a plain
RefCellbehindWeakrather than a struct of the runtime’s own:Weakalready implements it, and a new type could only opt in through the nightly-onlyCoerceUnsizedtrait.
The conversion looks like this:
struct Base { virtual int f() const = 0; };
struct Derived : Base { int f() const override { return 1; } };
Derived d;
Base *b = &d;
int r = b->f();
#![allow(unused)]
fn main() {
let d: Value<Derived> = Rc::new(RefCell::new(<Derived>::default()));
let b: Value<PtrDyn<dyn Base>> = Rc::new(RefCell::new(
(d.as_pointer()).to_dyn::<dyn Base>(|w| w),
));
let r: Value<i32> = Rc::new(RefCell::new(
({ (*(*b.borrow()).upgrade().deref()).f() }),
));
}
A virtual call goes through upgrade, which returns a StrongPtrDyn<dyn T>
holding the strong reference; its deref and deref_mut borrow the object and
the call dispatches through the trait’s vtable.
Warning
StrongPtrDynis set to be removed for the same reasons asStrongPtr: it holds a strong reference that can outlive the object’s C++ lifetime, and even as a temporary it spans the whole virtual call, so a method that deletes its own object panics ondelete.
PtrDyn is far smaller than Ptr: it is either null or a weak reference to a
single object, on the stack or on the heap. Ptr::to_dyn keeps that
distinction, so a base pointer made from a newed object can be deleted and
one made from a local cannot. It has no arithmetic, no comparison, no array
kinds, and no byte view. Because to_dyn is only defined for single-value
pointers, a base pointer into an array of polymorphic objects (a
Derived arr[N] walked through a Base *) cannot be formed.