Most desktop tools a business needs do not need Rust. They walk a folder,
read a spreadsheet, generate a PDF, call an API, write to a database. A
web front end in a plain native window, backed by Python, does all of
that well, builds in seconds and ships without ceremony.
So when we built the Lucky Traders Stock app
on Tauri, a framework that wraps a web front end in a Rust shell, it was
not out of habit. It was because this particular app had needs a simpler
wrapper could not meet. The reasoning generalises, and so do the costs.
What did this app need?
The Stock app runs a fastener godown. Pickers receive deliveries, print a
QR sticker for every bag and scan bags out as orders are packed. The owner
checks figures from wherever they are. Four requirements decided the
stack.
It had to run on Android and Windows from one codebase
Receiving happens at the rack, on a handset. Dispatch and purchasing
happen in the office, on a Windows machine. Two apps meant two codebases,
two sets of bugs and two release cycles for a team that needed one
system. Tauri 2 builds a signed Windows installer and an Android APK from
the same source, and the screens are the same React code on both.
Most desktop wrappers stop at the desktop. That alone narrowed the field.
It had to hear the barcode gun while Tally had focus
This is the requirement nobody writes in a brief, and it decided more
than any other.
A USB barcode gun behaves like a keyboard. It types the code into
whichever window has focus. In the Lucky Traders office, the window with
focus is usually Tally. A wedge scanner aimed at a bag while Tally is in
front sends the barcode into Tally, which is at best useless and at worst
an entry in the books.
The answer is Windows Raw Input, which lets an application register to
receive keystrokes from a device even when its window is not in front,
and tells it which keyboard each keystroke came from. That is an operating
system API, and Rust reaches it directly. The app tells a gun
from a person by speed: a gun sends a character every few milliseconds,
and nobody types that fast. Scans reach the Stock app. Typing stays where
the person is typing.
For guns configured as serial devices, the same Rust layer reads the COM
port directly.
It had to update itself, safely
A fix is only useful once it is on every machine. Tauri's updater
replaces the desktop app in place and refuses any bundle that is not
signed with the release key. Android cannot replace itself, so the app
downloads the new APK and hands it to the system installer, which checks
it against the installed app's signature. Either way, nobody has to visit
the godown with a USB stick.
It had to keep a secret properly
The app authenticates with a device token. On Windows that token lives in
Credential Manager, not in browser storage where any script in the page
could read it. Network requests go out from Rust rather than from the web
view, which also means the server needs no cross-origin exceptions at
all.
What it cost
The choice was not free, and it is worth being exact about the bill.
Build times. A Rust release build is slow, and some configuration
changes invalidate much of the build cache. A small change can mean a long
wait.
Signing keys that cannot be lost. The desktop updater trusts one key.
Lose it and no installed copy can ever be updated again; every machine
needs a manual reinstall. The Android keystore has the same property. Both
belong somewhere other than the machine that builds with them, and
deserve the seriousness that implies.
Silent failure modes. Two configuration mistakes let a release build
succeed while producing no update signatures, and a release without them
is offered to every desktop and then refused by all of them. Neither error
announces itself. The only defence is a checklist that looks for the
signature files after every build.
Android's own quirks. Printing from an Android web view is defined and
does nothing, so stickers print through Android's own print framework via
a small native plugin. The Android build itself runs in a dedicated
container on our build server, because the toolchain is heavy and
particular about where it lives.
None of these is a reason to avoid Tauri. All of them are reasons to
choose it only when the app needs what it gives.
The test we use
Before reaching for a Rust shell, we ask four questions.
- Does it need to run on a phone or tablet as well as a desktop? If
so, a desktop-only wrapper is out.
- Does it need operating system reach a web page cannot have? Input
from a background window, a serial port, a native print path, the
system credential store.
- Does it need to ship as a single self-contained executable onto
machines where installing a runtime is not an option?
- Does it do heavy computation where native speed changes what the
user experiences?
If every answer is no, the parsimonious choice wins: a web front end in a
plain native window, with Python behind it, built in seconds. Disk space
is cheap. Waiting on a release build is not.
If any answer is yes, Rust earns its place. For the Stock app, two of
the four were yes, and the barcode gun behind Tally would have settled it
alone.
The front end was the same HTML, CSS and TypeScript either way. The
choice of shell is a choice about what the app must reach beyond the
screen. If you are weighing that choice for a tool your team will live
in every day, start a conversation and bring your list of what it must
reach.