This article is machine translated
QRust is an open source component related to mixed programming in both Qt and Rust languages, and is a support technology for Qt calling Rust functions.
![](https://onthessh.com/wp-content/uploads/2024/11/屏幕截图-2024-11-11-110958.png)
QRust comes from the tool software OnTheSSH, which is jointly built by Qt and Rust. Rust implements the underlying protocol of SSH communication, Qt builds the program interface, and the technical requirements of Qt calling Rust give rise to QRust.
An example of using 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())?; //Deserialization gets parameter 1
let a2 = de::from_pack(args.pop().unwrap())?; //Deserialization gets parameter 2
let a3 = de::from_pack(args.pop().unwrap())?; //Deserialization gets parameter 3
let ret = api::foo2(a1, a2, a3); //Call function foo2 and get the return value
let pack = ser::to_pack(&ret)?; //Serialized return value
Ok(Some(pack))
}
……
}
}
In the above code, by matching the string “foo2” to the function api::foo2(), deserialize the three parameters and serialize the return value of the function.
Qt:
Rust rust("foo2"); //Declare the Rust function foo2 to call
//Parameter 1
QList<qint32> a1 = {100};
QByteArray ba1 = QRust_Ser::pack_list_i32(&a1); //serialize
//Parameter 2
QHash<qint32, QString> a2 = {{1, "abcde"}};
QByteArray ba2 = QRust_Ser::pack_hi_str(&a2); //serialize
//Parameter 3
QHash<QString, QString> a3 = {{"a", "12345中文"}};
QByteArray ba3 = QRust_Ser::pack_hs_str(&a3); //serialize
rust.call(ba1, ba2, ba3); //Call the function and pass arguments
QHash<QString, QString> ret; //Declared return value
QRust_De::upack_hs_str(rust.pop(), &ret); //Deserialization gets the return value
In the above code, declare the Rust side function foo2 to call, serialize and pass three parameters, and then deserialize the function call to get the return value. In the example, transformations of three complex data types are implemented:
Qt | QList<qint32> | QHash<qint32, QString> | QHash<QString, QString> |
Rust | Vec<i32> | HashMap<i32, String> | HashMap<String, String> |
Mixed language programming is always a challenging task. For both C and Rust, it is relatively easy for Rust to call C functions. In Rust’s underlying layer, there are a lot of call statements for C functions wrapped in unsafe. On the other hand, calling Rust from C is a bit more complicated, especially when there are complex parameter passes, such as collections, custom structs, and data stored on the heap, which immediately leads to exponential complexity and horrible code error rates.
In earlier versions of OnTheSSH, another simple technical implementation, Qt calls Rust services in TCP/Socket mode and encapsulates data via JSON, which is very easy to implement, but also has the following problems:
- Additional TCP services trigger a security prompt in some environments, which is always an unfriendly message to the user.
- Socket is a network call, compared to the intra-process call performance is much worse.
- There are a lot of JSON-related serialization and deserialization statements in the code, which is somewhat verbose.
Better techniques are needed to solve the above problems, but there are two technical difficulties with Qt calling Rust:
- How does Qt conveniently call Rust functions, and in what form?
- How can function arguments and return values, different variable types be easily passed from Qt to Rust and back from Rust to Qt?
Some programming languages (such as Java) have runtime reflection mechanisms, which can obtain the types and values of objects in memory, and dynamically call functions, which are useful tools for hybrid programming. However, neither C++ nor Rust has a runtime reflection mechanism, which is determined by the language philosophy, so it is difficult to dynamically obtain or analyze variable types in C++ and Rust.
In Rust’s language specification, Rust has an FFI interface for C to call, but I don’t want to cover FFI here because it’s too complex and error-prone.
QRust is a compromise technical implementation to solve the above problems, and its design ideas are reflected in the following aspects:
- To reduce the complexity of the FFI interface, users need only a small amount of basic learning to use Qt calls to Rust.
- Reduce the amount of code needed to serialize and deserialize.
- Pragmatism does not pursue full automation like runtime reflection, nor does it deliberately pursue the ability to pass complex data types, in the limited conditions of Qt and Rust languages, to maximize the ability to provide mixed programming.
- In line with the specifications and habits of Qt and Rust, for example, in serialization and deserialization technology, Rust uses the standard serde framework, and Qt uses QMetaObject technology to achieve struct traversal and read and write.
Download