QRust是一个开源组件,是Qt和Rust两种语言的混合编程中间件,是Qt调用Rust函数的支持技术。
QRust来源于工具软件OnTheSSH,OnTheSSH软件由Qt和Rust两种语言共同构建,Rust实现了SSH通讯底层协议,Qt搭建程序界面,Qt调用Rust的技术需求催生出了QRust。
一个使用QRust的例子:
Rust端:
fn invoke(fun_name: &str, mut args: Vec<&[u8]>) -> Result<Option<Vec<u8>>> {match fun_name{……"foo2" =>{let a1 = de::from_pack(args.pop().unwrap())?; //反序列化获得参数1let a2 = de::from_pack(args.pop().unwrap())?; //反序列化获得参数2let a3 = de::from_pack(args.pop().unwrap())?; //反序列化获得参数3 let ret = api::foo2(a1, a2, a3); //调用函数foo2,得到返回值let pack = ser::to_pack(&ret)?; //序列化返回值 Ok(Some(pack))}……} }
在上面的代码中,通过匹配字符串”foo2″定位到函数api::foo2(),传入三个反序列化得到的参数,并将函数返回值序列化后返回。
Qt端:
Rust rust("foo2"); //声明要调用的Rust函数foo2//参数 1 QList<qint32> a1 = {100}; QByteArray ba1 = QRust_Ser::pack_list_i32(&a1); //序列化 //参数 2 QHash<qint32, QString> a2 = {{1, "abcde"}}; QByteArray ba2 = QRust_Ser::pack_hi_str(&a2); //序列化 //参数 3 QHash<QString, QString> a3 = {{"a", "12345中文"}}; QByteArray ba3 = QRust_Ser::pack_hs_str(&a3); //序列化 rust.call(ba1, ba2, ba3); //调用函数并传参 QHash<QString, QString> ret; //声明返回值 QRust_De::upack_hs_str(rust.pop(), &ret); //反序列化获得返回值
在上面的代码中,声明要调用的Rust端函数foo2,序列化并传递三个参数,函数调用后反序列化获得返回值。在示例中,实现了三种复杂数据类型的转换:
Qt端 | QList<qint32> | QHash<qint32, QString> | QHash<QString, QString> |
Rust端 | Vec<i32> | HashMap<i32, String> | HashMap<String, String> |
QRust能带来什么?
混合语言编程总是一项具有挑战性的任务,在C/C++和Rust语言之间,Rust调用C函数相对容易一些,在Rust的底层中就存在着大量的用unsafe包裹的C函数的调用语句。反过来C调用Rust就相对复杂一些,特别是传递复杂的参数时,比如集合、自定义的struct、堆上存放的数据,这种场景马上会带来指数级的复杂性和恐怖的代码出错率。
在OnTheSSH的早期版本中,采用了另一种简单的技术实现,Qt以TCP/Socket方式调用Rust服务,通过JSON封装数据,这种方式非常容易实现,但也存在以下问题:
- 凭空多出了TCP服务,不仅占用资源,在一些环境中还会触发安全提示,对于使用者不是很友好。
- Socket是网络调用,相比进程内调用性能差了很多。
- 代码中存在大量的JSON相关的序列化和反序列化语句,显得有些啰里啰唆。
因此需要使用更好的技术来解决以上问题,但Qt调用Rust存在两个技术难点:
- Qt怎样方便的调用Rust函数,以什么形式调用?
- 种类众多且复杂的数据类型怎样方便的从Qt传递到Rust,然后从Rust传回Qt?
部分编程语言(如Java)具有运行时的反射机制,可以获得内存中对象的类型和值,能动态调用函数,是进行混合编程的利器。但C++和Rust都没有运行时反射机制(由语言哲学决定),很难在C++和Rust中动态获得或解析变量类型。
在Rust的语言规范中,C语言调用Rust函数有一套接口FFI,但这里我不想介绍FFI因为它太复杂,并不是理想的解决方案。
QRust是为解决以上问题的一种折中的技术实现,它的设计思想体现在这几个方面:
- 降低FFI接口的复杂性,让使用者通过基础的少量的学习,即可掌握Qt对Rust的调用技术。
- 减少序列化和反序列化的代码量。
- 推行实用主义,不追求具有反射能力的完全自动化,也不刻意追求复杂数据类型的传递能力,在Qt和Rust语言的有限条件下,最大限度的提供混合编程的能力。
- 契合Qt和Rust的规范和习惯,比如在序列化和反序列化技术上,Rust选用标准的serde框架,Qt选用QMetaObject技术来实现struct的遍历和读写。
下载QRust源码
QRust Source Code – onthessh.com