Найти в Дзене
Один Rust не п...Rust

Rust с Qt6

Для чего нужна данная статья? : Научиться использовать Rust для написания библиотеки и вызывая ее из Qt6 через FFI с использованием ML. Освоить: Найти компромиссы: 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 Зачем Вам это уметь? : Rust и Qt могут взаимодействовать через C++ с помощью FFI: Примеры библиотек: Это более удобный способ, чем чистый FFI. qt-rust позволяет использовать Qt с минимальными усилиями. Примеры: Минусы: Можно использовать QML (Qt Quick) для GUI, а бизнес-логику писать на Rust. Взаимодействие возможно через: Примеры библиотек: Плюсы:
✅ Простота и гибкость.
✅ Rust и Qt могут работать в отдельных процессах.
❌ Чуть больше накладных расходов из-за IPC. Есть проекты, которые генерируют привязки к Qt API автоматически: Минусы: Если у вас есть C++ Qt6-проект, можно напис
Оглавление
GitHub - nicktretyakov/QT

Для чего нужна данная статья? :

Научиться использовать 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

Что делает функция?

  1. Создаёт новый классификатор (модель машинного обучения).
  2. Если всё ок — предсказывает класс изображения по пути image_path.
  3. Сохраняет результат в self.prediction и возвращает его.
  4. Если ошибка — возвращает -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