Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 RefCell behind Weak rather than a struct of the runtime’s own: Weak already implements it, and a new type could only opt in through the nightly-only CoerceUnsized trait.

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

StrongPtrDyn is set to be removed for the same reasons as StrongPtr: 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 on delete.

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.