Why I built dumbsms in Rust and iced, not Swift
I'm on a Mac, so Swift was the obvious choice. I picked Rust and iced instead — because the people who carry a flip phone are just as likely to be on Windows or Linux, and I wanted one small, fast, native app instead of a browser in a trench coat.
I use dumbsms on a Mac. By every default, that means I should have written it in Swift — Xcode’s right there, SwiftUI is genuinely nice, and Apple’s tooling would have handed me a polished app for the least effort. So when I tell people I built it in Rust with iced instead, the fair question is: why make it harder on yourself?
Three things. One is about who else gets to use it, another is about what kind of app I wanted it to be. Also a robot built it!
The people who flip phones aren’t all on Macs
The whole point of dumbsms is for people who want a dumber phone but still need to type the occasional text on a real keyboard. That’s a particular kind of person — and they are absolutely not all Mac users.
A lot of them are exactly the type who run Linux on an old ThinkPad on principle, or who live in Windows for work. If I’d written this in Swift, I’d have shipped a beautiful app for my setup and locked out a big slice of the people most likely to want it. Swift technically runs elsewhere now, but a SwiftUI app is a Mac app — the moment I leaned on Apple’s UI stack, the door to Windows and Linux closes.
Rust doesn’t have that problem. The same codebase compiles to a native app on macOS, Windows, and Linux. I happen to develop and test on a Mac, but nothing about the architecture assumes one. That felt like the right trade for a tool whose entire audience is “people who opt out of the default.”
I wanted a native app, not a browser in a trench coat
The other half is taste. dumbsms is a small thing that should feel small: open it, it talks to the flip phone over a cable, you type, you’re done. I didn’t want it to boot slowly, eat half a gig of RAM idling, or be a web page wearing a desktop costume.
So the popular cross-platform routes were out on purpose:
- No Electron / Tauri / web UI. I went to a dumb phone to put less browser between me and my life. Shipping a Chromium instance to do that would be a funny kind of irony. Even the lighter web-shell options still mean my “native” app is really HTML and JavaScript underneath.
- No Slint, no big GUI frameworks. Slint is genuinely good Rust UI tech — this isn’t a knock on it. I just didn’t want a separate markup language and DSL for a single-window app. I wanted the UI to be plain Rust.
That left iced. It’s an all-Rust GUI library with an Elm-style architecture —
your state, the messages that change it, and a view function that draws it. No
HTML, no embedded browser, no .ui files. The app is one Rust binary that draws its
own pixels, starts instantly, and stays tiny in memory. The dependency line is almost
boring, which is the point:
iced = { version = "0.13", features = ["image", "tokio", "advanced"] }
“Simple and performant” was the actual spec
If I had to compress the decision to one sentence: I wanted the app to be as quiet and low-fuss as the phone it serves.
- Simple meant one language end to end. The core logic that talks to the phone is
already Rust, so building the GUI in Rust means there’s no second runtime, no
bridge between a JavaScript front end and a native back end, no marshalling layer to
babysit. It’s one
cargo build. - Performant meant native and light. A flip-phone companion that spins a fan or takes three seconds to open would defeat its own purpose. iced gives me a real native window with effectively no startup tax.
- Portable meant not betting the whole thing on the OS I happen to use.
Swift would have been faster for me, today, on this Mac. Rust and iced are better for dumbsms — a small, fast, native app that the Linux-on-a-ThinkPad crowd and the Windows-at-work crowd can run too, without me shipping a browser to do it.
The phone stays dumb. The app stays small. And it runs wherever the kind of person who flips happens to be.