This article is machine translated
The data types supported by QRust can be divided into two categories: primitive types and collection types. These data types are passed between Qt and Rust and can be used as function arguments, return values, and fields of struct.
Primitive Types
Rust | Qt |
bool | bool |
i8 | qint8 |
i16 | qint16 |
i32 | qint32 |
i64 | qint64 |
u8 | quint8 |
u16 | quint16 |
u32 | quint32 |
u64 | quint64 |
f32 | float |
f64 | double |
String | QString |
T struct | T struct |
Collection Types
Rust | Qt |
Vec<bool> | QList<bool> |
Vec<i8> | QList<qint8> |
Vec<i16> | QList<qint16> |
Vec<i32> | QList<qint32> |
Vec<i64> | QList<qint64> |
Vec<u8> | QList<quint8> |
Vec<u16> | QList<quint16> |
Vec<u32> | QList<quint32> |
Vec<u64> | QList<quint64> |
Vec<f32> | QList<float> |
Vec<f64> | QList<double> |
Vec<String> | QList<QString> |
Vec<T struct> | QList<T struct> |
HashMap<i32, bool> | QHash<qint32, bool> |
HashMap<i32, i8> | QHash<qint32, qint8> |
HashMap<i32, i16> | QHash<qint32, qint16> |
HashMap<i32, i32> | QHash<qint32, qint32> |
HashMap<i32, i64> | QHash<qint32, qint64> |
HashMap<i32, u8> | QHash<qint32, quint8> |
HashMap<i32, u16> | QHash<qint32, quint16> |
HashMap<i32, u32> | QHash<qint32, quint32> |
HashMap<i32, u64> | QHash<qint32, quint64> |
HashMap<i32, f32> | QHash<qint32, float> |
HashMap<i32, f64> | QHash<qint32, double> |
HashMap<i32, String> | QHash<qint32, String> |
HashMap<i32, T struct> | QHash<qint32, T struct> |
HashMap<String, bool> | QHash<QString, bool> |
HashMap<String, i8> | QHash<QString, qint8> |
HashMap<String, i16> | QHash<QString, qint16> |
HashMap<String, i32> | QHash<QString, qint32> |
HashMap<String, i64> | QHash<QString, qint64> |
HashMap<String, u8> | QHash<QString, quint8> |
HashMap<String, u16> | QHash<QString, quint16> |
HashMap<String, u32> | QHash<QString, quint32> |
HashMap<String, u64> | QHash<QString, quint64> |
HashMap<String, f32> | QHash<QString, float> |
HashMap<String, f64> | QHash<QString, double> |
HashMap<String, String> | QHash<QString, QString> |
HashMap<String, T struct> | QHash<QString, T struct> |
Note that in the table above, the type starting with the q character is an alias for some C++ type, such as qint32, which is int. It makes no difference whether a variable is defined as qint32 or int when programming with QRust. The corresponding aliases are listed below.
typedef | C++ Type |
qint8 | signed char |
qint16 | signed short |
qint32 | signed int |
qint64 | long long int |
quint8 | unsigned char |
quint16 | unsigned short |
quint32 | unsigned int |
quint64 | unsigned long long int |
Packet Structure
The above types of data are encapsulated in a binary package structure when passed between Qt and Rust, the package structure is as follows:
The primitive types package structure starts with 1 byte tag, the value of the tag indicates which type it is, and the following values are differentiated according to the length of bytes occupied by different types, such as i8 and u8 occupy 1 byte, and u16 and i16 occupy 2 bytes. More special is the string type, which describes the length of the string after the tag followed by 4 bytes, and the value of the string after the length (utf-8 vector, no ‘\0’ ending).
The collection types package structure is slightly more complex, but not hard to understand.
list package structure:
The tag ranges from 50 to 99. After the tag there is a 4-byte length unit that describes the length of the list package body. The body part contains all the elements, and the structure of the elements is the same as the primitive type structure.
Hash package structure with int as key:
The hash packet tag ranges from 100 to 149, followed by a 4-byte length unit. Each element in the package consists of two parts, because key is of type int, so the structure is the same as i32 in the primitive package structure, and the value part can also refer to the description of the primitive package structure.
Hash package structure with str as key:
The value of tag ranges from 150 to 199. It’s basically the same structure as the previous one, except that the element key has changed.
struct type:
QRust supports custom struct types, structs can be equated to objects in application scenarios, and support for automated (or possibly semi-automated) transformation of structs is one of the highlights of QRust.
When using Qrust for struct data types to pass between Qt and Rust, there are the following points to note:
1) The field name and quantity must be the same on both sides, and the struct name can be different.
2) The Rust side needs to add serialization and deserialization macros to the struct, such as:
[derive(Serialize, Deserialize, Clone, Debug)]
pub struct A {
a: bool,
v: Vec,
}
3) Qt side needs to carry out QMetaObject transformation of struct, for example:
struct A{
Q_GADGET
Q_PROPERTY(bool a MEMBER a);
Q_PROPERTY(QList v MEMBER v);
public:
bool a;
QList v;
};
In Rust, Serialize and Deserialize macros must be added to struct. In Qt, struct needs to be extended as follows:
- First, you need to add the Q_GADGET macro flag to support Qt meta-object programming.
- The second is to add a Q_PROPERTY macro for each field, format: Q_PROPERTY(type field name MEMBER field name), Q_PROPERTY macro supports struct field traversal, read and write, and type derivation.
- Third, add the public: flag before the field, because the Q_PROPERTY macro execution ends with private:, causing all fields to become private, adding public: will force the change back.
The package structure of struct is the most complex of all types, but it’s also not hard to understand:
The tag of the struct is 24 (base 10). A byte after the tag describes the length of the struct name, followed by the struct name string vector. One byte after the name describes the number of fields (this limits the number of fields to 255), followed by four bytes describing the length of the body, which is all the fields. The field structure is divided into two parts, the field name and the field value. The field name conforms to the definition of the String type, and the field value can refer to the definition of the primitive type or the collection type.
Note: The struct field does not all support primitive types and collection types, which need to be removed:
- struct
- Vec<struct>
- HashMap<i32, struct>
- HashMap<String, struct>
Complex types cannot be nested
A struct field cannot be defined as another struct type. Structs cannot be nested. For example, you cannot use the following struct definition in QRust:
struct A{
struct B b;
}
You can’t nest other collections in a collection type, you can’t define variables like:
QList<QHash<int, bool>> a;
Collection and struct are both complex types. The reason why QRust does not support complex type nesting is related to serialization and deserialization technology. Serialization technology uses the canonical framework library serde in Rust, and QMetaObject is used in the processing of struct on Qt, both of which do not support nesting or are not fully supported.
For static languages such as Rust and Qt, templates and macros are necessary in the serialization process. For example, type derivation is carried out in the serialization process of struct. Compilation preprocessing may generate a lot of code and increase the length of the source code. If complex types are allowed to be nested, the code length may increase exponentially, leading to problems. The compilation preprocessing of C++ templates and Rust macros is more complicated, and it is not discussed here. If you are interested, you can browse the relevant books.
But struct nesting is often encountered, such as the following struct:
struct User{
qint32 id;
QString name;
struct Address address;
};
It is natural to include address objects in user objects, but how do you solve this problem if you don’t support nesting? QRust’s solution is flattening, pass User and Address as two arguments, and the function definition can be modified:
add_user(user); //The user contains the address
Changed to:
add_user(user, address); //flattening
Function input is relatively easy to flatten, if the query from the database, i.e. function return value what to do, can return both user and address? The answer is yes, and QRust has been specifically extended to return multiple return values at the same time.
A function definition in Rust that returns a tuple structure that can contain multiple return values:
pub fn get_user(user_id: i32) -> (User, Address)
Multiple return values can also be processed when serializing:
let (user, address) = get_user(user_id);
let mut pack = Vec::new();
pack.extend(ser::to_pack(&user)?); //Serialize the first return value
pack.extend(ser::to_pack(&address)?); //Serialize the second return value
let pack_multi = ser::to_pack_multi(pack); //The two return values are assembled together
Ok(Some(pack_multi))
In OnTheSSH software, this multi-return value method is often used, for example, the system monitoring needs to obtain CPU, memory, disk, network, port and other structural information at the same time.
This problem can be extended a bit further, for example, when getting all users, it can be handled like this:
pub fn get_all_users() -> (Vec<User>, Vec<Address>)
pub fn get_all_users() -> (Vec<User>, HashMap<i32, Address>)
The first method needs to define a user_id field in the Address to associate which user the address belongs to, and the second method directly uses the key to associate. This is also why there are both int and string keys in QRust’s Hash type, because int and string are the most commonly used primary keys in the database and are used very frequently.
QRust limits the maximum number of function arguments and multiple return values to 255.
Null and Exception
Null and exceptions are special results that are often encountered in a business system, such as the user has been deleted when call get_user(id) function, or when a command is sent but the network is unreachable. In OnTheSSH software, multiple return values are used to handle null and exception, the first return value is a special structure:
struct RetState
{
bool state;
qint32 err_code;
};
The state field indicates the state of the function execution. If it is true, it means that the function was successfully executed and you get the result of the function from the second return value. If it is false, you do not need to get the subsequent return value. At this point, you should look at the err_code field to see what kind of error encoding is involved.
Serialize and deserialize functions
Using the serde framework gives Rust good type inference, so there is a unified from_pack and to_pack function for serialization and deserialization. The Qt side is currently unable to do this, each type needs to correspond to an independent function, the following table lists all serialization and deserialization correspondence:
Type | Serialization function | Deserialization function |
bool | pack_bool | upack_bool |
qint8 | pack_i8 | upack_i8 |
qint16 | pack_i16 | upack_i16 |
qint32 | pack_i32 | upack_i32 |
qint64 | pack_i64 | upack_i64 |
quint8 | pack_u8 | upack_u8 |
quint16 | pack_u16 | upack_u16 |
quint32 | pack_u32 | upack_u32 |
quint64 | pack_u64 | upack_u64 |
float | pack_f32 | upack_f32 |
double | pack_f64 | upack_f64 |
QString | pack_str | upack_str |
struct | pack_struct | upack_struct |
QList<bool> | pcak_list_bool | upcak_list_bool |
QList<qint8> | pcak_list_i8 | upcak_list_i8 |
QList<qint16> | pcak_list_i16 | upcak_list_i16 |
QList<qint32> | pcak_list_i32 | upcak_list_i32 |
QList<qint64> | pcak_list_i64 | upcak_list_i64 |
QList<quint8> | pcak_list_u8 | upcak_list_u8 |
QList<quint16> | pcak_list_u16 | upcak_list_u16 |
QList<quint32> | pcak_list_u32 | upcak_list_u32 |
QList<quint64> | pcak_list_u64 | upcak_list_u64 |
QList<float> | pcak_list_f32 | upcak_list_f32 |
QList<double> | pcak_list_f64 | upcak_list_f64 |
QList<QString> | pcak_list_str | upcak_list_str |
QList<T struct> | pcak_list_struct | upcak_list_struct |
QHash<qint32, bool> | pack_hi_bool | upack_hi_bool |
QHash<qint32, qint8> | pack_hi_i8 | upack_hi_i8 |
QHash<qint32, qint16> | pack_hi_i16 | upack_hi_i16 |
QHash<qint32, qint32> | pack_hi_i32 | upack_hi_i32 |
QHash<qint32, qint64> | pack_hi_i64 | upack_hi_i64 |
QHash<qint32, quint8> | pack_hi_u8 | upack_hi_u8 |
QHash<qint32, quint16> | pack_hi_u16 | upack_hi_u16 |
QHash<qint32, quint32> | pack_hi_u32 | upack_hi_u32 |
QHash<qint32, quint64> | pack_hi_u64 | upack_hi_u64 |
QHash<qint32, float> | pack_hi_f32 | upack_hi_f32 |
QHash<qint32, double> | pack_hi_f64 | upack_hi_f64 |
QHash<qint32, QString> | pack_hi_str | upack_hi_str |
QHash<qint32, T struct> | pack_hi_struct | upack_hi_struct |
QHash<QString, bool> | pack_hs_bool | upack_hs_bool |
QHash<QString, qint8> | pack_hs_i8 | upack_hs_i8 |
QHash<QString, qint16> | pack_hs_i16 | upack_hs_i16 |
QHash<QString, qint32> | pack_hs_i32 | upack_hs_i32 |
QHash<QString, qint64> | pack_hs_i64 | upack_hs_i64 |
QHash<QString, quint8> | pack_hs_u8 | upack_hs_u8 |
QHash<QString, quint16> | pack_hs_u16 | upack_hs_u16 |
QHash<QString, quint32> | pack_hs_u32 | upack_hs_u32 |
QHash<QString, quint64> | pack_hs_u64 | upack_hs_u64 |
QHash<QString, float> | pack_hs_f32 | upack_hs_f32 |
QHash<QString, double> | pack_hs_f64 | upack_hs_f64 |
QHash<QString, QString> | pack_hs_str | upack_hs_str |
QHash<QString, T struct> | pack_hs_struct | upack_hs_struct |