Skip to content
Published on

Rust Ecosystem 2026 Deep-Dive — Rust 1.85+, tokio, axum, actix-web, sqlx, Bevy, Tauri, Leptos, Dioxus, Embassy, Cargo

Authors

Intro — In May 2026 Rust has hardened into "the systems language standard"

When the rust-for-linux patches were merged into the mainline kernel in late 2024, Rust crossed a line from "promising" to "the language Linux adopted." In May 2026 the shift is even sharper. Cloudflare's Pingora (the Rust proxy replacing NGINX inside Cloudflare) handles more than 40 trillion requests per day; Discord's Read States service killed GC pauses and hit 99.99% reliability after the Rust rewrite; Dropbox's Magic Pocket storage engine, Figma's multiplayer HTTP layer, and Mozilla's Servo (transferred to the Linux Foundation in 2024) all run on Rust.

This article is not a marketing matrix. It honestly walks through "what does a 2026 production Rust stack actually look like" — from 1.85 / 2024 edition changes, tokio's scheduler, axum/sqlx code, Tauri 2 mobile GA, Bevy 0.15 ECS, Embassy no_std async, all the way to Korea/Japan adoption.

The 2026 Rust stack — broken into 8 layers

First, the big picture. The standard 2026 Rust stack divides into 8 layers.

  1. Language / toolchain: rustc, cargo, rustup, rust-analyzer
  2. Async runtime: tokio is the de-facto standard; async-std / smol / embassy fill niches
  3. Web / networking: axum, actix-web 5, poem, rocket 0.5, warp, salvo, loco-rs
  4. Data / ORM: sqlx, sea-orm, diesel 2.x, rbatis
  5. Errors / observability: anyhow, thiserror, eyre, snafu, miette, tracing
  6. GUI / rendering: Tauri 2, egui, Iced, Slint, Dioxus, Yew, Leptos, Sycamore
  7. Systems / embedded / games: Embassy, RTIC, embedded-hal, Bevy, winit, gtk-rs
  8. Tooling ecosystem: cargo-nextest, cargo-mutants, cargo-deny, cargo-audit, cross, binstall

The era when a single crate owned each layer is over. "axum + tokio + sqlx + tracing" is now the de-facto backend combo, while GUI splits into "Tauri 2 + Leptos/Dioxus" or "egui/Slint." We walk through every layer below.

Rust 1.85 and the 2024 edition — async fn in trait, let-else, stabilized GATs

Rust 1.85 is the first stable release of the 2024 edition. Key changes:

  • async fn in trait (RPITIT): the async-trait macro is no longer mandatory. The dyn-safety restriction remains, but for static dispatch you can skip the macro.
  • GAT (Generic Associated Types): stabilized in 1.65, with many lints cleaned up in the 2024 edition.
  • let-else: available since 1.65. The 2024 edition makes temporary lifetimes for if let more intuitive.
  • never type fallback: ! now falls back to ! rather than (). Some code needs explicit type annotations.
  • raw lifetimes / r#: resolves identifier collisions.

A representative 1.85-style trait definition looks like this.

use std::error::Error;

// 2024 edition: async fn in trait, no macro required
pub trait UserRepo {
    async fn find_by_id(&self, id: u64) -> Result<Option<User>, Box<dyn Error + Send + Sync>>;
    async fn create(&self, user: NewUser) -> Result<User, Box<dyn Error + Send + Sync>>;
}

pub struct User { pub id: u64, pub email: String }
pub struct NewUser { pub email: String }

pub struct PgUserRepo { pool: sqlx::PgPool }

impl UserRepo for PgUserRepo {
    async fn find_by_id(&self, id: u64) -> Result<Option<User>, Box<dyn Error + Send + Sync>> {
        let row = sqlx::query_as::<_, (i64, String)>("SELECT id, email FROM users WHERE id = $1")
            .bind(id as i64)
            .fetch_optional(&self.pool)
            .await?;
        Ok(row.map(|(id, email)| User { id: id as u64, email }))
    }

    async fn create(&self, user: NewUser) -> Result<User, Box<dyn Error + Send + Sync>> {
        let row: (i64,) = sqlx::query_as("INSERT INTO users(email) VALUES ($1) RETURNING id")
            .bind(&user.email)
            .fetch_one(&self.pool)
            .await?;
        Ok(User { id: row.0 as u64, email: user.email })
    }
}

tokio runtime internals — work stealing, LIFO slot, cooperative scheduling

tokio uses an M:N work-stealing scheduler. There are three core mechanisms.

  • Local queue + global queue: each worker owns a local queue (1 LIFO slot + 256 FIFO slots). When it overflows, half of it spills into the global queue.
  • LIFO slot: the most recently wake()d task is polled first, preserving cache locality.
  • Work stealing: when a worker's queue is empty, it steals half of another worker's queue at random.

Since tokio 1.40 the cooperative scheduling budget defaults to 128. A task that polls 128 times yields automatically — explicit tokio::task::yield_now().await in tight loops is almost never needed anymore.

use tokio::sync::mpsc;
use tokio::time::{sleep, Duration};

#[tokio::main(flavor = "multi_thread", worker_threads = 8)]
async fn main() {
    let (tx, mut rx) = mpsc::channel::<u64>(1024);

    // 100 producers
    for i in 0..100u64 {
        let tx = tx.clone();
        tokio::spawn(async move {
            for j in 0..1000u64 {
                let _ = tx.send(i * 1_000 + j).await;
                if j % 100 == 0 { sleep(Duration::from_micros(10)).await; }
            }
        });
    }
    drop(tx);

    // single consumer task
    let mut count = 0u64;
    while let Some(_v) = rx.recv().await { count += 1; }
    println!("received: {count}");
}

#[tokio::main] is just sugar; under the hood it calls tokio::runtime::Builder::new_multi_thread(). In libraries you typically use the builder directly instead of the macro.

Async runtimes compared — tokio vs async-std vs smol vs embassy

RuntimeTargetThreadingI/O backendNote
tokioServer / networking standardmulti-thread / current-threadmio (epoll/kqueue/IOCP)De-facto standard in 2026
async-stdSimple API-firstmulti-threadsmol-basedMaintenance mode; not recommended for new projects
smolLightweight (~1k LOC)single / multipolling / async-ioEmbedded, educational
embassyno_std / firmwarecooperative, optionally no-allocHAL integrationRP2040 / STM32 / ESP32
glommioLinux-only io_uringthread-per-coreio_uringDatabases / storage

As of May 2026 the standard pattern is "libraries stay runtime-agnostic; applications pick tokio." It is bad manners to bake #[tokio::main] into a library crate.

axum — the de-facto async web framework

Built by the tokio team itself, axum went through 0.7 and 0.8 and is now the standard. The defining traits are tower::Service-based middleware, a powerful extractor system, and a thin layer over hyper.

use axum::{
    Router,
    extract::{Path, State},
    response::Json,
    routing::{get, post},
    http::StatusCode,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use sqlx::PgPool;

#[derive(Clone)]
struct AppState { pool: PgPool }

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

#[derive(Deserialize)]
struct CreateUser { email: String }

async fn get_user(
    State(state): State<Arc<AppState>>,
    Path(id): Path<i64>,
) -> Result<Json<User>, StatusCode> {
    let row = sqlx::query_as::<_, (i64, String)>(
        "SELECT id, email FROM users WHERE id = $1"
    )
    .bind(id)
    .fetch_optional(&state.pool)
    .await
    .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
    .ok_or(StatusCode::NOT_FOUND)?;

    Ok(Json(User { id: row.0, email: row.1 }))
}

async fn create_user(
    State(state): State<Arc<AppState>>,
    Json(payload): Json<CreateUser>,
) -> Result<(StatusCode, Json<User>), StatusCode> {
    let row: (i64,) = sqlx::query_as("INSERT INTO users(email) VALUES ($1) RETURNING id")
        .bind(&payload.email)
        .fetch_one(&state.pool)
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
    Ok((StatusCode::CREATED, Json(User { id: row.0, email: payload.email })))
}

#[tokio::main]
async fn main() {
    let pool = PgPool::connect(&std::env::var("DATABASE_URL").unwrap()).await.unwrap();
    let state = Arc::new(AppState { pool });

    let app = Router::new()
        .route("/users/:id", get(get_user))
        .route("/users", post(create_user))
        .with_state(state);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

Extractors like Path, State, Json, Query are all extensible via the FromRequestParts or FromRequest traits. Middleware composes through tower::Layer and plugs in via Router::layer().

actix-web 5 and the other web frameworks — poem, rocket, warp, salvo, loco-rs

Even with axum as the standard, the alternatives are alive and worth knowing.

  • actix-web 5: the actor model has faded; idiomatic actix-web is now ordinary async-fn handlers. Still at the top of TechEmpower benchmarks. Preferred by teams that micro-tune for throughput.
  • poem: an extractor design similar to axum, on top of tokio. Standout strength is auto-OpenAPI generation via poem-openapi.
  • rocket 0.5: stabilized on the 1.0 track, but async adoption was slow and new uptake is dropping.
  • warp: filter combinator design. Maintenance activity is reduced.
  • salvo: active in the Chinese community. Tree-based router.
  • loco-rs: "the Rails of Rust." Bundles axum + sea-orm + background jobs + mailer into a full-stack assembly.
FrameworkDefining traitRecommended scenario
axumtower ecosystem, extractorsDefault backend choice
actix-web 5Top-tier throughputUltra-high-performance gateways
poemOpenAPI as a first-class citizenSchema-first APIs
loco-rsFull-stack conventions"Rails of Rust" fast starts
salvoTree-based routerChina market / very large routing trees

sqlx, sea-orm, diesel 2.x — comparing the database layer

The DB layer settled into three camps.

  • sqlx 0.8+: async, compile-time query checking (query! macro), Postgres / MySQL / SQLite / MSSQL. Migrations via sqlx-cli. The 2026 default.
  • sea-orm: full ORM, ActiveRecord-style, migration DSL, entity code generation via sea-orm-cli. The default ORM for loco-rs.
  • diesel 2.x: synchronous ORM with diesel-async adapter. Strongest compile-time safety, but the macro learning curve is steep.
  • rbatis: async ORM born in China. Mybatis-style XML / macros.

A sqlx compile-time-checked query example.

use sqlx::PgPool;

#[derive(sqlx::FromRow)]
struct Order { id: i64, user_id: i64, total_cents: i64, status: String }

async fn list_user_orders(pool: &PgPool, user_id: i64) -> sqlx::Result<Vec<Order>> {
    // The macro hits the DB at compile time to check SELECT column types.
    let orders = sqlx::query_as!(
        Order,
        r#"SELECT id, user_id, total_cents, status
           FROM orders
           WHERE user_id = $1
           ORDER BY id DESC
           LIMIT 100"#,
        user_id
    )
    .fetch_all(pool)
    .await?;
    Ok(orders)
}

sqlx::query_as! looks up the schema either through DATABASE_URL or via sqlx-data.json (offline mode). Using offline mode in CI is standard practice.

CrateStyleSafetyasyncUse when
sqlxSQL-first + macro checksCompile-timeNativeBackend default
sea-ormActiveRecord ORMRuntimeNativeFull-stack / loco-rs
dieselTyped DSLStrongest compile-timeAdapter neededSafety above all
rbatisXML / macro SQLRuntimeNativeChina-market compatibility

Error handling — anyhow, thiserror, eyre, snafu, miette

The error-handling crates split along two axes.

  • Library error types: thiserror (derive macros), snafu (context-adding focus).
  • Application error aggregation: anyhow (absorbs anything into anyhow::Error), eyre (anyhow + custom reporter), miette (pretty diagnostic output).

A typical library/application separation.

// Library side: explicit error types via thiserror
use thiserror::Error;

#[derive(Debug, Error)]
pub enum BillingError {
    #[error("invoice not found: {0}")]
    NotFound(i64),

    #[error("database error")]
    Db(#[from] sqlx::Error),

    #[error("payment provider error: {0}")]
    Provider(String),
}

pub async fn charge(invoice_id: i64) -> Result<(), BillingError> {
    Err(BillingError::Provider("stripe timeout".into()))
}

// Application side: accumulate context with anyhow
use anyhow::Context;

pub async fn process_batch(ids: &[i64]) -> anyhow::Result<()> {
    for &id in ids {
        charge(id).await
            .with_context(|| format!("failed to charge invoice {id}"))?;
    }
    Ok(())
}

miette produces compiler-style diagnostics that point at source locations. It is heavily used in user-facing CLI tools.

tracing + tracing-subscriber — the observability standard for async Rust

Past println! / log / env_logger, the 2026 standard for Rust observability is tracing. Built by the tokio team, it slots naturally into async code.

use tracing::{info, instrument, error};
use tracing_subscriber::{EnvFilter, fmt, prelude::*};

#[instrument(skip(pool))]
async fn handle_request(pool: &sqlx::PgPool, user_id: i64) -> anyhow::Result<()> {
    info!(user_id, "processing request");
    let _row = sqlx::query("SELECT 1").fetch_one(pool).await?;
    Ok(())
}

#[tokio::main]
async fn main() {
    tracing_subscriber::registry()
        .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()))
        .with(fmt::layer().json())
        .init();

    info!("server starting");
}

Adding tracing-opentelemetry forwards spans straight into an OTEL tracer; ecosystem exporters like tracing-loki and tracing-bunyan-formatter are abundant.

serde in 2026 — still the standard, plus miniserde and postcard

serde is effectively the standard serialization framework. As of May 2026 the 1.0 line is still maintained; an RFC for "serde 2.0" is under discussion but no stable release yet.

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
struct ApiResponse {
    #[serde(rename = "userId")]
    user_id: i64,

    #[serde(default)]
    tags: Vec<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    email: Option<String>,
}

fn parse_response(s: &str) -> serde_json::Result<ApiResponse> {
    serde_json::from_str(s)
}

Alternatives:

  • miniserde: minimalist serializer that drastically cuts macro compile time. JSON only.
  • postcard: serde-based binary format designed for embedded targets. no_std compatible.
  • bincode 2.0: standard fast binary format. Derive macros are split out.
  • rkyv: zero-copy deserialization. Performance-first choice.

Tauri 2 — desktop and mobile from the same code

If Tauri 1 was "a lighter Electron alternative," Tauri 2 (GA in 2024, on a stable track by May 2026) bundles desktop + Android + iOS around the same Rust core.

// src-tauri/src/lib.rs
use tauri::command;

#[command]
async fn greet(name: String) -> Result<String, String> {
    Ok(format!("Hello, {name}!"))
}

#[command]
async fn read_file(path: String) -> Result<String, String> {
    tokio::fs::read_to_string(&path).await.map_err(|e| e.to_string())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
        .invoke_handler(tauri::generate_handler![greet, read_file])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

The plugin system has matured; first-party plugins such as tauri-plugin-sql, tauri-plugin-store, tauri-plugin-deep-link, and tauri-plugin-notification cover mobile too. The frontend can be vanilla JS / React / Vue / Svelte / Leptos / Dioxus.

Leptos, Dioxus, Yew, Sycamore — Rust full-stack frontends

WASM + Rust full-stack frameworks split four ways.

  • Leptos 0.7+: signal-based fine-grained reactivity, first-class SSR / CSR / hydration / server fn. Full-stack build via cargo-leptos.
  • Dioxus 0.6+: RSX (React-like) macro, multi-target — desktop / mobile / web / TUI. The Dioxus team turned full-time OSS in 2025.
  • Yew: classic VDOM. Familiar onboarding but loses to Leptos on full-stack tooling.
  • Sycamore: Solid-inspired signal-based. Good for learning / small SPAs.

A Leptos full-stack component example.

use leptos::*;
use leptos::server_fn::ServerFn;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone)]
pub struct Todo { pub id: i64, pub title: String, pub done: bool }

#[server]
pub async fn list_todos() -> Result<Vec<Todo>, ServerFnError> {
    Ok(vec![Todo { id: 1, title: "ship".into(), done: false }])
}

#[component]
pub fn TodoList() -> impl IntoView {
    let todos = create_resource(|| (), |_| async move { list_todos().await });

    view! {
        <Suspense fallback=move || view! { <p>"Loading..."</p> }>
            {move || todos.get().map(|res| match res {
                Ok(items) => view! {
                    <ul>{items.into_iter().map(|t| view!{ <li>{t.title}</li> }).collect_view()}</ul>
                }.into_view(),
                Err(_) => view! { <p>"error"</p> }.into_view(),
            })}
        </Suspense>
    }
}

The same screen in Dioxus.

use dioxus::prelude::*;

#[derive(Clone, PartialEq, Props)]
struct Todo { id: i64, title: String, done: bool }

fn TodoList(cx: Scope) -> Element {
    let todos = use_state(cx, || vec![Todo { id: 1, title: "ship".into(), done: false }]);

    cx.render(rsx! {
        ul {
            todos.iter().map(|t| rsx!{
                li { key: "{t.id}", "{t.title}" }
            })
        }
    })
}
FrameworkParadigmMulti-targetFull-stackUse when
LeptosSignals, SSR-firstWebStrongDefault full-stack web
DioxusRSX, multi-rendererWeb / desktop / mobile / TUIIn progressMulti-target
YewVDOMWebWeakLearning / simple SPAs
SycamoreSignalsWebWeakLearning

egui, Iced, Slint — native GUI libraries

If Tauri is "WebView-based," the native GUI side has three options.

  • egui: immediate-mode GUI. Own renderer (eframe), short code, great for in-game UIs and internal tools.
  • Iced: Elm-inspired, Cmd/Sub pattern. Desktop apps.
  • Slint: DSL (.slint files). Embedded-friendly, with a commercial license available.
use eframe::egui;

struct App { name: String, age: u32 }

impl eframe::App for App {
    fn update(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            ui.heading("My egui Application");
            ui.horizontal(|ui| {
                ui.label("Your name: ");
                ui.text_edit_singleline(&mut self.name);
            });
            ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));
            if ui.button("Increment").clicked() { self.age += 1; }
            ui.label(format!("Hello '{}', age {}", self.name, self.age));
        });
    }
}

fn main() -> eframe::Result<()> {
    let opts = eframe::NativeOptions::default();
    eframe::run_native(
        "demo",
        opts,
        Box::new(|_| Ok(Box::new(App { name: "Arthur".into(), age: 42 }))),
    )
}
LibraryParadigmRendererBest for
eguiImmediate modewgpu / glowIn-game debug, internal tools
IcedElm-like retainedwgpu / tiny-skiaDesktop apps
SlintDSL retainedsoftware / skia / femtovgEmbedded, kiosks
Dioxus desktopWebViewOS WebViewWeb-asset reuse
Tauri 2WebViewOS WebViewFull desktop + mobile

Bevy 0.15+ — the de-facto Rust game engine

Bevy is a data-oriented ECS game engine. In 0.15 (2025) it landed BSN (Bevy Scene Notation), render graph v2, and a UI overhaul; the 0.16 / 0.17 line in 2026 is adding the editor, hot reload, and more materials.

use bevy::prelude::*;

#[derive(Component)]
struct Player { speed: f32 }

fn spawn_player(mut commands: Commands) {
    commands.spawn((
        Player { speed: 250.0 },
        Sprite::from_color(Color::srgb(0.2, 0.7, 1.0), Vec2::new(40.0, 40.0)),
        Transform::default(),
    ));
}

fn move_player(
    time: Res<Time>,
    keys: Res<ButtonInput<KeyCode>>,
    mut q: Query<(&Player, &mut Transform)>,
) {
    for (p, mut t) in &mut q {
        let mut dir = Vec3::ZERO;
        if keys.pressed(KeyCode::KeyW) { dir.y += 1.0; }
        if keys.pressed(KeyCode::KeyS) { dir.y -= 1.0; }
        if keys.pressed(KeyCode::KeyA) { dir.x -= 1.0; }
        if keys.pressed(KeyCode::KeyD) { dir.x += 1.0; }
        t.translation += dir.normalize_or_zero() * p.speed * time.delta_secs();
    }
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, spawn_player)
        .add_systems(Update, move_player)
        .run();
}

Bevy's charm is that "ECS is a first-class citizen and systems are just functions." Beyond games, it sees growing adoption for simulations and tooling backends.

Embassy, RTIC, embedded-hal — embedded and firmware Rust

In no_std embedded land the 2026 story is clear. Embassy is the de-facto standard, while interrupt-priority-based deterministic systems still favor RTIC.

#![no_std]
#![no_main]

use embassy_executor::Spawner;
use embassy_rp::gpio::{Level, Output};
use embassy_time::{Duration, Timer};
use {defmt_rtt as _, panic_probe as _};

#[embassy_executor::task]
async fn blink(mut led: Output<'static>) {
    loop {
        led.set_high();
        Timer::after(Duration::from_millis(500)).await;
        led.set_low();
        Timer::after(Duration::from_millis(500)).await;
    }
}

#[embassy_executor::main]
async fn main(spawner: Spawner) {
    let p = embassy_rp::init(Default::default());
    let led = Output::new(p.PIN_25, Level::Low);
    spawner.spawn(blink(led)).unwrap();
}

The embedded-hal 1.0 release in late 2023 unified the driver ecosystem. Whether you target STM32 / RP2040 / nRF52 / ESP32, the same driver runs on any HAL implementing the embedded-hal traits.

CLI and system tools — clap 4.5, argh, ripgrep, bat, fd

For CLI parsers the de-facto standard is clap.

use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "blogctl", version, about = "Blog management CLI")]
struct Cli {
    #[command(subcommand)]
    command: Cmd,
}

#[derive(Subcommand)]
enum Cmd {
    /// Create a new post
    New { title: String, #[arg(short, long)] draft: bool },
    /// Publish a post
    Publish { slug: String },
}

fn main() {
    let cli = Cli::parse();
    match cli.command {
        Cmd::New { title, draft } => println!("new post: {title} (draft={draft})"),
        Cmd::Publish { slug } => println!("publish {slug}"),
    }
}

argh (Google) keeps derive-macro compile time low and suits small tools. Among system command-line tools, Rust-written ones have become standard: ripgrep, fd, bat, eza, zoxide, bottom, dust, procs, hyperfine, tokei, mdbook.

Cargo workspaces, build.rs, cross-compilation

Large Rust projects bundle crates into a workspace.

# Cargo.toml at the workspace root
[workspace]
resolver = "3"
members = ["crates/api", "crates/domain", "crates/storage"]

[workspace.dependencies]
tokio = { version = "1.40", features = ["full"] }
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres"] }
axum = "0.8"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "1"
anyhow = "1"

[profile.release]
opt-level = 3
lto = "thin"
codegen-units = 1
strip = "symbols"

For cross-compilation, the cross crate is the standard. cross build --target aarch64-unknown-linux-musl --release produces a static binary via a Dockerized toolchain.

Cargo tooling — nextest, mutants, deny, audit, machete, expand, binstall

Stock cargo test is no longer enough. The 2026 standard companion tools are:

ToolPurpose
cargo-nextestParallel test runner, polished output, isolation
cargo-mutantsMutation testing for test-quality verification
cargo-denyLicense / vulnerability / duplicate-dep checks
cargo-auditRUSTSEC DB vulnerability scanner
cargo-macheteDetects unused dependencies
cargo-expandView macro-expanded output
cargo-binstallInstall prebuilt binaries
cargo-watchAuto-rebuild on file changes
cargo-leptosLeptos full-stack build
# Standard CI bundle
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo nextest run --workspace --all-features
cargo deny check
cargo audit
cargo machete

WebAssembly — wasm-bindgen, wasm-pack, wasmtime, wasmer

Rust + WASM has two camps.

  • Browser: wasm-bindgen + wasm-pack. Automatic bindings between JS and Rust.
  • Server / edge: wasmtime (Bytecode Alliance, sponsored by Fastly / Microsoft) and wasmer. WASI Preview 2 settled in 2024, and component-model-based modularization is rolling out.

Cloudflare Workers supports Rust + WASM as a first-class citizen, and companies like Fermyon, Spin, and WasmCloud are pulling WASM-based serverless forward.

rust-for-linux, Cloudflare Pingora, Discord, Dropbox, Figma — production stories

Rust adoption is no longer "the startup experiment."

Company / projectWhenWhere Rust is usedImpact
Linux KernelMerged 2022, mainline expansion 2024Drivers, Android BinderSystems-language certification
Cloudflare PingoraAnnounced 2022, open-sourced 2024Next-gen HTTP proxy (NGINX replacement)More than 40T requests/day
Discord2020 onwardRead States, gatewayGC pause eliminated, 99.99% reliability
Dropbox2014 onwardMagic Pocket storageExabyte scale
Figma2022 onwardMultiplayer HTTP layerConcurrent edit performance
Mozilla / ServoLF transfer 2024Next-gen browser engineEmbedded browser
AWSCompany-wideFirecracker, Bottlerocket, parts of S3Containers / microVMs
GoogleAndroidBinder, crash subsystemSystem memory safety
MicrosoftWindowsSelected system componentsMemory-safety hardening
MetaBackendParts of the service meshOfficially adopted

Pitfalls and trade-offs — easy traps when introducing Rust

Five traps we see when teams introduce Rust on a new project.

  1. Compile times: full builds in a large workspace are long. The standard setup combines cargo check-centric workflow, sccache, the mold linker, and debug-info=line-tables-only.
  2. async + Send boundaries: do not sprinkle Box<dyn Trait> everywhere. Library traits should declare Send + 'static bounds where realistic.
  3. dyn compatibility vs. RPITIT: moving every async-trait method to RPITIT breaks dyn-trait usage. Traits used with dyn may still need the async-trait macro.
  4. Error-type explosion: do not bake a giant enum Error into a single library. Use small per-module error types and lift them with From.
  5. unsafe overuse: 99% of code does not need unsafe. If you are not doing FFI / embedded / SIMD, stop and revisit.

Korea and Japan — Rust adoption at Naver, Kakao, CyberAgent, Mercari, Sansan

Korea and Japan are equally active.

  • Naver: parts of search / ad backends and media pipelines moved to Rust. Internal Rust study groups and seminars run on a recurring cadence.
  • Kakao Brain / Kakao Enterprise: Rust for ML inference servers and model gateways, combined with Python via PyO3.
  • Discord Korea / Twitch Korea group: relies on the global Rust stack as-is.
  • CyberAgent: rewrote parts of ad and game infrastructure in Rust, including some AbemaTV microservices.
  • Mercari: search, infra tools, and CLIs in Rust. Rust roles open continuously.
  • Sansan: Rust in the business-card OCR post-processing pipeline.
  • LINE Yahoo: system daemons and build tools migrated to Rust.

Both Tokyo and Seoul host quarterly Rust meetups. Korea has the "Rust Korea" Slack / Discord / Facebook groups; Japan has the recurring "Rust.Tokyo" conference.

A 2026 learning roadmap — what to study, in what order

A recommended flow for developers picking up Rust today.

  1. The Rust Programming Language ("the book") chapters 1-10: build intuition for ownership, borrowing, and lifetimes.
  2. rustlings: small fill-in-the-blank exercises to get comfortable with compiler messages.
  3. cargo new a CLI: clap + serde + anyhow combo.
  4. A small API with tokio + axum: sqlx + Postgres + tracing all the way through.
  5. Study the 2024 edition changes and async fn in trait explicitly.
  6. Pick a domain:
    • Web / full-stack: Leptos or Dioxus.
    • Desktop: Tauri 2 or egui.
    • Games: Bevy.
    • Embedded: Embassy + RP2040 or STM32 Discovery.
    • Systems: read the source of rust-for-linux, Firecracker, or Pingora.

The goal is not "write fast" — it is "talk with the compiler and build a model." Compiler errors are friends.

Wrap-up — in 2026 Rust is not "a choice" but "a default"

Connect the dots — rust-for-linux merged in 2024, Tauri 2 GA in 2024, Pingora open-sourced in 2025, axum 0.8 in 2025, Rust 1.85 / 2024 edition in 2026 — and the picture is clear. Backend defaults to tokio + axum + sqlx; desktop / mobile defaults to Tauri 2; games default to Bevy; embedded defaults to Embassy; full-stack web defaults to Leptos / Dioxus.

The question is no longer "should we use Rust?" but "where do we use Rust?" — and the answer keeps expanding.

References