QRust(二)数据类型

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
posted @   dyf029  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示