QRust支持的数据类型可分为两类:基本类型、集合类型。这些数据类型可作为函数参数、返回值或struct的字段,在Qt和Rust之间传递。
基本类型
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 |
集合类型
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> |
注意上面表格中以q开始的通常是一些C++类型的别名,比如qint32就是int。在使用QRust编程时变量定义为qint32还是int是没有区别的,下面列出了这些别名的对应。
typedef | C++原类型 |
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 |
数据包结构
以上类型的数据在Qt和Rust之间传递时被封装为一种二进制包结构,基本类型的包结构如下:
基本数据类型包结构以一个字节的tag标记开始,tag的数值表示这是哪种类型,后面的值部分根据类型不同占用字节长度也有区分,比如i8和u8占用1个字节,u16和i16占用两个字节。比较特殊的是string类型,在tag后紧跟4个字节描述字符串的长度,在长度单元后是字符串的值(utf-8字节向量,没有’\0’结尾)。
集合类型的包结构稍显复杂一些,但不难理解。
list包结构:
list包的tag根据其包含的数据类型不同而有变化,范围50~99。tag后跟4字节长度单元,描述list包体的长度。包体部分是一个接一个的元素,元素的结构和基本数据包结构一样。
以int为key的Hash包结构:
hash包的tag根据其包含的数据类型变化,范围100~149,和list一样tag后跟4字节长度单元。包体中每个元素由两部分组成,因为key是int类型所以结构和基础包结构中的i32相同,值部分也是可参照基础包结构的描述。
以字符串为key的Hash包结构:
以字符串为key的tag取值范围150~199,和以int为key的hash包结构基本相同,只是元素key有变化。
struct类型:
QRust支持自定义的struct类型,struct在应用场景中可以等同为对象,支持struct的自动化(也可能称之为半自动化)转换是QRust的靓点之一。
使用Qrust进行struct数据类型在Qt和Rust之间传递时,有以下注意点:
1)字段名称、数量双方必须一致,struct名称可以不一样。
2)Rust端需要对struct加上序列化和反序列化的宏标记,例如:
[derive(Serialize, Deserialize, Clone, Debug)] pub struct A { a: bool, v: Vec, }
3)Qt端需要对struct进行QMetaObject的改造,例如:
struct A{ Q_GADGET Q_PROPERTY(bool a MEMBER a); Q_PROPERTY(QList v MEMBER v); public: bool a; QList v; };
在Rust端必须给struct添加Serialize和Deserialize宏,在Qt端需要对struct进行以下扩展:
- 第一需要添加 Q_GADGET 宏标志,来支持Qt的元对象编程。
- 第二为每个字段添加 Q_PROPERTY 宏,格式: Q_PROPERTY(类型 字段名 MEMBER 字段名),Q_PROPERTY 宏支持struct的字段遍历、读写、以及类型推导。
- 第三在字段前添加 public:标志,因为Q_PROPERTY 宏执行以 private: 结束,将导致所有字段变为私有的,添加 public: 将强制改回来。
struct的包结构是所有类型中最复杂的,但也不难理解:
struct的tag标志是24(10进制),tag后的一个字节描述struct名称的长度,后面跟struct名称字符串向量。名称后的一个字节描述字段的数量(这点限制了字段最多255个),再后面4个字节描述包体的长度,包体部分是一个一个的字段。字段结构分为两个部分,字段名和字段值。字段名符合基本类型String的定义,字段值可参考基本类型或集合类型的定义。
注意struct字段并没有全部支持基本类型和集合类型,需要去除这几个:
- struct
- Vec<struct>
- HashMap<i32, struct>
- HashMap<String, struct>
类型不足的原因和解决方法
struct的字段不能再定义为另一个struct类型,既struct不能嵌套,例如你不能在QRust中使用如下的struct定义:
struct A{
struct B b;
}
同样的原因在集合类型中也不能嵌套其他集合,你不能定义这样的变量:
QList<QHash<int, bool>> a;
集合类型和struct都属于复杂类型,QRust不支持复杂类型嵌套的原因和序列化和反序列化技术有关,序列化技术在Rust端使用了规范框架库serde,在Qt端对struct的处理使用了QMetaObject,这两种技术都不支持嵌套或不完全支持。
对于Rust和Qt这种静态语言在序列化处理过程中离不开模板和宏,例如在struct的序列化过程中进行类型推导,编译预处理时就可能生成大量代码而增加源代码的长度,如果允许复杂类型的嵌套,代码长度可能会指数级增加导致出现问题。有关C++模板和Rust宏的编译预处理比较复杂,这里不展开讨论,有兴趣可浏览相关的书籍资料。
但struct嵌套是经常遇到的场景,比如下面的struct结构:
struct User{ qint32 id; QString name; struct Address address; };
在用户对象中包含地址对象是自然而然地,如果不支持嵌套怎么来解决这个问题呢?QRust的解决办法是扁平化,将User和Address分成两个参数传递,函数定义可做修改:
add_user(user); //原定义user中包含address
改为:
add_user(user, address); //扁平化的
函数入参比较容易进行扁平化,如果从数据库查询,即函数返回值怎样处理呢,能同时返回user和address吗?答案是可以,QRust针对性的做了扩展,可以同时返回多个返回值。
Rust端的函数定义,返回tuple结构可包含多个返回值:
pub fn get_user(user_id: i32) -> (User, Address)
反序列化时也可对多个返回值进行处理:
let (user, address) = get_user(user_id); let mut pack = Vec::new(); pack.extend(ser::to_pack(&user)?); //序列化第一个返回值 pack.extend(ser::to_pack(&address)?); //序列化第二个返回值 let pack_multi = ser::to_pack_multi(pack); //两个返回值组包在一起 Ok(Some(pack_multi))
在OnTheSSH软件中,经常使用这种多返回值的方式,例如系统监控要同时获得CPU、内存、磁盘、网络、端口等多种结构信息。
这个问题再扩展一些,比如获得全部用户时,扁平化可以这样处理:
pub fn get_all_users() -> (Vec<User>, Vec<Address>) pub fn get_all_users() -> (Vec<User>, HashMap<i32, Address>)
第一种方式需要在Address中定义一个user_id的字段,来关联地址是属于哪一个用户的,第二种则直接用key进行关联。这也是在QRust的Hash类型中为什么会同时存在int和string两种类型的key,因为int和string在数据库中最常用作主键,使用频率都很高。
QRust对函数参数和多返回值的最大数量,限制在255个。
空和异常
在业务系统中空和异常是经常遇到的特殊结果,比如get_user(id)时用户已被删除,或者发送命令而网络不可达时。在OnTheSSH软件中是利用多返回值来处理空和异常的,第一个返回值是一个特殊结构:
struct RetState { bool state; qint32 err_code; };
字段state表示函数执行的状态,如果为真表示函数执行成功,你再从第二个返回值开始获得函数的结果,反之如果为假,就不必获取后续的函数结果了,这时应该查看err_code字段是哪种错误的编码。
序列化和反序列化函数
使用serde框架让Rust有很好的类型推导能力,所以序列化和反序列化时有统一from_pack和to_pack函数。Qt端目前还做不到,每种类型都需要对应独立的函数,下表列出了所有的序列化和反序列化对应关系:
类型 | 序列化函数 | 反序列化函数 |
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 |