KDAB / cxx-qt

Safe interop between Rust and Qt
https://kdab.github.io/cxx-qt/book/
1.04k stars 72 forks source link

Traits for CxxQtType/QtObject #562

Open LeonMatthesKDAB opened 1 year ago

LeonMatthesKDAB commented 1 year ago

We could move some of our "magic" methods into traits, which would make it a lot more obvious where they are coming from, and would allow us to e.g. "inherit" methods from QObject, reason about types, etc.

// cxx-qt crate - may also need to go in cxx-qt-lib
mod ffi {
    extern "C++" {
        type QObject;

        fn children(self: &QObject) -> QList<*mut QObject>;
        fn objectName(self: &QObject) -> QString;
    }
}

// Note: QtObject vs QObject
trait QtObject {
    fn as_qobject(&self) -> &cxx_qt_lib::QObject;
    fn as_qobject_mut(&mut self) -> Pin<&mut cxx_qt_lib::QObject>;

    fn children(&self) -> QList<*mut QObject> {
        as_qobject().children()
    }

    fn objectName(&self) -> Pin<&mut cxx_qt_lib::QObject> {
        as_qobject().objectName()
    }

    // ... other methods wrapped as needed.
}

trait CxxQtType {
    type Inner;

    fn rust(&self) -> &Self::Inner;
    fn rust_mut(self: Pin<&mut self>) -> &mut Self::Inner;
}

Then in C++, we could generate a generic cast to QObject, similar to how we do constructors.

template<typename T>
QObject *as_qobject(T* t) {
    return static_cast<QObject*>(t);
}
ahayzen-kdab commented 1 year ago

I also wonder with this if we have an existing C++ QObject if we should allow for tagging it to get the trait implemented on it ?

Eg if we have

unsafe extern "C++Qt" {
   type MyQObject;
}

This won't have the QtObject trait implemented on it, but instead we could use the same syntax as RustQt to to do this

Eg

unsafe extern "C++Qt" {
   #[qobject] 
   type MyQObject;
}
LeonMatthesKDAB commented 1 year ago

Hm, shouldn't all classes in "C++Qt" be QObjects? In that case we should just always implement the trait.

Though that brings up the question of whether all types within C++Qt are actually QObjects :thinking: Could they also be QGadgets? And would there be any use for it?

But maybe we should start requiring #[qobject] on every type in "C++Qt" to make sure the behavior is the same in C++Qt as in RustQt.

LeonMatthesKDAB commented 1 year ago

Also another suggestion regarding naming, we could use AsX here. E.g. the trait could be named AsQObject.

ahayzen-kdab commented 1 year ago

Q_GADGET IIRC doesn't support Q_SIGNAL but does support things like Q_PROPERTY. As the only feature we effectively add with extern "C++Qt" is signals then it could make sense to assume all are deriving from QObject.

But I fear it's a slight inconsistency with extern "RustQt".

Maybe the #[qobject] attribute ends up meaning

LeonMatthesKDAB commented 7 months ago

Note: We should potentially make use of Deref as well to allow for upcasting to the base class.

Once https://doc.rust-lang.org/std/pin/struct.Pin.html#method.as_deref_mut is no longer nightly only, we could even use DerefMut and upcast pointers easily that way.

Something along the lines of:

unsafe trait Inherits {
    // note using an associated type only allows for single-inheritance!
    type Base;

    fn upcast_ptr(self: *mut Self) -> *mut Base;

    fn upcast_mut(self: Pin<&mut Self>) -> Pin<&mut Base> {
       // provided by the trait
    }
}

// unsure whether this is possible, but would be awesome!
impl<T> Deref for T where T: Inherits {
    type Target = <T as Inherits>::Base;
    ...
}
mystchonky commented 5 months ago

It would be nice if we could use From and Into traits. It will be consistent with how rust generally works.

LeonMatthesKDAB commented 5 months ago

Right, I was hoping we could even get away without explicit Into calls by using Deref, but we'll have to see how this pans out.

mystchonky commented 5 months ago

Rust is verbose and is one of its strong points. Automatic casting has caused problems for me in the past, which is why I would be against it. In my opinion, Rust's type inference powered object.into() syntax gives the best of automatic casting and is clear when the cast actually happens.