Ferris in JS

Topics:

Converting a JS app to Rust via @EtienneLaurin

Slides: js-rust-talk.pdf

Etienne presented his experience building the same web application ("Words Myth" - a storytelling game) in both JavaScript and Rust, comparing the developer experience, pain points, and maintainability.

Technology Stack Comparison

JavaScript Stack

  • React components and hooks
  • Express.js server
  • Prisma ORM
  • Single-threaded runtime
  • Various config formats (JSON, YAML, .config.js)

Rust Stack

  • Dioxus components and hooks (React-like)
  • Axum web framework
  • Diesel ORM
  • Multi-threaded by default
  • TOML for configuration

Key Observations

Type Safety & Maintainability

Rust is "definitely" more maintainable due to:

  • Explicit error handling (Result types)
  • Strict type system prevents runtime errors
  • Compile-time guarantees catch issues early
  • No implicit behaviors or type coercion

Forced Understanding of Systems

Rust requires explicit decisions about implementation details that JavaScript frameworks abstract away:

  • Sessions: Express provides req.session with defaults. Rust requires explicitly choosing storage backend (memory, disk, Redis, etc.)
  • Database connections: Connection pooling must be configured explicitly
  • Error handling: Every error case must be acknowledged

This friction leads to deeper understanding of how systems actually work.

Time/Date Libraries

  • chrono: Most popular, but uses i32 for timestamps
  • time: More verbose format strings
  • jiff: New library by BurntSushi, inspired by JavaScript's Temporal proposal

React Hooks in Dioxus

  • Rules of hooks enforced with helpful error messages
  • Type system prevents many async/mutation pitfalls common in React
  • However, still encountered runtime errors with async operations

Async/Await Challenges

Coming from JavaScript's setTimeout(), async in Rust was initially confusing:

  • No direct equivalent to setTimeout
  • Had to understand futures and async runtimes
  • Needed to use async_std::task::sleep() within Dioxus' spawn
  • Different async runtimes (tokio, async-std) have different APIs

Trade-offs

JavaScript Advantages:

  • Fast iteration and prototyping
  • Conventions and "magic" reduce boilerplate
  • Single-threaded model is simpler

Rust Advantages:

  • Better long-term maintainability
  • Compile-time safety catches entire classes of bugs
  • Forces understanding of underlying systems
  • Safer refactoring

Rust Disadvantages:

  • Slow compile times (vs JavaScript's fast runtime)
  • Steeper learning curve
  • More upfront decision-making required

Caveats

  • Framework choices (Dioxus, Diesel) were somewhat arbitrary based on popularity and beginner-friendliness
  • Results may differ with other Rust web frameworks

Getting started with Tauri v2 for building cross-platform apps via @DanielPBank

Daniel shared the codebase for a Desktop app he is working on built with Tauri v2. Tauri as a system is well-liked amongst developers. It affords you the same experience of building a native app in TypeScript that the popular Electron framework provides. Under the hood, however, Tauri leverages the operating system's native web view (WebKit on macOS, Microsoft Edge WebView on Windows, etc). This means you don't need to ship a Chromium runtime with your app and so the footprint is much smaller.

A simple Hello World app written in Electron takes 100MB of space. In comparison, the production app that Daniel demoed was only a measly 22MB.

Daniel demonstrated a few of the plethora of Tauri plugins that facilitate using native system capabilities, as well as Tauri commands which serve as an interface for calling Rust code from the TypeScript frontend.

It was noted with type safety breaks down when passing types between Rust and TypeScript. More accurately, in Tauri v2 when you use commands with the #[tauri::command] macro, the return types from Rust are typed in TypeScript through auto-generated TypeScript bindings. Tauri can generate type definitions for your commands. However, this requires some setup - you need to configure TypeScript bindings generation. If you haven't set this up, the types would default to unknown or any. Going the other way, TypeScript types are serialized as JSON and deserialized in Rust via Serde (runtime type checking)

Crates you should know