Skip to content

Commit 97fd8b9

Browse files
Document cxx_qt::Constructor trait.
The Constructor is now stable enough to become public API.
1 parent bbe61b9 commit 97fd8b9

File tree

9 files changed

+187
-63
lines changed

9 files changed

+187
-63
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222
- `unsafe impl !cxx_qt::Locking for qobject::T` to disable internal locking
2323
- `Deref` is now implemented for `qobject::T` to reach the `T` Rust struct
2424
- Support for C++ only methods by not having a `#[qinvokable]` attribute
25+
- Ability to define a custom C++ Constructor using `cxx_qt::Constructor`
26+
- `cxx_qt::Initialize` trait for easier default-constructor implementation
2527

2628
### Changed
2729

book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ SPDX-License-Identifier: MIT OR Apache-2.0
3131
- [Threading](./concepts/threading.md)
3232
- [Nested Objects](./concepts/nested_objects.md)
3333
- [Inheritance & Overriding](./concepts/inheritance.md)
34+
- [Custom Constructors](./concepts/constructor.md)

book/src/concepts/constructor.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
3+
SPDX-FileContributor: Leon Matthes <[email protected]>
4+
5+
SPDX-License-Identifier: MIT OR Apache-2.0
6+
-->
7+
8+
# Defining a custom C++/QML constructor
9+
10+
By default, CXX-Qt will generate a constructor that takes a single `QObject` pointer as optional argument (usually the QObject parent).
11+
12+
It then:
13+
1. Calls the base class constructor with the optional parent pointer.
14+
2. uses [Rust's default trait][default-trait] to construct the inner Rust struct.
15+
16+
For most cases this is good enough, however there are a few cases in which this will not be sufficient, for example:
17+
* the base class constructor expects arguments other than the parent QObject pointer, or a different type than QObject is used for the parent (for example QQuickItem's constructor takes a QQuickItem pointer).
18+
* the QObject needs to run code after it is initialized (e.g. open a connection to a server, etc.).
19+
* the Rust struct can't have a `default` implementation as it needs certain arguments for construction.
20+
21+
To facilitate these use-cases CXX-Qt provides the [`Constructor` trait][constructor-trait].
22+
23+
## Implementing & Declaring a Constructor
24+
In order for CXX-Qt to generate a custom constructor, the `qobject::T` must implement the [`cxx_qt::Constructor` trait][constructor-trait].
25+
Additionally, the constructor must be declared within the [cxx_qt::bridge](./bridge.md).
26+
27+
This declaration uses Rust's `impl ... for` syntax, but with a few special rules:
28+
* The implementation block must be empty
29+
* If any of the associated types in the constructor are not `()`, they
30+
must be listed using `Type=(...)` in the constructor generics.
31+
32+
Example:
33+
```rust,ignore,noplayground
34+
#[cxx_qt::bridge]
35+
mod qobject {
36+
{{#include ../../../examples/qml_features/rust/src/signals.rs:book_signals_struct}}
37+
38+
{{#include ../../../examples/qml_features/rust/src/signals.rs:book_constructor_decl}}
39+
}
40+
// Don't forget to actually implement the trait **outside** the bridge!
41+
```
42+
[Full example](https://github.com/KDAB/cxx-qt/blob/main/examples/qml_features/rust/src/signals.rs)
43+
44+
## Routing arguments of the Constructor
45+
A C++ constructor is more complex then a Rust struct initialization.
46+
47+
Like a normal function, it takes a list of arguments.
48+
However, before the actual code inside the constructor is executed, C++ ensures the following:
49+
* The base class is constructed
50+
* All members of the struct are initialized (for CXX-Qt this includes the underlying Rust struct).
51+
52+
Both of these steps may need any of the arguments given to the constructor.
53+
The code that is actually run *inside* the constructor may also need to access some of the arguments.
54+
55+
`cxx_qt::Constructor` allows controlling how the arguments are passed around in the constructor using the `route_arguments` function.
56+
For this it uses three associated types as well as one generic type:
57+
* Constructor generic `Arguments` - argument list that the constructor takes.
58+
* `type BaseArguments` - argument list that is passed to the base class constructor.
59+
* `type NewArguments` - argument list used to construct the Rust struct using the `new` function.
60+
* `type InitializeArguments` - argument list that is passed to the code run within the constructor (the `initialize` function).
61+
62+
These types must all be tuple types and can therefore support an arbitrary list of arguments.
63+
64+
The `route_arguments` function takes the Arguments provided to the constructor and distributes them to the three different types in the above list.
65+
Doing this in Rust allows calling any base class constructor and controlling movement, copying or other operations on the arguments.
66+
Every constructor defined using `cxx_qt::Constructor` will execute this function once, before anything else in the Constructor happens and then distributes the resultant values to the appropriate steps of the QObject construction.
67+
68+
As actual construction takes place in C++, all types used must be compatible with CXX as they must be able to pass the FFI barrier.
69+
70+
## Constructing the Rust struct with arguments
71+
If the construction of the inner Rust struct requires some arguments, the `new` function of the Constructor can be given arguments by using the `NewArguments` associated type.
72+
73+
Example:
74+
```rust,ignore
75+
#[cxx_qt::bridge]
76+
mod qobject {
77+
// ...
78+
79+
{{#include ../../../examples/qml_features/rust/src/properties.rs:book_constructor_new_decl}}
80+
}
81+
82+
{{#include ../../../examples/qml_features/rust/src/properties.rs:book_constructor_new_impl}}
83+
```
84+
85+
## Initializing the QObject
86+
In addition to running code before constructing the inner Rust struct, it may be useful to run code from the context of the QObject itself (i.e. inside the Constructor implementation).
87+
88+
The `initialize` function can be used to run code inside a constructor.
89+
It is given a pinned mutable self reference to the QObject and the list of `InitializeArguments`.
90+
91+
### Using the `Initialize` trait
92+
As QML uses the default constructor, for most QML types only the `initialize` part of the constructor is of interest, as everything else is defaulted.
93+
To reduce the boilerplate of this use-case, CXX-Qt provides the [`Initialize`][initialize-trait] trait.
94+
95+
If a QObject implements the `Initialize` trait, and the inner Rust struct is [Default][default-trait]-constructible it will automatically implement `cxx_qt::Constructor<()>`.
96+
97+
Example:
98+
```rust,ignore
99+
{{#include ../../../examples/qml_features/rust/src/signals.rs:book_initialize_impl}}
100+
// ...
101+
}
102+
}
103+
```
104+
[Full example](https://github.com/KDAB/cxx-qt/blob/main/examples/qml_features/rust/src/signals.rs)
105+
106+
Then just remember to declare the `cxx_qt::Constructor<()>` inside the `cxx_qt::bridge`.
107+
```rust,ignore
108+
#[cxx_qt::bridge]
109+
mod qobject {
110+
{{#include ../../../examples/qml_features/rust/src/signals.rs:book_signals_struct}}
111+
112+
{{#include ../../../examples/qml_features/rust/src/signals.rs:book_initialize_decl}}
113+
}
114+
```
115+
116+
[constructor-trait]: https://docs.rs/cxx-qt/latest/cxx_qt/trait.Constructor.html
117+
[initialize-trait]: https://docs.rs/cxx-qt/latest/cxx_qt/trait.Initialize.html
118+
[default-trait]: https://doc.rust-lang.org/std/default/trait.Default.html

book/src/concepts/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ SPDX-License-Identifier: MIT OR Apache-2.0
2323
* [Threading concept and safety](./threading.md)
2424
* [Nesting Rust objects](./nested_objects.md)
2525
* [Inheriting QObjects and overriding methods](./inheritance.md)
26+
* [Defining custom C++/QML Constructors](./constructor.md)

book/src/concepts/inheritance.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ Some Qt APIs require you to override certain methods from an abstract base class
1111

1212
To support creating such subclasses directly from within Rust, CXX-Qt provides you with multiple helpers.
1313

14+
Some superclasses may require special parameters for construction.
15+
This can be achieved by using a [custom constructor](./constructor.md).
16+
1417
## Accessing base class methods
1518
To access the methods of a base class in Rust, use the `#[inherit]` macro.
1619
It can be placed in front of a function in a `extern "RustQt"` block in a `#[cxx_qt::bridge]`.

examples/qml_features/rust/src/custom_parent_class.rs

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -103,26 +103,8 @@ impl qobject::CustomParentClass {
103103
}
104104
}
105105

106-
impl cxx_qt::Constructor<()> for qobject::CustomParentClass {
107-
type NewArguments = ();
108-
type BaseArguments = (*mut qobject::QQuickItem,);
109-
type InitializeArguments = ();
110-
111-
fn route_arguments(
112-
_args: (),
113-
) -> (
114-
Self::NewArguments,
115-
Self::BaseArguments,
116-
Self::InitializeArguments,
117-
) {
118-
((), (core::ptr::null_mut(),), ())
119-
}
120-
121-
fn new((): ()) -> CustomParentClassRust {
122-
CustomParentClassRust::default()
123-
}
124-
125-
fn initialize(self: core::pin::Pin<&mut Self>, _arguments: Self::InitializeArguments) {
106+
impl cxx_qt::Initialize for qobject::CustomParentClass {
107+
fn initialize(self: core::pin::Pin<&mut Self>) {
126108
self.on_color_changed(|qobject| qobject.update()).release();
127109
}
128110
}

examples/qml_features/rust/src/nested_qobjects.rs

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -98,27 +98,9 @@ impl qobject::OuterObject {
9898
}
9999
}
100100

101-
impl cxx_qt::Constructor<()> for qobject::OuterObject {
102-
type InitializeArguments = ();
103-
type NewArguments = ();
104-
type BaseArguments = ();
105-
106-
fn route_arguments(
107-
_: (),
108-
) -> (
109-
Self::NewArguments,
110-
Self::BaseArguments,
111-
Self::InitializeArguments,
112-
) {
113-
((), (), ())
114-
}
115-
116-
fn new(_: Self::NewArguments) -> <Self as cxx_qt::CxxQtType>::Rust {
117-
Default::default()
118-
}
119-
120-
/// Initialise the QObject, creating a connection from one signal to another
121-
fn initialize(self: core::pin::Pin<&mut Self>, _: Self::InitializeArguments) {
101+
impl cxx_qt::Initialize for qobject::OuterObject {
102+
/// Initialize the QObject, creating a connection from one signal to another
103+
fn initialize(self: core::pin::Pin<&mut Self>) {
122104
// Example of connecting a signal from one QObject to another QObject
123105
// this causes OuterObject::Called to trigger InnerObject::Called
124106
self.on_called(|qobject, obj| {

examples/qml_features/rust/src/properties.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ pub mod qobject {
3636
#[qinvokable]
3737
fn disconnect(self: Pin<&mut RustProperties>);
3838
}
39+
40+
impl cxx_qt::Constructor<()> for RustProperties {}
41+
42+
// ANCHOR: book_constructor_new_decl
43+
impl<'a>
44+
cxx_qt::Constructor<
45+
(bool, &'a QUrl, &'a QUrl, &'a QString),
46+
NewArguments = (bool, &'a QUrl, &'a QUrl, &'a QString),
47+
> for RustProperties
48+
{
49+
}
50+
// ANCHOR_END: book_constructor_new_decl
3951
}
4052

4153
use core::pin::Pin;
@@ -102,3 +114,37 @@ impl qobject::RustProperties {
102114
self.set_connected_url(QUrl::default());
103115
}
104116
}
117+
118+
impl cxx_qt::Initialize for qobject::RustProperties {
119+
fn initialize(self: Pin<&mut Self>) {}
120+
}
121+
122+
// ANCHOR: book_constructor_new_impl
123+
impl<'a> cxx_qt::Constructor<(bool, &'a QUrl, &'a QUrl, &'a QString)> for qobject::RustProperties {
124+
type NewArguments = (bool, &'a QUrl, &'a QUrl, &'a QString);
125+
type InitializeArguments = ();
126+
type BaseArguments = ();
127+
128+
fn route_arguments(
129+
arguments: (bool, &'a QUrl, &'a QUrl, &'a QString),
130+
) -> (
131+
Self::NewArguments,
132+
Self::BaseArguments,
133+
Self::InitializeArguments,
134+
) {
135+
// pass all arguments to the `new` function.
136+
(arguments, (), ())
137+
}
138+
139+
fn new(
140+
(connected, connected_url, previous_connected_url, status_message): Self::NewArguments,
141+
) -> Self::Rust {
142+
Self::Rust {
143+
connected,
144+
connected_url: connected_url.clone(),
145+
previous_connected_url: previous_connected_url.clone(),
146+
status_message: status_message.clone(),
147+
}
148+
}
149+
}
150+
// ANCHOR_END: book_constructor_new_impl

examples/qml_features/rust/src/signals.rs

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@ pub mod qobject {
3434
}
3535
// ANCHOR_END: book_signals_block
3636

37+
// ANCHOR: book_signals_struct
3738
unsafe extern "RustQt" {
38-
// ANCHOR: book_signals_struct
3939
#[qobject]
4040
#[qml_element]
4141
#[qproperty(bool, logging_enabled)]
4242
type RustSignals = super::RustSignalsRust;
4343
}
44+
// ANCHOR_END: book_signals_struct
4445

4546
// ANCHOR: book_rust_obj_impl
4647
unsafe extern "RustQt" {
@@ -54,9 +55,13 @@ pub mod qobject {
5455
}
5556
// ANCHOR_END: book_rust_obj_impl
5657

58+
// ANCHOR: book_initialize_decl
5759
impl cxx_qt::Constructor<()> for RustSignals {}
60+
// ANCHOR_END: book_initialize_decl
5861

62+
// ANCHOR: book_constructor_decl
5963
impl<'a> cxx_qt::Constructor<(&'a QUrl,), InitializeArguments = (&'a QUrl,)> for RustSignals {}
64+
// ANCHOR_END: book_constructor_decl
6065
}
6166

6267
use core::pin::Pin;
@@ -91,27 +96,11 @@ impl qobject::RustSignals {
9196
}
9297
}
9398

94-
impl cxx_qt::Constructor<()> for qobject::RustSignals {
95-
type BaseArguments = ();
96-
type NewArguments = ();
97-
type InitializeArguments = ();
98-
99-
fn route_arguments(
100-
_: (),
101-
) -> (
102-
Self::NewArguments,
103-
Self::BaseArguments,
104-
Self::InitializeArguments,
105-
) {
106-
((), (), ())
107-
}
108-
109-
fn new(_: Self::NewArguments) -> <Self as CxxQtType>::Rust {
110-
Default::default()
111-
}
112-
113-
/// Initialise the QObject, creating a connection reacting to the logging enabled property
114-
fn initialize(self: core::pin::Pin<&mut Self>, _: Self::InitializeArguments) {
99+
// ANCHOR: book_initialize_impl
100+
impl cxx_qt::Initialize for qobject::RustSignals {
101+
/// Initialize the QObject, creating a connection reacting to the logging enabled property
102+
fn initialize(self: core::pin::Pin<&mut Self>) {
103+
// ANCHOR_END: book_initialize_impl
115104
self.on_logging_enabled_changed(|mut qobject| {
116105
// Determine if logging is enabled
117106
if *qobject.as_ref().logging_enabled() {

0 commit comments

Comments
 (0)