
В этой статье я расскажу, как я для себя понимаю архитектуру десктоп-приложений — и почему в какой-то момент Rust перестал быть «языком, который я когда-нибудь посмотрю», и стал практичным решением.
Сразу оговорюсь: это не попытка доказать, что «Electron — зло», а Rust — «спаситель». Это скорее дневник архитектора, который хотел собрать удобный продукт и по дороге несколько раз наступил на грабли. Некоторые грабли были с подогревом.
Немного контекста: как мы сюда пришли
Многие разработчики действительно начинали с фронтенда: HTML, CSS, JavaScript. Затем появились инструменты, которые последовательно снижали порог входа и повышали скорость разработки: jQuery, позже React/Vue/Angular.
Бэкенд тоже развивался: PHP эволюционировал (включая важный шаг к PHP 7), активно укрепились Python (Django) и Node.js. Для многих это упростило путь к «фуллстеку»: один язык, один стек, одна команда — и ощущение, что мир наконец-то стал понятнее.
Параллельно в мире десктопа долгое время доминировали нативные инструменты: Visual Studio для Windows, Xcode для macOS, Delphi (с наследием Turbo Pascal) и тому подобные . Плюс нативного подхода — богатые SDK и максимальная интеграция с ОС. Минус — кроссплатформенность. А когда Linux (во многом благодаря Ubuntu) стал заметнее как пользовательская платформа, вопрос «как делать одно приложение для всех» перестал быть теоретическим.
И вот на сцену выходят кроссплатформенные фреймворки: идея простая — пишем один раз, запускаем везде.
Electron: идеальный план… до первого серьёзного «но»
Electron выглядит как мечта: HTML/CSS/JS, знакомый стек, кроссплатформенность, быстрый time-to-market. Под капотом — Chromium + Node.js, отдельный процесс, доступ к системным API. На бумаге всё идеально.
Проблемы начинаются, когда приложение перестаёт быть «простой формой с таблицей» и в нём появляется:
нетривиальная бизнес-логика,
вычисления,
работа с файлами/криптографией/секретами,
требования по безопасности.
Да, Electron можно ускорять: выносить тяжёлое в нативные модули Node.js (C/C++), использовать worker-ы, оптимизировать рендеринг. Но дальше я упёрся не только в производительность.
Что у меня было на старте
Отличная идея продукта.
Проверенный стек.
Возможность расширения под тяжёлые задачи.
На MVP решение напрашивалось само: Electron. Быстро, понятно, «зато сделаем и проверим гипотезу».
И всё было неплохо ровно до момента, пока я не задумался о двух вещах:
безопасность данных (и доступ к ним из пользовательской среды),
защита бизнес-логики (хотя бы до разумного уровня).
И вот тут начались эксперименты.
Попытка №1: вынести логику в бэкенд и общаться по WebSocket
Классика: поднимаем локальный бэкенд, Electron становится оболочкой, общение — по WebSocket.
Технически это работает. Но по ощущениям — «костыль с гарантией». Почему:
канал связи по сути сетевой, даже если слушаем localhost;
придётся очень аккуратно думать про авторизацию, происхождение запросов и угрозы от локальных процессов;
защита «всё равно условная»: любой процесс в ОС может попытаться постучаться (да, можно усложнять жизнь атакующему, но магии тут нет).
Мы добавили защиту, стало лучше. Но внутреннее ощущение правильности решения так и не появилось.
Попытка №2: UDS / pipes вместо TCP + gRPC
Следующая идея: убрать TCP как класс и перейти на Unix Domain Sockets / named pipes. Логика понятная: локальный IPC, меньше «сетевых запахов», проще ограничивать доступ.
На практике я столкнулся с неприятными вещами:
потоковая передача работала нестабильно в моих сценариях;
ошибки диагностировались тяжело (особенно когда у тебя несколько процессов, разные рантаймы и «оно просто упало»);
отладка превращалась в отдельный вид спорта.
И снова: да, это можно довести до production, но цена «доведения» начала расти быстрее, чем ценность MVP.
Попытка №3: нативный аддон на C++ — надёжно, но больно
Затем я пошёл туда, куда Electron сам подталкивает в «серьёзных» случаях: native addon на C/C++.
Плюсы:
максимальная производительность;
полный контроль над взаимодействием с системой.
Минусы (лично мои):
мои знания C/C++ на момент эксперимента были недостаточно уверенными для быстрой и безопасной разработки;
любое изменение требовало продолжительного тестирования;
ловля проблем уровня race condition и SIGKILL быстро превращается в ежедневную рутину.
Попытка №4: Go как компромисс (и как источник новых граблей)
Логика была простой: если C/C++ тяжеловато, возьмём то, что хорошо знаю и даёт скорость разработки — Go.
Тем более локальный бэкенд на Go у меня уже был: WebSocket и UDS-эксперименты не прошли зря.
В целом решение получилось рабочим. Но:
любые изменения приходилось долго и тщательно тестировать;
периодически вылезали race condition, падения, загадочные SIGKILL (и да, часть из этого — моя дисциплина и архитектурные решения, а не «плохой Go»);
сборка стала тяжелеть.
И финальный «контрольный выстрел»:
Первый релиз — около 500 МБ. Из них порядка 200 МБ — сам Electron (в зависимости от платформы/сборки цифры могли менять, но порядок оставался таким).
Для современного мира это не катастрофа, но для довольно простого приложения управления данными — неприятный осадок оставляет. Особенно если ты хочешь, чтобы продукт скачивали без ощу��ения, что вместе с ним ставится небольшой браузер. Хотя… ладно, не будем делать вид, что это не так.
Поворотный момент: Rust и Tauri
В какой-то момент я понял, что упёрся не в «как заставить Electron работать», а в архитектурную модель, которая постоянно требует обходных манёвров вокруг безопасности, ресурсов и поставки.
Я вспомнил про Rust — язык, до которого «никак не доходили руки». И подумал: если не сейчас, то когда.
И тут совпало сразу несколько вещей:
Rust даёт сильную модель безопасности памяти и дисциплинирует архитектуру.
Tauri позволяет оставить фронтенд (web-UI), но использовать системный WebView вместо встраивания полного Chromium.
Коммуникация между UI и ядром — внутри приложения, без ощущения «у меня тут локальный сервер торчит наружу».
План был быстрый:
фронт остаётся,
ядро на Rust,
старую Go-библиотеку сначала подключаем как subprocess (чтобы не переписывать всё сразу).
Да, всё завелось. Да, были ошибки. Но результат по ощущениям был из другой лиги:
скорость работы стала заметно лучше (рендеринг 120 кадров в секунду даже дает неимоверные впечатления плавности),
размер дистрибутива — около 12 МБ (опять же, зависит от платформы и сборки, но порядок впечатляет).
После этого мы всё-таки переписали Go на Rust, чтобы не разводить «зоопарк» технологий и убрать лишний межпроцессный слой. Сейчас приложение готовится к публичному релизу.
Что я вынес из этой истории
Electron отлично подходит для MVP, когда важнее скорость, чем идеальная инженерия. Но надо честно понимать цену: память, размер, безопасность и сложность при росте требований.
Локальный бэкенд + WebSocket — быстрый путь, но без правильной модели доверия это вечный источник «а точно никто не постучится?».
UDS/pipes + gRPC звучит красиво, но на практике может стать сложным в диагностике и сопровождении, особенно если вы строите систему из нескольких процессов.
Нативные аддоны — мощно, но требуют компетенций и времени на качество.
Rust + Tauri — хороший вариант, когда вам нужен web-UI, но вы не хотите тащить за собой целый Chromium и хотите более «собранную» архитектуру приложения.
Главная мысль простая: ищите, пробуйте, сравнивайте и не бойтесь выбрасывать решения, если они перестали отвечать требованиям. Исследовательский подход окупается — иногда даже размером дистрибутива.
Подписывайтесь на канал для получения информации от ИТ архитектора с более чем 20 летним стажем.