autocxx icon indicating copy to clipboard operation
autocxx copied to clipboard

Implement `AsRef` for casting to superclasses

Open adetaylor opened this issue 4 years ago • 10 comments

If you have a reference b_ref: &B, and B is a subclass of A, it would sometimes be useful to have an &A in order to pass into APIs which expect a &A. Experiments suggest that this is ergonomic using AsRef, at least for constant references (not so much for pinned mutable references).

adetaylor avatar Sep 04 '21 15:09 adetaylor

Isn't this casting from subclasses to superclasses? (title)

sollyucko avatar Sep 04 '21 18:09 sollyucko

#663 does this for const references, but mutable references continue to be problemmy.

adetaylor avatar Jan 07 '22 04:01 adetaylor

hey, for my own knowledge, as a rust beginner, why are mutable references a problem? by that I mean, if base methods are generated in the subclass with pin mut ?

steeve avatar Oct 11 '22 19:10 steeve

I can't remember for sure; the only comment I can find is here which says it's due to the Pinning. It might be relatively easy to flip that boolean and fix it up to see what works and what goes wrong...

adetaylor avatar Oct 11 '22 19:10 adetaylor

I did a bit more investigation into what it would take to add mutable casts using autocxx::PinMut. This is largely a note to my future self.

The first obstacle is that the signature of pin_mut is currently fn pin_mut(&mut self) -> std::pin::Pin<&mut T>. We might need to change that. Any option isn't great:

  • Sticking with fn pin_mut(&mut self) -> std::pin::Pin<&mut T>: that &mut self prevents us from implementing this for Pin<&mut T> without significant changes to the code generator to be aware of this special case. (Code generation for these is done by pretending bindgen gave us a function fn cast(this: *mut T) which our translation layer automatically turns into a cxx-compatible Pin<&mut T>). While we could possibly add this special casing, I'm a little nervous that self: &mut Pin<&mut T> will have complications, e.g. we'll have to juggle two lifetimes, and it just doesn't feel right.
  • fn pin_mut(self) -> std::pin::Pin<&mut T>: not object safe. That's probably OK. We'd be impling the trait for for Pin<&mut T> rather than T. That might be OK too. For the current usage of PinMut within the fake chromium render frame example, it would be a bit annoying. But most of all, I don't know that it's possible to use syntax like + 'a anywhere to indicate that the self must contain a reference to which the return type's lifetime can be bound. So I think this option is out.
  • fn pin_mut(self: std::pin::Pin<&mut T>) -> std::pin::Pin<&mut T> seems perhaps least bad for usage with these casts, but completely breaks the ability to use this trait for the sort of object lifetime handle management code we've got in fake-chromium-render-frame-host.

More thought required.

adetaylor avatar Oct 21 '22 12:10 adetaylor

A note for others who run across this - as an alternative to the Rust workaround in this repo's examples https://github.com/google/autocxx/blob/762dbf397ebc5ab0697b8656b06af69851187da6/examples/chromium-fake-render-frame-host/src/render_frame_host.rs#L226

It seems that C++ upcast function(s) can also be a workable approach as shown in https://github.com/dtolnay/cxx/issues/797#issuecomment-809951817.

That approach works trivially in autocxx for my use case where I'm trying to upcast a std::unique_ptr:

// in .h
inline std::unique_ptr<A> upcast_b(std::unique_ptr<B> b) { return b; }
// in .rs
include_cpp! {
    safety!(unsafe_ffi)
    generate!("upcast_b")
}
let a: UniquePtr<A> = ffi::upcast_b(b);

I don't have any opinion on whether performing the upcast in Rust or C++ is better.

rzhw avatar Sep 05 '23 06:09 rzhw

Apparently PyO3 uses Deref for this purpose rather than AsRef, so casts are automatic/transparent.

adetaylor avatar Oct 17 '23 12:10 adetaylor

A note for others who run across this - as an alternative to the Rust workaround in this repo's examples https://github.com/google/autocxx/blob/762dbf397ebc5ab0697b8656b06af69851187da6/examples/chromium-fake-render-frame-host/src/render_frame_host.rs#L226

Based on this, I found that

unsafe fn upcast<D, S>(derived: Pin<&mut D>) -> Pin<&mut S> {
    let subclass_obs_ptr = Pin::into_inner_unchecked(derived) as *mut D;
    std::pin::Pin::new_unchecked(&mut *subclass_obs_ptr.cast::<S>())
}

works very well for most cases. You can use this both with UniquePtr's via UniquePtr::pin_mut() and autocxx::prelude::New aswell as casting between generated ans self implemented ffi's.

baboon25 avatar Feb 09 '24 17:02 baboon25