Cross-platform applications with Rust 1: Durable and flexible
Cross-platform development without a framework has several advantages, and the Rust programming language is well suited for implementation.
(Image: iX)
- Marcel Koch
Many user interfaces are created based on web technologies. Nevertheless, native applications are still the better approach for many use cases or even have no alternative. Both desktop programs with hardware connections and mobile apps require development for a specific operating system.
Native applications have advantages such as good performance, a native look and feel, and direct access to connected hardware. The disadvantages include specific code bases for each operating system, differences between the native APIs, and cumbersome installation of the software.
The idea of cross-platform frameworks is to counter at least the first two disadvantages. Frameworks are a particularly good choice for quick results with little effort. Dynamic languages enable fast results. If the application is only to run for one or two years, development teams can use such frameworks without hesitation.
However, if a team wants to further develop an application for ten or more years, it must critically scrutinize various aspects: How long will the manufacturer continue to develop the framework? Is the support for Android, iOS and web sufficient? Is the performance of a web view sufficient? Should the application integrate emerging UI technologies or operating systems? How do (automated) tests run to keep quality high?
Longevity through a detached core
Since both cross-platform frameworks and UI technology are subject to rapid change, it makes sense to separate the UI from the permanent part. This idea is known as hexagonal architecture. It allows developers to concentrate on the core functions and define technical requirements as ports that dock onto the core from the outside. In a cross-platform application, ports are created for the user interface and other platform-specific APIs. Examples of APIs are access to the camera or the file system. The core contains the complete business logic and all other parts of the application that are intended to last for a long time.
Selection of the core programming language
The extracted core must run on all targeted platforms. Android, iOS, Windows, macOS, and Raspberry Pi are defined as examples of target platforms. Stability, robustness, longevity, and flexibility are important criteria for a language suitable for the core.
(Image:Â evgeenius/Shutterstock)
On November 10, 2025, the online conference betterCode() Rust will focus on developing industrial applications with Rust. The presentations will be dedicated to asynchronous programming, managing dependencies and high-performance Rust, among other things.
A programming environment is considered stable if it is ready for production and is only subject to a few fundamental changes. Robustness refers to the code written in the programming language. The language should continue to allow the code to be extended, rewritten, and kept understandable for the next ten years.
Videos by heise
To achieve this, the underlying language must also be durable. It should also be as flexible as possible and ideally cover all major operating systems, single-board computers, microcontrollers, and the browser. For this article, the choice fell on the programming language Rust, which can be used for numerous platforms.
Rust for the core
Rust makes it possible to design a maintainable and high-performance core for flexible areas of application with reasonable effort. The language runs on all desktop operating systems, mobile devices, single-board computers, many microcontrollers, and via WebAssembly through compact wasm modules also well in the browser.
The explicit syntax of Rust favors the development of robust software that can be maintained for a long time. The disadvantages are the complexity, the steep learning curve, and the smaller community compared to JavaScript or C++. However, the popularity of Rust is growing, and so is the community. This is also reflected in the migration from C to Rust that has been announced in many places.
The Rust Foundation was founded in 2021 by AWS, Google, Huawei, Microsoft, and Mozilla. Today, many other companies support the Foundation, including Meta, JetBrains, and Threema. The companies are interested in maintaining the language, which was created by Mozilla, for a long time.
Architecture of the core
Once the language has been selected, the question of the architecture for the core arises. The greater the proportion of the application in the core, the greater the advantages of the architectural approach of covering different platforms without further effort. This applies in particular to logic and state, but also to translations for multilingualism.
To better understand the idea of the architecture, a simple app for saving names and email addresses is used as an example below.
The data to be rendered on the user interface must be prepared in the same way as the actions linked to buttons or input fields. When users change the email address and save it by pressing a button, the core should provide interfaces for the current data, the changes, and the confirmation text, which can be connected directly to the user interface code.
(Image:Â Marcel Koch)
Clear separation through MVVM
In the MVVM (Model View ViewModel) design pattern, the model encapsulates the functional data – and, if applicable, the status. The ViewModel contains a format for this data so that it can be integrated into the view (user interface) without further processing. The core provides the ViewModel. Defined in Rust, it can be a simple struct:
ViewModel {
name: String,
email: String
}
UI actions as functional actions
During UI development, native elements such as widgets and controls trigger technical events, for example buttonXYClicked(). These events lead to technical actions such as "change email address". The core provides interfaces for these actions. The actions are designed in such a way that an application can link them directly to the UI elements. This results in actions that are optimal for the UI and at the same time tailored to the business.
In Rust, the list of actions can be an enum:
pub enum Actions {
ChangeName(String),
ChangeEmail(String),
ApplyChanges,
}
Managing the state
The core also manages the state. The UI remains stateless: it sends actions to the core and reacts to the changes in the ViewModel. The state can be a simple struct that is kept in memory:
pub struct ViewModel {
pub name: String,
pub email: String,
}
pub enum Actions {
ChangeName(String),
ChangeEmail(String),
ApplyChanges
}
struct State {
name: String,
email: String
}
impl Default for State {
fn default() -> Self {
State {
name: "".into(),
email: "".into()
}
}
}
pub struct App {
state: State
}
impl App {
pub fn new() -> App {
App {
state: State::default()
}
}
pub fn do_action(&mut self, action: Actions) -> ViewModel {
match action {
Actions::ChangeName(name) => {
self.state.name = name;
}
Actions::ChangeEmail(email) => {
self.state.email = email;
}
Actions::ApplyChanges => {}
}
self.render_view_model()
}
pub fn render_view_model(&self) -> ViewModel {
ViewModel{
name: self.state.name.clone(),
email: self.state.email.clone()
}
}
}
impl Default for App {
fn default() -> Self {
Self::new()
}
}