В этой статье я расскажу, как я для себя понимаю архитектуру десктоп-приложений — и почему в какой-то момент 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. безопасность данных (и доступ к ним из пользовательской среды),

  2. защита бизнес-логики (хотя бы до разумного уровня).

И вот тут начались эксперименты.

Попытка №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, чтобы не разводить «зоопарк» технологий и убрать лишний межпроцессный слой. Сейчас приложение готовится к публичному релизу.

Что я вынес из этой истории

  1. Electron отлично подходит для MVP, когда важнее скорость, чем идеальная инженерия. Но надо честно понимать цену: память, размер, безопасность и сложность при росте требований.

  2. Локальный бэкенд + WebSocket — быстрый путь, но без правильной модели доверия это вечный источник «а точно никто не постучится?».

  3. UDS/pipes + gRPC звучит красиво, но на практике может стать сложным в диагностике и сопровождении, особенно если вы строите систему из нескольких процессов.

  4. Нативные аддоны — мощно, но требуют компетенций и времени на качество.

  5. Rust + Tauri — хороший вариант, когда вам нужен web-UI, но вы не хотите тащить за собой целый Chromium и хотите более «собранную» архитектуру приложения.

Главная мысль простая: ищите, пробуйте, сравнивайте и не бойтесь выбрасывать решения, если они перестали отвечать требованиям. Исследовательский подход окупается — иногда даже размером дистрибутива.


Подписывайтесь на канал для получения информации от ИТ архитектора с более чем 20 летним стажем.

Комментарии (30)


  1. Roman_Cherkasov
    16.12.2025 11:27

    Хм, если уже был бэк на GO, то почему бы не посмотреть в сторону Wails?


    1. askid Автор
      16.12.2025 11:27

      не смотрел в эту сторону и по-любому бек с фронтом нужно было как-то вязать, потому решили все переписать на Rust.


      1. jurikobe
        16.12.2025 11:27

        wails вяжет js биндингами, очень просто


  1. Shado_vi
    16.12.2025 11:27

    есть ещё https://github.com/DioxusLabs/dioxus из интересных проектов.
    изначально был на webview.
    осенью добавили Dioxus Native (on the GPU with WGPU).


  1. Karroplan
    16.12.2025 11:27

    дистриб 12мб? чо-то слабо верю. одна только скопмиленная либа на гошечке будет как раз 12мб размером, т.к. туда весь go-runtime прилинкуется.


    1. Shado_vi
      16.12.2025 11:27

      проект(с vue.js) на tauri v2 у меня весит ~7 мб, но это без плагинов и медиа-контента.


  1. Sabirman
    16.12.2025 11:27

    А rust проще c++ для вас оказался ?


    1. askid Автор
      16.12.2025 11:27

      да, как не странно, к C++ у меня личное отрицание еще с 2010 года, возможно я просто не могу преодолеть этот барьер.


  1. Biga
    16.12.2025 11:27

    А подскажите, пожалуйста, как в этой архитектуре сейчас принято работать с базами данных?

    С чем я лично столкнулся: у базы данных свои типы (string, int'ы разные, дата-время и так далее), у нативного языка - свои, у js - свои. В итоге постепенно пришёл к тому, чтобы подружить js и базу данных напрямую, оставив одну конвертацию типов вместо двух, и всю бизнес-логику перенёс в js. Но такое странное ощущение осталось, потому что js и веб вроде бы должны выполнять чисто интерфейсные функции, а бизнес-логику хотелось бы держать отдельно от интерфейса (даже если она будет написана на том же языке). Но и дублирования хотелось бы как-то избежать (модель данных в js / модель данных в нативном языке / структура данных в DB). Как это нормальные люди делают?


    1. askid Автор
      16.12.2025 11:27

      1. Базы данных бывают разные, не одним SQL как говорится, потому тебе в любом случае потребуется промежуточное звено перед базой данных, но есть плюс большая часть таких адаптеров уже написана для большинства языков. И получается, что созданный тобой картеж данных легко через адаптер трансформируется в форматы базы данных. Можно конечно и на более низком уровне, самому написать адаптер. Также есть ORM всякие (я их правда не уважаю, за избыточность) для SQL сильно упрощает жизнь.

      2. Что касается JS тут мастхев JSON, удобней без заморочек, но если бинарный поток то я думаю ты не будешь его в базу данных складывать, а уже в какое-нибудь S3 хранилище.

      Итого главная структура данных в основном обработчике, дальше адаптеры, плюсы есть например в GO там можно писать структуры json и не париться с конвертацией. Почему так? Почему в основном обработчике - потому что сегодня ты хранишь данные в SQL а завтра решишь что скорости не хватает и решишь переехать на Redis или еще что-то модное.

      Ну чтобы упростить жизнь можно использовать JSON везде и выбрать соответствующую базу данных например mongodb. Да это может быть не так эффективно и будет проигрывать на больших объемах всяким мапера, но за-то жизнь себе ты сильно облегчишь как разработчик.


      1. Biga
        16.12.2025 11:27

        Ну вот да, я пробовал "везде json", но оказалось, что в C++ офигеть как неудобно работать с json. Пробовал и ORM-стайл, но и там свои сложности.
        Интересно даже, отличается ли Rust в этом плане чем-то.


        1. askid Автор
          16.12.2025 11:27

          Тут можно написать ответ который потянет на отдельную статью.

          Но если кратко JSON динамический формат, C++ статический в этом главная проблема, данные то строка, то null, то массив - придется писать кучу проверок, не передал поле - краш, передал больше краш. Были у меня танцы с этим языком не люблю его.

          В Rust с этим проще пишешь структуру, что в нее не помещается просто отбрасывается. Однако нужно заранее подумать где может быть null, где может не быть поля, где нужен enum.

          Итого в этих двух языках для нормально работы с JSON нужен слой нормализации. В C++ это чуть сложнее (для меня лично), в Rust проще.


          1. tenzink
            16.12.2025 11:27

            Оба языка со статической типизацией и, казалось бы, работа с json тут плюс-минус одинаковая. В отличии от python


            1. miklin-ag
              16.12.2025 11:27

              Разница с C++ в том, что в Rust легко получить сериализацию в разные форматы, в том числе в json, с помощью декларативных атрибутов к структуре - serde в помощь


              1. tenzink
                16.12.2025 11:27

                В принципе да, хотя я обычно для подобных задач беру кодогенерацию типа protobuf


        1. domix32
          16.12.2025 11:27

          А как вы с json в cpp работали? rapid_json какой-нибудь?


      1. milano44
        16.12.2025 11:27

        Можно заложить бизнес логику на саги (акторы) ими удобно управлять и легко настраивать, если саг много можно легко настроить runtime runner и runtime processor для live, а на клиента выводить через проекции, если бинарный формат, то через array u8, таури нативно поддерживает, не придётся упаковывать через base64. Клиент лучше не связывать с БД на прямую, иначе может случиться проблема с гонками. По факту сколько всего перепробовал на сегодня самым идеальным вариантом как для мобильных устройств так и для десктоп считаю таури, практически ничего в приложении менять не нужно, клиент работает на вебе, никакого react native, сам использую в основном vite + solid!


  1. HemulGM
    16.12.2025 11:27

    Tauri всё равно будет тащить движок хромиума в <Win10. Плюс ко всему, стабильность тоже будет под вопросом, когда у каждого в системе свой хромиум движок


    1. DieSlogan
      16.12.2025 11:27

      Не тащит, он использует системный компонент MS WebView Edge

      https://v2.tauri.app/start/prerequisites/


      1. andreymal
        16.12.2025 11:27

        Откуда он возьмётся в семёрке-то?

        https://v2.tauri.app/distribute/windows-installer/#offline-installer


        1. m0tral
          16.12.2025 11:27

          А не нужно поддерживать 7ку и прочий хлам

          Таури в первую очередь про мобильные платформы мне кажется все таки


        1. DieSlogan
          16.12.2025 11:27

          Вы ещё про XP вспомните. По-моему на семёрку обновления безопасности уже несколько лет не выходят.


          1. andreymal
            16.12.2025 11:27

            Ну лично мне пофиг, но вы ответили на комментарий, в котором явно указано «<Win10»


    1. askid Автор
      16.12.2025 11:27

      В требованиях к нашему проекту мы не брали версии Windows меньше 10, там слишком много моментов и не только браузер.


      1. HemulGM
        16.12.2025 11:27

        В Линукс у людей тоже может не быть хромиума


        1. andreymal
          16.12.2025 11:27

          А в линуксе вообще webkit, чтобы веб-разработчику веселее было


  1. unicorn_style
    16.12.2025 11:27

    Аргументация слабая. Из разряда: «Rust даже в терминал print делает быстрее»

    Я не увидел аргумента кроме размера приложения почему выбран раст. Говорю это как владелец приложения на React + Electron которое принимает 7-10к rps по UDP и все это успешно успевает отрисовывать с бека на Go, блекджеком и без всяких модулей на C++

    Вообще очень сложно читать статью где автор рассказывает так между делом, что его пугает race conditions «фантомные» в Go, или там null в типизированном языке при обработке json. Оно просто сводит на нет весь вывод в конце статьи потому что очевидно, что автор «не разобрался» с теми проблемами которые были и полез в новое озеро. Даже разбор технологий из разряда: ну мы попробовали, потыкали, там что-то плохо…вы себя хотите убедить в вашем выборе что он единственный верный?

    rust и C++ - хорошие языки, но только порог входа там намного выше чем в Go, особенно в задачах где есть многопоточность. Про race conditions и утечку памяти я молчу, это отдельный навык уметь делать и видеть правильную структуру приложения чтобы потом все плюсы языка не оказались его недостатками.


    1. megahertz
      16.12.2025 11:27

      Присоединюсь к вашему комментарию. К тому-же не понял, зачем был нужен backend. Для связи main-renderer есть пути проще.


    1. askid Автор
      16.12.2025 11:27

      Чтобы расставить точки над и. Вы упускаете момент в вопросах безопасности, разобрать электрон приложение на запчасти и достать от туда бизнес-логику которая имеет наивысшую ценность очень просто, потому потребовалось решение за пределами самого электрона (это что касается почему появился бек), дальше вопрос как поженить ужа с ежом так чтобы не было мучительно больно. Собственно это опыт и попытка описать процесс RND на примере одного решения.

      Сделать MVP на голом электроне я смог и так, там не было каких-либо сложностей и работало все нормально. Вопрос поиска другого варианта решения, как раз этот процесс и описан в статье.

      Что касается Rust и C++ тут могу сказать одно, на текущий момент по Rust я не нашел вариант как заставить его жрать память (ну конечно если прямо этим не озадачиться) в отличии от С++ в котором чуть не уследил и получите, и право Rust проще C++.

      Go в свою очередь отличный язык (и там тоже есть вариант с WebView, но это тема отдельной статьи), с которым я уже давно на короткой ноге.

      Но по факту хотелось прям вау! Чтобы быстро, современно, безопасно, а не старый блекджек со шлюхами.


  1. Lainhard
    16.12.2025 11:27

    Сразу оговорюсь: это не попытка доказать, что «Electron — зло»

    Вообще-то electron действительно зло.