This article is machine translated
In this chapter, follow the demo examples with the QRust project to learn how to use QRust step by step.
Example with no parameters and no return value
Let’s start with the simplest example, foo().
Qt:
void MainWindow::on_btn_foo_clicked()
{
ui->ptext_out->appendPlainText("------------- foo() -------------");
Rust rust("foo");
rust.call();
if (!rust.fun_ok())
{
ui->ptext_out->appendPlainText("Function execution failure");
return;
}
ui->ptext_out->appendPlainText("Function execution success");
}
This is a call with no arguments and no return value, Rust rust(“foo”); This sentence sets the string corresponding name of the business function while instantiating the call object, and rust.call() initiates the call, which is a synchronous call function, meaning that the call will block here until it receives a return from Rust. rust.fun_ok() to determine whether the calling process is abnormal.
Rust side match section:
match fun_name
{
"foo" =>
{
api::foo();
Ok(None)
}
......
}
After the “foo” string is matched, the business function api is called api::foo(), OK(None) indicates that the business function has no return value.
Rust side business functions:
pub fn foo()
{
debug!("foo() running...");
}
There is one parameter and no return value
Qt:
Rust rust("foo1");
qint32 i = 100;
QByteArray ba1 = QRust_Ser::pack_i32(i);
rust.call(ba1);
In the foo1 example, an int parameter 100 is passed, the QRust_Ser::pack_i32(i) statement wraps the parameter serialization, and Ru.call (ba1) passes the wrapped parameter to Rust as it is called.
Rust side match section:
let a1 = de::from_pack(args.pop().unwrap())?;
api::foo1(a1);
Ok(None)
de::from_pack deserializes the parameter 100 and calls the business function foo1.
Rust side business functions:
pub fn foo1(arg: i32)
{
debug!("foo1() running... arg: {}", arg);
}
Multiple parameters
Qt:
Rust rust("foo2");
//Parameter 1
QList<qint32> a1 = {100};
QByteArray ba1 = QRust_Ser::pack_list_i32(&a1); //Serialization
//Parameter 2
QHash<qint32, QString> a2 = {{1, "abcde"}};
QByteArray ba2 = QRust_Ser::pack_hi_str(&a2); //Serialization
//Parameter 3
QHash<QString, QString> a3 = {{"a", "12345中文"}};
QByteArray ba3 = QRust_Ser::pack_hs_str(&a3); //Serialization
rust.call(ba1, ba2, ba3);
if (!rust.fun_ok())
{
ui->ptext_out->appendPlainText("Function execution failure");
return;
}
//deserialization
QHash<QString, QString> ret;
if (!QRust_De::upack_hs_str(rust.pop(), &ret))
{
ui->ptext_out->appendPlainText("Deserialization failure");
return;
}
This example passes three arguments and deserializes the return value of the business function via QRust_De::upack_hs_str. rust.pop() takes and consumes the first return value, and if the business function has multiple return values, calls rust.pop() again to get the second. During deserialization, the result variable needs to be defined first, corresponding to the QHash ret statement, and then the pointer to ret is passed to the upack_hs_str function, and the result value is written to the ret variable after the internal deserialization of upack_hs_str.
Rust side match section:
let a1 = de::from_pack(args.pop().unwrap())?;
let a2 = de::from_pack(args.pop().unwrap())?;
let a3 = de::from_pack(args.pop().unwrap())?;
let ret = api::foo2(a1, a2, a3);
let pack = ser::to_pack(&ret)?;
Ok(Some(pack))
Deserialize the three parameters separately, taking care that they are in the same order as the Qt side. The return value of the business function serializes the group package via to_pack and is then returned to the caller.
It should be noted here that the parameters of the to_pack function are passed references (&ret in the example) for all collection types and the String and struct of the primitive types, while they are passed values for the other primitive types. This is because the types of passed references are of indefinite length and may take up a lot of memory. Passing references can reduce memory consumption.
It makes sense for the return value to be encapsulated as Ok(Some(pack)), where Ok indicates a successful function call rather than an exception, and Some indicates a value rather than an empty one.
Rust side business functions:
pub fn foo2(arg1: Vec<i32>, arg2: HashMap<i32, String>, arg3: HashMap<String, String>) -> HashMap<String, String>
{
debug!("foo2() running... arg1: {:?}, arg2:{:?}, args:{:?}", arg1, arg2, arg3);
arg3
}
Example of custom struct
This is an example of passing custom struct data, first look at the struct definition on the Qt side:
struct A
{
Q_GADGET
Q_PROPERTY(bool a MEMBER a);
Q_PROPERTY(QList<quint32> v MEMBER v);
public:
bool a;
QList<quint32> v;
};
The definition of this struct is explained in detail in Chapter 2, and then look at the call section.
Rust rust("foo_struct");
A a1;
a1.a = true;
a1.v.append(10);
QByteArray ba1 = QRust_Ser::pack_struct(&a1);
quint8 count = 5;
QByteArray ba2 = QRust_Ser::pack_u8(count);
rust.call(ba1, ba2);
if (!rust.fun_ok())
{
ui->ptext_out->appendPlainText("Function execution failure");
return;
}
QHash<QString, A> rets;
if (!QRust_De::upack_hs_struct(rust.pop(), &rets))
{
ui->ptext_out->appendPlainText("Deserialization failure");
return;
}
It can be seen that in QRust, the serialization process of the custom struct is the same as the previous simple variables, and the deserialization process of the returned struct wrapped in the Hash structure is also simple.
Rust side match section:
let arg = de::from_pack(args.pop().unwrap())?;
let count = de::from_pack(args.pop().unwrap())?;
let ret = api::foo_struct(arg, count);
let pack = ser::to_pack(&ret)?;
Ok(Some(pack))
Rust side business functions:
pub fn foo_struct(arg: A, count: u8) -> HashMap<String, A>
{
debug!("foo_struct() runnint... count: {}, arg: {:#?}", count, arg);
let mut v = HashMap::new();
for i in 0..count
{
v.insert(i.to_string(), arg.clone());
}
v
}
Multiple parameters, multiple return values
Qt:
QList<bool> b = {true, false};
QList<qint8> i8 = {-1, 0, 1};
QList<qint16> i16 = {-1, 0, 1};
QList<qint32> i32 = {-1, 0, 1};
QList<qint64> i64 = {-1, 0, 1};
QList<quint8> u8 = {0, 1, 2};
QList<quint16> u16 = {0, 1, 2};
QList<quint32> u32 = {0, 1, 2};
QList<quint64> u64 = {0, 1, 2};
QList<float> f32 = {(float)0.1, (float)0.2, (float)0.3};
QList<double> f64 = {0.1, 0.2, 0.3};
QList<QString> str = {"abcde", "12345", "中文"};
QList<A> strcts;
A a1;
a1.a = true;
a1.v.append(10);
strcts.append(a1);
QByteArray ba1 = QRust_Ser::pack_list_bool(&b);
QByteArray ba2 = QRust_Ser::pack_list_i8(&i8);
QByteArray ba3 = QRust_Ser::pack_list_i16(&i16);
QByteArray ba4 = QRust_Ser::pack_list_i32(&i32);
QByteArray ba5 = QRust_Ser::pack_list_i64(&i64);
QByteArray ba6 = QRust_Ser::pack_list_u8(&u8);
QByteArray ba7 = QRust_Ser::pack_list_u16(&u16);
QByteArray ba8 = QRust_Ser::pack_list_u32(&u32);
QByteArray ba9 = QRust_Ser::pack_list_u64(&u64);
QByteArray ba10= QRust_Ser::pack_list_f32(&f32);
QByteArray ba11= QRust_Ser::pack_list_f64(&f64);
QByteArray ba12= QRust_Ser::pack_list_str(&str);
QByteArray ba13= QRust_Ser::pack_list_struct(&strcts);
Rust rust("test_list");
rust.call(ba1, ba2, ba3, ba4, ba5, ba6, ba7, ba8, ba9, ba10, ba11, ba12, ba13);
QList<bool> ret_b;
QList<qint8> ret_i8;
QList<qint16> ret_i16;
QList<qint32> ret_i32;
QList<qint64> ret_i64;
QList<quint8> ret_u8;
QList<quint16> ret_u16;
QList<quint32> ret_u32;
QList<quint64> ret_u64;
QList<float> ret_f32;
QList<double> ret_f64;
QList<QString> ret_str;
QList<A> ret_strcts;
QRust_De::upack_list_bool(rust.pop(), &ret_b);
QRust_De::upack_list_i8(rust.pop(), &ret_i8);
QRust_De::upack_list_i16(rust.pop(), &ret_i16);
QRust_De::upack_list_i32(rust.pop(), &ret_i32);
QRust_De::upack_list_i64(rust.pop(), &ret_i64);
QRust_De::upack_list_u8(rust.pop(), &ret_u8);
QRust_De::upack_list_u16(rust.pop(), &ret_u16);
QRust_De::upack_list_u32(rust.pop(), &ret_u32);
QRust_De::upack_list_u64(rust.pop(), &ret_u64);
QRust_De::upack_list_f32(rust.pop(), &ret_f32);
QRust_De::upack_list_f64(rust.pop(), &ret_f64);
QRust_De::upack_list_str(rust.pop(), &ret_str);
QRust_De::upack_list_struct(rust.pop(), &ret_strcts);
This example is a unit test for all List types, passing in and returning all list type parameters.
Rust side match section:
let b = de::from_pack(args.pop().unwrap())?;
let i8 = de::from_pack(args.pop().unwrap())?;
let i16 = de::from_pack(args.pop().unwrap())?;
let i32 = de::from_pack(args.pop().unwrap())?;
let i64 = de::from_pack(args.pop().unwrap())?;
let u8 = de::from_pack(args.pop().unwrap())?;
let u16 = de::from_pack(args.pop().unwrap())?;
let u32 = de::from_pack(args.pop().unwrap())?;
let u64 = de::from_pack(args.pop().unwrap())?;
let f32 = de::from_pack(args.pop().unwrap())?;
let f64 = de::from_pack(args.pop().unwrap())?;
let str = de::from_pack(args.pop().unwrap())?;
let strct = de::from_pack(args.pop().unwrap())?;
let (b, i8, i16, i32, i64,
u8, u16, u32, u64,
f32, f64, str, strct)
= api::test_list(b, i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, str, strct);
let mut pack = Vec::new();
pack.extend(ser::to_pack(&b)?);
pack.extend(ser::to_pack(&i8)?);
pack.extend(ser::to_pack(&i16)?);
pack.extend(ser::to_pack(&i32)?);
pack.extend(ser::to_pack(&i64)?);
pack.extend(ser::to_pack(&u8)?);
pack.extend(ser::to_pack(&u16)?);
pack.extend(ser::to_pack(&u32)?);
pack.extend(ser::to_pack(&u64)?);
pack.extend(ser::to_pack(&f32)?);
pack.extend(ser::to_pack(&f64)?);
pack.extend(ser::to_pack(&str)?);
pack.extend(ser::to_pack(&strct)?);
let pack_multi = ser::to_pack_multi(pack);
Ok(Some(pack_multi))
For multiple return values, each return package is extended to the collection of multiple return values, and ser::to_pack_multi will package the collection and return it.
Rust side business functions:
pub fn test_list(b:Vec<bool>, i8:Vec<i8>, i16:Vec<i16>, i32:Vec<i32>, i64:Vec<i64>,
u8:Vec<u8>, u16:Vec<u16>, u32:Vec<u32>, u64:Vec<u64>, f32:Vec<f32>, f64:Vec<f64>,
str:Vec<String>, strct: Vec<A>)
-> (Vec<bool>, Vec<i8>, Vec<i16>, Vec<i32>, Vec<i64>,
Vec<u8>, Vec<u16>, Vec<u32>, Vec<u64>, Vec<f32>, Vec<f64>,
Vec<String>, Vec<A>)
{
debug!("b: {:?}", b);
debug!("i8: {:?}", i8);
debug!("i16: {:?}", i16);
debug!("i32: {:?}", i32);
debug!("i64: {:?}", i64);
debug!("u8: {:?}", u8);
debug!("u16: {:?}", u16);
debug!("u32: {:?}", u32);
debug!("u64: {:?}", u64);
debug!("f32: {:?}", f32);
debug!("f64: {:?}", f64);
debug!("str: {:?}", str);
debug!("strct: {:#?}", strct);
(b, i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, str, strct)
}
Asynchronous call example
If the business function takes a long time to execute, the synchronous call will block the Qt window thread, causing the window program to be unresponsive. Asynchronous calls in QRust are run through Qt’s signal/slot mechanism.
Qt mainwindow.h file:
private:
Ui::MainWindow *ui;
Rust *rust_asyn;
Start by defining a pointer to a Rust variable that will be used during asynchronous calls.
Define the slot function that the function completes:
private slots:
void finish();
Then bind the signal to the slot in the constructor (mainwindow.cpp file) :
rust_asyn = new Rust();
connect(rust_asyn, &Rust::fun_finish, this, &MainWindow::finish);
new Rust() instantiates the Rust object, and notice that the string name of the function to be called is not defined.
Let’s look at the code for the call:
rust_asyn->reset();
rust_asyn->setFunName("test_asyn");
qint32 i = 7;
QByteArray ba1 = QRust_Ser::pack_i32(i);
rust_asyn->asyncall(ba1);
When the synchronous call Rust objects can be released after the completion of the call, and when the asynchronous call rust objects because of signals and slots, usually a longer life cycle, so the writing here is different from the previous:
- reset() resets rust objects, which means that rust objects are reusable.
- setFunName sets the name of the called business function.
- asyncall indicates that this is an asynchronous call, and the asyncall function is returned immediately, so it does not occupy GUI threads.
QRust monitors the call internally, and if Rust returns, it immediately signals and the slot function finish executes:
void MainWindow::finish()
{
ui->btn_asyn->setEnabled(true); //enable button
ui->btn_asyn->setStyleSheet("Color:black");
if (!rust_asyn->fun_ok())
{
ui->ptext_out->appendPlainText("Function execution failure");
return;
}
qint32 ret;
if (!QRust_De::upack_i32(rust_asyn->pop(), &ret))
{
ui->ptext_out->appendPlainText("Deserialization failure");
return;
}
QString out = QString("result: %1").arg(QString::number(ret));
ui->ptext_out->appendPlainText(out);
}
Asynchronous calls are a Qt side technology, and Rust doesn’t care and can’t tell whether a call is synchronous or asynchronous.
Rust side match section:
let i = de::from_pack(args.pop().unwrap())?;
let ret = api::test_asyn(i);
let pack = ser::to_pack(&ret)?;
Ok(Some(pack))
Rust side business functions:
pub fn test_asyn(i: i32) -> i32
{
debug!("test_asyn() start...");
//Sleep for 10 seconds
let ten_secs = time::Duration::from_millis(10_000);
thread::sleep(ten_secs);
debug!("test_asyn() finish...");
i + i
}
Performance test example
This example computes 1 + 2 + 3 + …… + 100000.
Qt:
quint64 n = 0;
int max = 100000;
ui->ptext_out->appendPlainText("------------- test speed -------------");
qint64 start = QDateTime::currentMSecsSinceEpoch();
for (int i = 1; i <= max; i++)
{
Rust rust("test_speed");
QByteArray ba1 = QRust_Ser::pack_u64(n);
QByteArray ba2 = QRust_Ser::pack_i32(i);
rust.call(ba1, ba2);
if (!rust.fun_ok())
{
ui->ptext_out->appendPlainText("Function execution failure");
return;
}
if (!QRust_De::upack_u64(rust.pop(), &n))
{
ui->ptext_out->appendPlainText("Deserialization failure");
return;
}
}
qint64 end = QDateTime::currentMSecsSinceEpoch();
QString out1 = QString("1 + 2 + 3 + ... + %1 = %2").arg(QString::number(max), QString::number(n));
QString out2 = QString("time: %1ms").arg(QString::number(end-start));
ui->ptext_out->appendPlainText(out1);
ui->ptext_out->appendPlainText(out2);
Rust side match section:
let n = de::from_pack(args.pop().unwrap())?;
let i = de::from_pack(args.pop().unwrap())?;
let n = test_speed(n, i);
let pack = ser::to_pack(&n)?;
Ok(Some(pack))
Rust side business functions:
pub fn test_speed(n: u64, i: i32) -> u64
{
n + i as u64
}
Below is a screenshot of the performance test example running on my notebook, 100,000 calls consume more than 1 second, you can see that the efficiency of intra-process calls is still very high. The actual time consumption occurs mainly in the serialization and deserialization operations during the call.
![This image has an empty alt attribute; its file name is demo_speed.png](https://onthessh.com/wp-content/uploads/2024/09/image.png)
———————
The main purpose of the other examples in the project is to unit test all data types.
The example with empty in the name is doing an empty collection test for all collection types, because hollow collections in QRust are special in serialization and deserialization operations.