Application Development with Rust for Enterprise Systems Link to heading

Building enterprise applications requires a diverse set of tools and approaches depending on the target platform and user interface requirements. In this post, we’ll explore how Rust can be used to develop web, CLI, desktop, and mobile applications for enterprise environments.

Web Application Development Link to heading

Web applications are a common requirement for enterprise systems. Rust offers several approaches to building web frontends, from server-side rendering to WebAssembly.

Server-Side Rendering with Templates Link to heading

For traditional server-rendered applications, Rust provides several templating engines:

Tera Templates Link to heading

Tera is inspired by Jinja2 and Django templates, making it familiar to developers from Python backgrounds:

use axum::{
    extract::State,
    response::Html,
    routing::get,
    Router,
};
use serde::Serialize;
use std::sync::Arc;
use tera::{Context, Tera};

#[derive(Serialize)]
struct User {
    id: u64,
    name: String,
    email: String,
}

struct AppState {
    tera: Tera,
    users: Vec<User>,
}

async fn users_page(State(state): State<Arc<AppState>>) -> Html<String> {
    let mut context = Context::new();
    context.insert("users", &state.users);
    context.insert("title", "User List");
    
    let rendered = state.tera.render("users.html", &context)
        .expect("Failed to render template");
    
    Html(rendered)
}

#[tokio::main]
async fn main() {
    // Initialize Tera
    let mut tera = Tera::default();
    tera.add_raw_templates(vec![
        ("base.html", r#"
        <!DOCTYPE html>
        <html>
        <head>
            <title>{{ title }}</title>
        </head>
        <body>
            <header>
                <h1>{{ title }}</h1>
            </header>
            <main>
                {% block content %}{% endblock content %}
            </main>
            <footer>
                <p>© 2025 Enterprise App</p>
            </footer>
        </body>
        </html>
        "#),
        ("users.html", r#"
        {% extends "base.html" %}
        
        {% block content %}
            <table>
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>Name</th>
                        <th>Email</th>
                    </tr>
                </thead>
                <tbody>
                    {% for user in users %}
                    <tr>
                        <td>{{ user.id }}</td>
                        <td>{{ user.name }}</td>
                        <td>{{ user.email }}</td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        {% endblock content %}
        "#),
    ]).expect("Failed to add templates");
    
    // Sample data
    let users = vec![
        User { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() },
        User { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string() },
    ];
    
    // Create app state
    let state = Arc::new(AppState { tera, users });
    
    // Build our application
    let app = Router::new()
        .route("/users", get(users_page))
        .with_state(state);
    
    // Run our server
    let addr = "0.0.0.0:3000";
    println!("Server running on http://{}", addr);
    axum::Server::bind(&addr.parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

Askama Templates Link to heading

Askama compiles templates to Rust code, providing type safety and performance:

use askama::Template;
use axum::{
    response::{Html, IntoResponse},
    routing::get,
    Router,
};

#[derive(Template)]
#[template(path = "users.html")]
struct UsersTemplate {
    title: String,
    users: Vec<User>,
}

struct User {
    id: u64,
    name: String,
    email: String,
}

async fn users_page() -> impl IntoResponse {
    let users = vec![
        User { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() },
        User { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string() },
    ];
    
    let template = UsersTemplate {
        title: "User List".to_string(),
        users,
    };
    
    Html(template.render().unwrap())
}

#[tokio::main]
async fn main() {
    // Build our application
    let app = Router::new()
        .route("/users", get(users_page));
    
    // Run our server
    let addr = "0.0.0.0:3000";
    println!("Server running on http://{}", addr);
    axum::Server::bind(&addr.parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

WebAssembly Frontend Development Link to heading

For more interactive web applications, Rust can compile to WebAssembly (Wasm), enabling rich client-side experiences:

Yew Framework Link to heading

Yew is a modern Rust framework for creating multi-threaded frontend web apps with WebAssembly:

use yew::prelude::*;

#[derive(Clone, PartialEq, Properties)]
struct UserProps {
    id: u64,
    name: String,
    email: String,
}

#[function_component(UserRow)]
fn user_row(props: &UserProps) -> Html {
    html! {
        <tr>
            <td>{ props.id }</td>
            <td>{ &props.name }</td>
            <td>{ &props.email }</td>
        </tr>
    }
}

#[function_component(UserList)]
fn user_list() -> Html {
    let users = vec![
        UserProps { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() },
        UserProps { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string() },
    ];
    
    html! {
        <div>
            <h1>{ "User List" }</h1>
            <table>
                <thead>
                    <tr>
                        <th>{ "ID" }</th>
                        <th>{ "Name" }</th>
                        <th>{ "Email" }</th>
                    </tr>
                </thead>
                <tbody>
                    { for users.iter().map(|user| html! { <UserRow ..user.clone() /> }) }
                </tbody>
            </table>
        </div>
    }
}

#[function_component(App)]
fn app() -> Html {
    html! {
        <div>
            <header>
                <h1>{ "Enterprise App" }</h1>
            </header>
            <main>
                <UserList />
            </main>
            <footer>
                <p>{ "© 2025 Enterprise App" }</p>
            </footer>
        </div>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}

Dioxus Framework Link to heading

Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces:

use dioxus::prelude::*;

#[derive(Props, PartialEq)]
struct UserProps {
    id: u64,
    name: String,
    email: String,
}

fn UserRow(cx: Scope<UserProps>) -> Element {
    cx.render(rsx! {
        tr {
            td { "{cx.props.id}" }
            td { "{cx.props.name}" }
            td { "{cx.props.email}" }
        }
    })
}

fn UserList(cx: Scope) -> Element {
    let users = vec![
        UserProps { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() },
        UserProps { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string() },
    ];
    
    cx.render(rsx! {
        div {
            h1 { "User List" }
            table {
                thead {
                    tr {
                        th { "ID" }
                        th { "Name" }
                        th { "Email" }
                    }
                }
                tbody {
                    for user in users {
                        UserRow {
                            id: user.id,
                            name: user.name,
                            email: user.email,
                        }
                    }
                }
            }
        }
    })
}

fn App(cx: Scope) -> Element {
    cx.render(rsx! {
        div {
            header {
                h1 { "Enterprise App" }
            }
            main {
                UserList {}
            }
            footer {
                p { "© 2025 Enterprise App" }
            }
        }
    })
}

fn main() {
    dioxus_web::launch(App);
}

Full-Stack Rust Applications Link to heading

For enterprise applications, a full-stack Rust approach can provide consistency and type safety across the entire application:

// Shared types between frontend and backend
#[derive(Serialize, Deserialize, Clone, PartialEq)]
struct User {
    id: u64,
    name: String,
    email: String,
}

// Backend API endpoint
async fn get_users() -> Json<Vec<User>> {
    // Fetch from database
    Json(vec![
        User { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() },
        User { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string() },
    ])
}

// Frontend component using the same User type
#[function_component(UserList)]
fn user_list() -> Html {
    let users = use_state(|| Vec::<User>::new());
    
    {
        let users = users.clone();
        use_effect_with_deps(move |_| {
            wasm_bindgen_futures::spawn_local(async move {
                let fetched_users: Vec<User> = Request::get("/api/users")
                    .send()
                    .await
                    .unwrap()
                    .json()
                    .await
                    .unwrap();
                users.set(fetched_users);
            });
            || ()
        }, ());
    }
    
    html! {
        <div>
            <h1>{ "User List" }</h1>
            <table>
                <thead>
                    <tr>
                        <th>{ "ID" }</th>
                        <th>{ "Name" }</th>
                        <th>{ "Email" }</th>
                    </tr>
                </thead>
                <tbody>
                    { for users.iter().map(|user| html! {
                        <tr>
                            <td>{ user.id }</td>
                            <td>{ &user.name }</td>
                            <td>{ &user.email }</td>
                        </tr>
                    }) }
                </tbody>
            </table>
        </div>
    }
}

CLI Application Development Link to heading

Command-line interfaces (CLIs) are essential for enterprise applications, especially for administrative tasks, batch processing, and automation. Rust excels at building robust CLI tools.

Building CLI Applications with Clap Link to heading

Clap is the most popular command-line argument parser for Rust:

use clap::{Parser, Subcommand};
use std::path::PathBuf;

#[derive(Parser)]
#[command(name = "enterprise-cli")]
#[command(about = "Enterprise CLI tool", long_about = None)]
struct Cli {
    /// Optional name to operate on
    #[arg(short, long)]
    name: Option<String>,

    /// Sets a custom config file
    #[arg(short, long, value_name = "FILE")]
    config: Option<PathBuf>,

    /// Turn debugging information on
    #[arg(short, long, action = clap::ArgAction::Count)]
    debug: u8,

    #[command(subcommand)]
    command: Option<Commands>,
}

#[derive(Subcommand)]
enum Commands {
    /// Add a new user
    Add {
        /// User name
        #[arg(short, long)]
        name: String,
        
        /// User email
        #[arg(short, long)]
        email: String,
    },
    /// List all users
    List {
        /// Filter by role
        #[arg(short, long)]
        role: Option<String>,
        
        /// Limit the number of results
        #[arg(short, long, default_value_t = 10)]
        limit: usize,
    },
}

fn main() {
    let cli = Cli::parse();

    // You can check the value provided by positional arguments, or option arguments
    if let Some(name) = cli.name.as_deref() {
        println!("Value for name: {name}");
    }

    if let Some(config_path) = cli.config.as_deref() {
        println!("Value for config: {}", config_path.display());
    }

    // You can see how many times a particular flag or argument occurred
    // Note, only flags can have multiple occurrences
    match cli.debug {
        0 => println!("Debug mode is off"),
        1 => println!("Debug mode is kind of on"),
        2 => println!("Debug mode is on"),
        _ => println!("Don't be crazy"),
    }

    // You can check for the existence of subcommands, and if found use their
    // matches just as you would the top level cmd
    match &cli.command {
        Some(Commands::Add { name, email }) => {
            println!("Adding user {} with email {}", name, email);
            // Add user to database
        }
        Some(Commands::List { role, limit }) => {
            println!("Listing users");
            if let Some(role) = role {
                println!("Filtering by role: {}", role);
            }
            println!("Limiting to {} results", limit);
            // List users from database
        }
        None => {}
    }
}

Interactive CLI Applications Link to heading

For more interactive CLI experiences, Rust offers several libraries:

Dialoguer for User Input Link to heading

use dialoguer::{Input, Password, Select, Confirm, MultiSelect};

fn main() {
    // Simple input
    let name: String = Input::new()
        .with_prompt("Enter your name")
        .default("John Doe".into())
        .interact_text()
        .unwrap();
    
    // Password input
    let password = Password::new()
        .with_prompt("Enter your password")
        .with_confirmation("Confirm password", "Passwords don't match")
        .interact()
        .unwrap();
    
    // Selection
    let options = vec!["Option 1", "Option 2", "Option 3"];
    let selection = Select::new()
        .with_prompt("Select an option")
        .default(0)
        .items(&options)
        .interact()
        .unwrap();
    
    // Confirmation
    let confirmed = Confirm::new()
        .with_prompt("Do you want to continue?")
        .default(true)
        .interact()
        .unwrap();
    
    // Multi-select
    let items = vec!["Item 1", "Item 2", "Item 3", "Item 4"];
    let selections = MultiSelect::new()
        .with_prompt("Select multiple items")
        .items(&items)
        .defaults(&[true, false, true, false])
        .interact()
        .unwrap();
    
    println!("Name: {}", name);
    println!("Password: {}", "*".repeat(password.len()));
    println!("Selected option: {}", options[selection]);
    println!("Confirmed: {}", confirmed);
    println!("Selected items: {:?}", selections.iter().map(|&i| items[i]).collect::<Vec<_>>());
}

Indicatif for Progress Bars Link to heading

use indicatif::{ProgressBar, ProgressStyle};
use std::time::Duration;

fn main() {
    // Simple progress bar
    let pb = ProgressBar::new(100);
    pb.set_style(ProgressStyle::default_bar()
        .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})")
        .unwrap()
        .progress_chars("#>-"));
    
    for i in 0..100 {
        pb.inc(1);
        std::thread::sleep(Duration::from_millis(50));
    }
    pb.finish_with_message("done");
    
    // Multi-progress bar for parallel tasks
    let mp = indicatif::MultiProgress::new();
    
    let style = ProgressStyle::default_bar()
        .template("{prefix:.bold.dim} {spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})")
        .unwrap()
        .progress_chars("#>-");
    
    let pb1 = mp.add(ProgressBar::new(50));
    pb1.set_style(style.clone());
    pb1.set_prefix("Task 1");
    
    let pb2 = mp.add(ProgressBar::new(100));
    pb2.set_style(style.clone());
    pb2.set_prefix("Task 2");
    
    let pb3 = mp.add(ProgressBar::new(75));
    pb3.set_style(style);
    pb3.set_prefix("Task 3");
    
    std::thread::spawn(move || {
        for i in 0..50 {
            pb1.inc(1);
            std::thread::sleep(Duration::from_millis(100));
        }
        pb1.finish_with_message("done");
    });
    
    std::thread::spawn(move || {
        for i in 0..100 {
            pb2.inc(1);
            std::thread::sleep(Duration::from_millis(50));
        }
        pb2.finish_with_message("done");
    });
    
    for i in 0..75 {
        pb3.inc(1);
        std::thread::sleep(Duration::from_millis(75));
    }
    pb3.finish_with_message("done");
    
    mp.join().unwrap();
}

Building Robust CLI Applications Link to heading

Enterprise CLI applications require additional considerations:

Error Handling Link to heading

use thiserror::Error;
use clap::Parser;
use std::path::PathBuf;
use std::fs;

#[derive(Error, Debug)]
enum CliError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    
    #[error("Config error: {0}")]
    Config(String),
    
    #[error("Validation error: {0}")]
    Validation(String),
}

#[derive(Parser)]
struct Cli {
    #[arg(short, long)]
    config: PathBuf,
}

fn run() -> Result<(), CliError> {
    let cli = Cli::parse();
    
    // Check if config file exists
    if !cli.config.exists() {
        return Err(CliError::Config(format!("Config file not found: {}", cli.config.display())));
    }
    
    // Read config file
    let content = fs::read_to_string(&cli.config)?;
    
    // Validate config
    if content.trim().is_empty() {
        return Err(CliError::Validation("Config file is empty".to_string()));
    }
    
    // Process config
    println!("Config loaded successfully");
    
    Ok(())
}

fn main() {
    if let Err(e) = run() {
        eprintln!("Error: {}", e);
        std::process::exit(1);
    }
}

Configuration Management Link to heading

use config::{Config, ConfigError, File};
use serde::Deserialize;
use std::path::Path;

#[derive(Debug, Deserialize)]
struct Database {
    url: String,
    username: String,
    password: String,
}

#[derive(Debug, Deserialize)]
struct Server {
    host: String,
    port: u16,
}

#[derive(Debug, Deserialize)]
struct Settings {
    debug: bool,
    database: Database,
    server: Server,
}

fn load_config<P: AsRef<Path>>(config_path: P) -> Result<Settings, ConfigError> {
    let config = Config::builder()
        // Start with default values
        .set_default("debug", false)?
        .set_default("server.host", "127.0.0.1")?
        .set_default("server.port", 8080)?
        // Add in settings from the config file
        .add_source(File::from(config_path.as_ref()))
        // Add in settings from environment variables (with a prefix of APP)
        // E.g., `APP_DEBUG=1 ./target/app` would set the `debug` key
        .add_source(config::Environment::with_prefix("APP").separator("_"))
        .build()?;
    
    // Deserialize the configuration into our Settings struct
    config.try_deserialize()
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let settings = load_config("config.toml")?;
    
    println!("Debug mode: {}", settings.debug);
    println!("Database URL: {}", settings.database.url);
    println!("Server: {}:{}", settings.server.host, settings.server.port);
    
    Ok(())
}

Desktop Application Development Link to heading

Enterprise applications often require desktop interfaces for internal tools and administrative applications. Rust offers several frameworks for building cross-platform desktop applications.

Tauri Link to heading

Tauri allows you to build desktop applications with web technologies while leveraging Rust for the backend:

// src-tauri/src/main.rs
#![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
)]

use serde::{Deserialize, Serialize};
use tauri::{command, State};
use std::sync::Mutex;

#[derive(Serialize, Deserialize, Clone)]
struct User {
    id: u64,
    name: String,
    email: String,
}

struct AppState {
    users: Mutex<Vec<User>>,
}

#[command]
fn get_users(state: State<AppState>) -> Vec<User> {
    state.users.lock().unwrap().clone()
}

#[command]
fn add_user(state: State<AppState>, name: String, email: String) -> User {
    let mut users = state.users.lock().unwrap();
    let id = users.len() as u64 + 1;
    
    let user = User {
        id,
        name,
        email,
    };
    
    users.push(user.clone());
    user
}

fn main() {
    let app_state = AppState {
        users: Mutex::new(vec![
            User { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() },
            User { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string() },
        ]),
    };
    
    tauri::Builder::default()
        .manage(app_state)
        .invoke_handler(tauri::generate_handler![get_users, add_user])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Frontend (using React):

import { useState, useEffect } from 'react';
import { invoke } from '@tauri-apps/api/tauri';

function App() {
  const [users, setUsers] = useState([]);
  const [newUser, setNewUser] = useState({ name: '', email: '' });

  useEffect(() => {
    loadUsers();
  }, []);

  async function loadUsers() {
    const users = await invoke('get_users');
    setUsers(users);
  }

  async function handleAddUser(e) {
    e.preventDefault();
    await invoke('add_user', { 
      name: newUser.name, 
      email: newUser.email 
    });
    setNewUser({ name: '', email: '' });
    loadUsers();
  }

  return (
    <div className="container">
      <h1>User Management</h1>
      
      <form onSubmit={handleAddUser}>
        <input
          type="text"
          placeholder="Name"
          value={newUser.name}
          onChange={(e) => setNewUser({ ...newUser, name: e.target.value })}
          required
        />
        <input
          type="email"
          placeholder="Email"
          value={newUser.email}
          onChange={(e) => setNewUser({ ...newUser, email: e.target.value })}
          required
        />
        <button type="submit">Add User</button>
      </form>
      
      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
          </tr>
        </thead>
        <tbody>
          {users.map(user => (
            <tr key={user.id}>
              <td>{user.id}</td>
              <td>{user.name}</td>
              <td>{user.email}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

export default App;

Iced Link to heading

Iced is a cross-platform GUI library focused on simplicity and type safety:

use iced::{
    button, scrollable, text_input, Button, Column, Container, Element, Length, Row, Scrollable,
    Settings, Text, TextInput,
};

#[derive(Default)]
struct UserForm {
    name_input: text_input::State,
    name_value: String,
    email_input: text_input::State,
    email_value: String,
    add_button: button::State,
}

#[derive(Clone, Debug)]
struct User {
    id: u64,
    name: String,
    email: String,
}

#[derive(Default)]
struct UserList {
    users: Vec<User>,
    scroll: scrollable::State,
}

#[derive(Default)]
struct UserManager {
    user_form: UserForm,
    user_list: UserList,
}

#[derive(Debug, Clone)]
enum Message {
    NameInputChanged(String),
    EmailInputChanged(String),
    AddButtonPressed,
}

impl UserManager {
    fn new() -> Self {
        let mut user_manager = Self::default();
        
        // Add some initial users
        user_manager.user_list.users = vec![
            User { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() },
            User { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string() },
        ];
        
        user_manager
    }
    
    fn update(&mut self, message: Message) {
        match message {
            Message::NameInputChanged(value) => {
                self.user_form.name_value = value;
            }
            Message::EmailInputChanged(value) => {
                self.user_form.email_value = value;
            }
            Message::AddButtonPressed => {
                if !self.user_form.name_value.is_empty() && !self.user_form.email_value.is_empty() {
                    let id = self.user_list.users.len() as u64 + 1;
                    
                    self.user_list.users.push(User {
                        id,
                        name: self.user_form.name_value.clone(),
                        email: self.user_form.email_value.clone(),
                    });
                    
                    self.user_form.name_value.clear();
                    self.user_form.email_value.clear();
                }
            }
        }
    }
    
    fn view(&mut self) -> Element<Message> {
        let title = Text::new("User Management")
            .size(30);
        
        let name_input = TextInput::new(
            &mut self.user_form.name_input,
            "Name",
            &self.user_form.name_value,
            Message::NameInputChanged,
        )
        .padding(10);
        
        let email_input = TextInput::new(
            &mut self.user_form.email_input,
            "Email",
            &self.user_form.email_value,
            Message::EmailInputChanged,
        )
        .padding(10);
        
        let add_button = Button::new(
            &mut self.user_form.add_button,
            Text::new("Add User"),
        )
        .on_press(Message::AddButtonPressed)
        .padding(10);
        
        let form_row = Row::new()
            .spacing(10)
            .push(name_input)
            .push(email_input)
            .push(add_button);
        
        let header_row = Row::new()
            .spacing(10)
            .push(Text::new("ID").width(Length::FillPortion(1)))
            .push(Text::new("Name").width(Length::FillPortion(3)))
            .push(Text::new("Email").width(Length::FillPortion(5)));
        
        let users_list = self.user_list.users.iter().fold(
            Column::new().spacing(10),
            |column, user| {
                column.push(
                    Row::new()
                        .spacing(10)
                        .push(Text::new(user.id.to_string()).width(Length::FillPortion(1)))
                        .push(Text::new(&user.name).width(Length::FillPortion(3)))
                        .push(Text::new(&user.email).width(Length::FillPortion(5)))
                )
            },
        );
        
        let scrollable_users = Scrollable::new(&mut self.user_list.scroll)
            .push(users_list)
            .height(Length::Fill);
        
        let content = Column::new()
            .spacing(20)
            .push(title)
            .push(form_row)
            .push(header_row)
            .push(scrollable_users);
        
        Container::new(content)
            .width(Length::Fill)
            .height(Length::Fill)
            .padding(20)
            .into()
    }
}

fn main() -> iced::Result {
    UserManager::new().run(Settings::default())
}

impl iced::Application for UserManager {
    type Executor = iced::executor::Default;
    type Message = Message;
    type Flags = ();
    
    fn new(_flags: ()) -> (Self, iced::Command<Message>) {
        (Self::new(), iced::Command::none())
    }
    
    fn title(&self) -> String {
        String::from("User Manager - Iced")
    }
    
    fn update(&mut self, message: Message) -> iced::Command<Message> {
        self.update(message);
        iced::Command::none()
    }
    
    fn view(&mut self) -> Element<Message> {
        self.view()
    }
}

Mobile Application Development Link to heading

Enterprise applications increasingly require mobile interfaces. While Rust is not yet a mainstream language for mobile development, there are several approaches to building mobile apps with Rust.

React Native with Rust Native Modules Link to heading

You can use Rust to build native modules for React Native applications:

// rust/src/lib.rs
use jni::JNIEnv;
use jni::objects::{JClass, JString};
use jni::sys::jstring;

#[no_mangle]
pub extern "C" fn Java_com_example_RustModule_processData(
    env: JNIEnv,
    _class: JClass,
    input: JString,
) -> jstring {
    // Convert Java string to Rust string
    let input: String = env
        .get_string(input)
        .expect("Couldn't get Java string!")
        .into();
    
    // Process the data
    let output = format!("Processed: {}", input);
    
    // Convert Rust string back to Java string
    let output = env
        .new_string(output)
        .expect("Couldn't create Java string!");
    
    output.into_inner()
}

JavaScript bridge:

// RustModule.js
import { NativeModules } from 'react-native';

const { RustModule } = NativeModules;

export default {
  processData: (input) => RustModule.processData(input),
};

React Native component:

import React, { useState } from 'react';
import { View, TextInput, Button, Text } from 'react-native';
import RustModule from './RustModule';

const App = () => {
  const [input, setInput] = useState('');
  const [output, setOutput] = useState('');

  const handleProcess = async () => {
    const result = await RustModule.processData(input);
    setOutput(result);
  };

  return (
    <View style={{ padding: 20 }}>
      <TextInput
        value={input}
        onChangeText={setInput}
        placeholder="Enter data to process"
        style={{ borderWidth: 1, padding: 10, marginBottom: 10 }}
      />
      <Button title="Process with Rust" onPress={handleProcess} />
      {output ? (
        <Text style={{ marginTop: 20 }}>Result: {output}</Text>
      ) : null}
    </View>
  );
};

export default App;

Flutter with Rust FFI Link to heading

You can integrate Rust with Flutter using FFI:

// rust/src/lib.rs
use std::os::raw::{c_char};
use std::ffi::{CString, CStr};

#[no_mangle]
pub extern "C" fn process_data(input: *const c_char) -> *mut c_char {
    let c_str = unsafe {
        assert!(!input.is_null());
        CStr::from_ptr(input)
    };
    
    let input_str = c_str.to_str().unwrap();
    let output = format!("Processed: {}", input_str);
    
    let c_string = CString::new(output).unwrap();
    c_string.into_raw()
}

#[no_mangle]
pub extern "C" fn free_string(s: *mut c_char) {
    unsafe {
        if s.is_null() { return }
        CString::from_raw(s)
    };
}

Dart FFI:

// lib/rust_bridge.dart
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';

// Load the dynamic library
final DynamicLibrary rustLib = Platform.isAndroid
    ? DynamicLibrary.open("librust.so")
    : DynamicLibrary.process();

// Define the function signatures
typedef ProcessDataNative = Pointer<Utf8> Function(Pointer<Utf8>);
typedef ProcessData = Pointer<Utf8> Function(Pointer<Utf8>);

typedef FreeStringNative = Void Function(Pointer<Utf8>);
typedef FreeString = void Function(Pointer<Utf8>);

// Create the Dart functions that call into native code
final ProcessData _processData = rustLib
    .lookup<NativeFunction<ProcessDataNative>>('process_data')
    .asFunction();

final FreeString _freeString = rustLib
    .lookup<NativeFunction<FreeStringNative>>('free_string')
    .asFunction();

// Wrapper function to handle memory management
String processData(String input) {
  final inputPointer = input.toNativeUtf8();
  final resultPointer = _processData(inputPointer);
  
  final result = resultPointer.toDartString();
  
  // Free the memory
  malloc.free(inputPointer);
  _freeString(resultPointer);
  
  return result;
}

Flutter widget:

// lib/main.dart
import 'package:flutter/material.dart';
import 'rust_bridge.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Rust FFI Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final TextEditingController _controller = TextEditingController();
  String _output = '';

  void _processData() {
    final input = _controller.text;
    if (input.isNotEmpty) {
      final result = processData(input);
      setState(() {
        _output = result;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Rust FFI Demo'),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            TextField(
              controller: _controller,
              decoration: InputDecoration(
                labelText: 'Enter data to process',
                border: OutlineInputBorder(),
              ),
            ),
            SizedBox(height: 16),
            ElevatedButton(
              onPressed: _processData,
              child: Text('Process with Rust'),
            ),
            SizedBox(height: 16),
            if (_output.isNotEmpty)
              Text(
                'Result: $_output',
                style: TextStyle(fontSize: 16),
              ),
          ],
        ),
      ),
    );
  }
}

Conclusion Link to heading

Rust provides a rich ecosystem for developing enterprise applications across various platforms:

  1. Web Applications: From server-side rendering with templates to WebAssembly frontends with Yew or Dioxus
  2. CLI Applications: Robust command-line tools with Clap, interactive interfaces with Dialoguer, and progress visualization with Indicatif
  3. Desktop Applications: Cross-platform GUIs with Tauri (web technologies) or Iced (native Rust)
  4. Mobile Applications: Integration with React Native or Flutter through native modules and FFI

When choosing an approach for your enterprise application:

  • Consider your team’s expertise: Web technologies may be more familiar to your team than native GUI frameworks
  • Evaluate performance requirements: Native Rust UIs generally offer better performance than WebView-based approaches
  • Assess cross-platform needs: Some frameworks offer better cross-platform support than others
  • Factor in integration requirements: Enterprise applications often need to integrate with existing systems

In the next post, we’ll explore miscellaneous tools for Rust enterprise applications, focusing on observability, logging, and machine learning integration.

Stay tuned!

This series was conceived of and originally written by Jason Grey the human. I've since used various AI agents to help me write and structure it better, and eventually aim to automate a quarterly updated version of it using and agent which follows my personal process.