Back to Blog

Using Rust Crates in Tauri: regex, chrono & uuid | Lesson 73

Sandy LaneSandy Lane

Video: Using Rust Crates in Tauri: regex, chrono & uuid | Lesson 73 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

The Rust ecosystem is your standard library. Add a crate, expose a Tauri command, call it from React. The pattern that makes Tauri's backend feel limitless.

One of the underrated wins of Tauri is that your backend is just Rust. Anything on crates.io — regex, chrono, uuid, sqlx, reqwest, image, ring, base64, anything — works in your Tauri app with one cargo add and a few lines of glue.

Today we add three small crates and expose four commands that use them.

What we are building

Four Tauri commands powered by three crates:

  • regex: extract email addresses from a block of text.
  • chrono: get the current time and format dates.
  • uuid: generate a random UUID.

All exposed as #[tauri::command] so the React frontend can call them with invoke.

Adding the crates

cd src-tauri
cargo add regex chrono uuid --features uuid/v4

cargo add writes to your Cargo.toml. After this:

[dependencies]
tauri = { version = "2", features = [] }
serde = { version = "1", features = ["derive"] }
regex = "1"
chrono = "0.4"
uuid = { version = "1", features = ["v4"] }

The --features uuid/v4 enables UUID v4 generation specifically. Many crates have feature flags that pull in optional functionality.

Command 1: extract emails

use regex::Regex;

#[tauri::command]
fn extract_emails(text: String) -> Vec<String> {
  let re = Regex::new(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}").unwrap();
  re.find_iter(&text)
    .map(|m| m.as_str().to_string())
    .collect()
}

Regex::new compiles the pattern. .find_iter returns every match. We collect the matches into a Vec<String>.

For real apps, lazy-static the regex:

use once_cell::sync::Lazy;

static EMAIL_RE: Lazy<Regex> = Lazy::new(|| {
  Regex::new(r"...").unwrap()
});

So the regex is only compiled once, not per call.

Command 2: current time

use chrono::Local;

#[tauri::command]
fn get_current_time() -> String {
  Local::now().format("%Y-%m-%d %H:%M:%S").to_string()
}

Local::now() returns the current local time as a DateTime<Local>. .format accepts a strftime-style string.

For UTC, use chrono::Utc::now(). For a specific timezone, the chrono-tz crate.

Command 3: format a date

#[tauri::command]
fn format_date(format_str: String) -> Result<String, String> {
  let now = Local::now();
  let formatted = now.format(&format_str).to_string();
  if formatted.is_empty() {
    return Err("Invalid format string".to_string());
  }
  Ok(formatted)
}

User-supplied format string. Returns Result so a bad format reaches the frontend as an error.

Command 4: generate a UUID

use uuid::Uuid;

#[tauri::command]
fn generate_uuid() -> String {
  Uuid::new_v4().to_string()
}

Uuid::new_v4() produces a random v4 UUID. .to_string() formats it as xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx.

UUID has many variants: v1 (timestamp + MAC), v3/v5 (namespace + name hashed), v4 (random), v7 (timestamp-prefixed sortable). Most apps want v4 unless they have a specific reason otherwise.

Registering the commands

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
  tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![
      extract_emails,
      get_current_time,
      format_date,
      generate_uuid,
    ])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}

Standard invoke_handler plus generate_handler!. Each command is now callable from JS.

Using them from the frontend

import { invoke } from "@tauri-apps/api/core";

async function demo() {
  const emails = await invoke<string[]>("extract_emails", {
    text: "Contact alice@example.com or bob@test.org",
  });
  // ["alice@example.com", "bob@test.org"]

  const time = await invoke<string>("get_current_time");
  // "2026-05-07 14:23:18"

  const formatted = await invoke<string>("format_date", {
    formatStr: "%A, %B %d %Y",
  });
  // "Thursday, May 07 2026"

  const id = await invoke<string>("generate_uuid");
  // "550e8400-e29b-41d4-a716-446655440000"
}

Each call is just invoke plus the command name and args.

Other useful crates

A short list of crates that come up often in Tauri apps:

Crate Purpose
serde / serde_json (de)serialisation. Already in every project.
reqwest HTTP client.
sqlx / rusqlite SQL databases.
tokio Async runtime. Tauri brings it in by default.
anyhow / thiserror Error types.
regex Pattern matching.
chrono / time Dates and times.
uuid Unique identifiers.
image Image decoding/encoding.
ring / sha2 Cryptographic hashing.
base64 Base64 encode/decode.
directories OS-standard directories (config, data, cache).
notify File system watcher.
walkdir Recursive directory traversal.
rayon Parallel iterators for CPU-heavy work.

You will reach for one of these every other Tauri project.

Why this is a big deal

In a normal web stack, every "I want to do X" requires either a third-party API (with auth, latency, quota) or a backend service (with deployment, scaling, ops). In Tauri, you can:

  • Manipulate images locally (no Cloudinary).
  • Hash passwords without round-tripping to a server.
  • Parse PDFs with the pdf-extract crate.
  • Stream a websocket with tokio-tungstenite.
  • Run inference with ort (ONNX) or candle.

The user's machine becomes the backend. Latency is microseconds. No server to maintain.

Common mistakes

Forgetting feature flags. uuid without features = ["v4"] doesn't give you Uuid::new_v4. Read the crate's docs to know which features you need.

Using unwrap for parsing user input. A bad regex or a bad format string panics. Use ? and propagate errors.

Compiling regex per call. Use once_cell::sync::Lazy or lazy_static!.

Adding crates without checking maintenance. Some crates are abandoned. Check the last release date and the issue tracker before depending on it.

Adding huge crates for one function. chrono is fine for date work; time is a smaller alternative if you only need parsing. Profile your bundle size; a 50MB Tauri app is unusual but possible if you pull in big crates.

Series wrap

That's twenty-four lessons of Tauri. From a scaffolded "Hello, world" to a real app with database, file I/O, native dialogs, system tray, async commands, error handling, modules, and now Rust crate ecosystems.

The pattern across all of them: web frontend, Rust backend, IPC bridge with invoke and events, capabilities for security. Once you have those four ideas, the rest is library knowledge — what's available, what does what, how to use it.

The real next step is yours: pick a tool you wish existed on your desktop, build it. Tauri shrinks the bar from "a real desktop app" to "a weekend project."

Recap

cargo add to pull in any Rust crate. Wrap with #[tauri::command]. Register in generate_handler![]. Call from React with invoke. The whole crates.io ecosystem becomes your backend toolkit. Compile regexes once. Use Result for fallible operations. The directories, notify, walkdir, image, reqwest, sqlx, and friends are the workhorses of most Tauri apps.

Thanks for following the series.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.