Для чего нужна данная статья? :
Научиться использовать Rust для написания библиотеки и вызывая ее из Qt6 через FFI с использованием ML.
Освоить:
- Использование QML для интерфейса.
- Взаимодействие между Rust и QML через cxx-qt.
- Асинхронную обработку данных в Rust (tokio).
- Динамическое обновление UI из Rust.
Найти компромиссы:
FFI (bindgen, cxx) 🟢 Полный доступ к Qt API
qt-rust (qmetaobject, cxx-qt)🟢 Хорошая интеграция
D-Bus/WebSockets/gRPC🟠 Раздельные процессы
cpp_to_rust🟢 Автоматическая генерация
cdylib + CMake🟠требует C++
slint (Rust-альтернатива Qt)🔴 Не 100% Qt
Зачем Вам это уметь? :
🔹 1. Прямое взаимодействие через C++ и FFI (Foreign Function Interface)
Rust и Qt могут взаимодействовать через C++ с помощью FFI:
- Rust код вызывает C++-библиотеки Qt через extern "C".
- Можно вручную писать C++-код-обертку и использовать CXX (cxx crate) или bindgen для генерации привязок.
- Минусы: сложность ручного связывания, возможные проблемы с совместимостью ABI.
Примеры библиотек:
- cxx — безопасное взаимодействие Rust <-> C++.
- bindgen — автоматическая генерация привязок к C++ API.
🔹 2. Использование qt-rust (Qt и Rust через C++-мост)
Это более удобный способ, чем чистый FFI. qt-rust позволяет использовать Qt с минимальными усилиями.
Примеры:
- qmetaobject — удобная привязка к Qt Meta-Object System (QML + QtWidgets).
- cxx-qt — современный мост между Rust и Qt, который использует cxx и делает возможным интеграцию с QML.
Минусы:
- Внутри всё равно есть C++-мост.
- Пока не поддерживает все возможности Qt.
🔹 3. Использование QML и Rust через D-Bus или WebSockets
Можно использовать QML (Qt Quick) для GUI, а бизнес-логику писать на Rust. Взаимодействие возможно через:
- D-Bus (zbus) — удобный способ IPC между процессами.
- WebSockets (например, через tokio-tungstenite).
- gRPC (tonic) — для более сложного взаимодействия.
Примеры библиотек:
- zbus — D-Bus для Rust.
- tokio-tungstenite — WebSocket-сервер.
- tonic — gRPC для Rust.
Плюсы:
✅ Простота и гибкость.
✅ Rust и Qt могут работать в отдельных процессах.
❌ Чуть больше накладных расходов из-за IPC.
🔹 4. Использование cpp_to_rust (Генерация привязок к Qt)
Есть проекты, которые генерируют привязки к Qt API автоматически:
- Rust Qt Binding Generator — устаревший, но полезный проект.
- cpp_to_rust — автоматическая генерация Rust-оберток для C++ Qt API.
Минусы:
- Часто такие проекты забрасываются.
- Требуют компиляции больших C++ библиотек.
🔹 5. Компиляция Rust в CDylib и подключение к Qt через CMake
Если у вас есть C++ Qt6-проект, можно написать бизнес-логику на Rust, скомпилировать её в cdylib (.so, .dll) и подключить через CMake.
Пример Cargo.toml:
[lib]
crate-type = ["cdylib"]
Пример вызова в C++:
extern "C" int my_rust_function();
Плюсы:
✅ Rust можно использовать без изменения существующего Qt-проекта.
❌ Нужно писать C++-обертки.
🔹 6. Использование Qt с Rust через slint (альтернатива QML)
slint — это альтернатива Qt/QML, написанная на Rust, но с синтаксисом, похожим на QML.
Преимущества:
- Нет зависимости от C++/Qt.
- Написано на Rust и работает нативно.
- Поддержка WebAssembly, Linux, Windows, macOS, Android.
Пример использования:
slint::slint! {
export component App inherits Window {
Text { text: "Hello, World!"; }
}
}
fn main() {
App::new().unwrap().run().unwrap();
}
Минусы:
❌ Пока не поддерживает 100% возможностей Qt.
Описание логики приведенного выше кода
use cxx_qt_lib::CxxQtType;
Что это?
Это подключение (импорт) модуля CxxQtType из библиотеки cxx_qt_lib. Этот модуль нужен, чтобы Rust мог взаимодействовать с C++ и Qt — популярным фреймворком для создания графических интерфейсов.
Пример:
Представь, что ты пишешь программу на Rust, но хочешь использовать красивые кнопки и окна из Qt (которые обычно пишут на C++). Чтобы Rust "понимал" эти кнопки, нужен специальный "переводчик" — это и есть CxxQtType.
#[cxx_qt::bridge(namespace = "rust_qt_ml")]
Что это?
Это атрибут (аннотация), который говорит компилятору: "Сгенерируй код, чтобы Rust и C++/Qt могли общаться друг с другом". Все функции и структуры внутри этого модуля будут доступны из C++/Qt под именем rust_qt_ml.
Пример:
Если ты создаёшь игру, где логика написана на Rust, а интерфейс — на C++/Qt, этот атрибут поможет им "дружить".
#[qobject] и #[derive(Default, CxxQtType)]
Что это?
- #[qobject] — говорит, что структура MlBridge будет объектом Qt (QObject). Это основа для всех виджетов и классов в Qt.
- #[derive(Default, CxxQtType)] — автоматически добавляет стандартные значения для полей структуры и позволяет использовать её в коде C++/Qt.
Пример:
Представь, что ты создаёшь класс "Кофеварка". #[qobject] делает её "умной" — теперь её можно использовать в Qt-интерфейсе, а #[derive(Default)] позволяет создать кофеварку с настройками по умолчанию.
#[qproperty]
Что это?
Это атрибут, который делает поле структуры свойством Qt. Такие свойства можно менять и читать из C++/Qt, а также привязывать к интерфейсу (например, к ползунку или метке).
Пример:
В твоей кофеварке есть свойство "температура". С помощью #[qproperty] ты можешь привязать его к ползунку на экране, и пользователь сможет менять температуру, перемещая ползунок.
#[qinvokable]
Что это?
Это атрибут, который делает функцию доступной для вызова из C++/Qt. То есть, ты можешь вызвать эту функцию из кода на C++ или прямо из Qt Designer.
Пример:
В твоей кофеварке есть функция "сварить кофе". С помощью #[qinvokable] ты можешь добавить кнопку "Сварить" в интерфейсе, и при нажатии будет вызываться эта функция.
Логика функции predict_image
Что делает функция?
- Создаёт новый классификатор (модель машинного обучения).
- Если всё ок — предсказывает класс изображения по пути image_path.
- Сохраняет результат в self.prediction и возвращает его.
- Если ошибка — возвращает -1.
Пример:
Представь, что у тебя есть модель, которая определяет, кошка на фото или собака. Ты передаёшь путь к фото, а функция возвращает 0 (кошка) или 1 (собака). Если модель не загрузилась — вернётся -1.
use tch::{nn, Device, vision::{resnet, image}};
Что это?
Это импорт библиотек и модулей, которые нужны для работы с нейросетями и изображениями.
- tch — это Rust-обёртка над PyTorch (популярная библиотека для машинного обучения).
- nn — модуль для работы с нейронными сетями.
- Device — позволяет выбрать, где будет работать модель: на CPU или GPU.
- vision::{resnet, image} — модули для работы с изображениями и готовыми моделями (например, ResNet).
Пример:
Представь, что ты хочешь испечь торт. Тебе нужны мука, яйца, духовка. Здесь ты "импортируешь" всё необходимое для "выпечки" нейросети.
pub struct ImageClassifier { ... }
Что это?
Это описание структуры (класса) ImageClassifier, которая будет классифицировать изображения.
- model: resnet::ResNet — поле, где хранится модель ResNet (нейросеть для классификации изображений).
- device: Device — поле, где хранится информация, на каком устройстве работает модель (CPU/GPU).
Пример:
Структура — как чертеж машины. Здесь ты описываешь, что у машины есть двигатель (model) и место, где она ездит (device).
impl ImageClassifier { ... }
Что это?
Это реализация методов (функций) для структуры ImageClassifier.
- pub fn new() -> Result<Self> { ... } — конструктор, который создаёт новый экземпляр ImageClassifier.
- let vs = nn::VarStore::new(Device::Cpu); — создаёт хранилище для переменных модели на CPU.
- let model = resnet::resnet18(&vs.root(), 1000, true)?; — загружает готовую модель ResNet18, обученную на 1000 классов (ImageNet).
Пример:
Это как инструкция по сборке машины. Ты говоришь: "Чтобы сделать машину, возьми двигатель (модель) и поставь её на дорогу (CPU)".
pub fn predict(&self, image_path: &str) -> i64 { ... }
Что это?
Метод, который предсказывает класс изображения.
- let img = image::load(image_path).expect("..."); — загружает изображение по пути.
- img.to_device(self.device).apply(&self.model); — передаёт изображение в модель для анализа.
- let output = img.argmax(1, false); — находит наиболее вероятный класс (что на изображении).
- output.int64_value(&[]) — возвращает номер класса (например, 281 — это "кошка").
Пример:
Ты даёшь машине фотографию и спрашиваешь: "Что это?" Машина отвечает: "Это кошка (класс 281)".
pub fn new_classifier() -> Result<ImageClassifier> { ... }
Что это?
Просто функция, которая вызывает конструктор ImageClassifier::new() и возвращает новый классификатор.
Пример:
Это как кнопка "Создать машину" на заводе.
use tch::{nn, Device, vision::{resnet, image}};
Что это?
Это подключение библиотек (наборов готовых инструментов) для работы с нейросетями, изображениями и устройствами (например, CPU/GPU).
Пример:
Как если бы вы сказали: "Я хочу использовать кухонный комбайн (nn), плиту (Device), рецепты для выпечки (resnet) и продукты (image)".
pub fn new() -> Result<Self>
Что это?
Это функция, которая создаёт новый объект ImageClassifier. Она загружает модель ResNet18 (уже обученную на тысячах изображений) и говорит: "Я буду работать на процессоре".
Пример:
Как если бы вы сказали: "Собери мне новый велосипед (ImageClassifier) по инструкции (ResNet18) и поставь его в гараж (CPU)".
pub fn predict(&self, image_path: &str) -> i64
Что это?
Это функция, которая берёт путь к изображению, загружает его, пропускает через модель и возвращает номер класса (например, "это кот" — класс 281).
Пример:
Вы даёте классификатору фотографию кота, а он отвечает: "Это класс 281" (то есть "кот").
Arc<ImageClassifier> и ClassifierHandle
Что это?
Arc — это обёртка, которая позволяет нескольким частям программы безопасно пользоваться одним и тем же объектом. ClassifierHandle — это "ручка" для передачи классификатора в другие языки (например, C++).
Пример:
Как если бы у вас была одна книга, но несколько человек могли бы её читать одновременно, не мешая друг другу.
#[cxx::bridge] и FFI
Что это?
Это мост между Rust и C++. Он позволяет вызывать функции из Rust в C++ и наоборот.
Пример:
Как если бы у вас был переводчик между русским и английским: вы говорите по-русски (Rust), а он передаёт ваши слова англоговорящему (C++).
create_classifier, predict_image, destroy_classifier
Что это?
Это функции для работы с классификатором из C++:
- create_classifier — создаёт новый классификатор.
- predict_image — предсказывает класс изображения.
- destroy_classifier — удаляет классификатор, когда он больше не нужен.
Пример:
Как если бы у вас был пульт от телевизора:
- "Включи" — create_classifier
- "Покажи канал 5" — predict_image
- "Выключи" — destroy_classifier