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

- Name
- Youngju Kim
- @fjvbn20031
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.
- Language / toolchain: rustc, cargo, rustup, rust-analyzer
- Async runtime: tokio is the de-facto standard; async-std / smol / embassy fill niches
- Web / networking: axum, actix-web 5, poem, rocket 0.5, warp, salvo, loco-rs
- Data / ORM: sqlx, sea-orm, diesel 2.x, rbatis
- Errors / observability: anyhow, thiserror, eyre, snafu, miette, tracing
- GUI / rendering: Tauri 2, egui, Iced, Slint, Dioxus, Yew, Leptos, Sycamore
- Systems / embedded / games: Embassy, RTIC, embedded-hal, Bevy, winit, gtk-rs
- 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-traitmacro 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 letmore 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
| Runtime | Target | Threading | I/O backend | Note |
|---|---|---|---|---|
| tokio | Server / networking standard | multi-thread / current-thread | mio (epoll/kqueue/IOCP) | De-facto standard in 2026 |
| async-std | Simple API-first | multi-thread | smol-based | Maintenance mode; not recommended for new projects |
| smol | Lightweight (~1k LOC) | single / multi | polling / async-io | Embedded, educational |
| embassy | no_std / firmware | cooperative, optionally no-alloc | HAL integration | RP2040 / STM32 / ESP32 |
| glommio | Linux-only io_uring | thread-per-core | io_uring | Databases / 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.
| Framework | Defining trait | Recommended scenario |
|---|---|---|
| axum | tower ecosystem, extractors | Default backend choice |
| actix-web 5 | Top-tier throughput | Ultra-high-performance gateways |
| poem | OpenAPI as a first-class citizen | Schema-first APIs |
| loco-rs | Full-stack conventions | "Rails of Rust" fast starts |
| salvo | Tree-based router | China 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 viasqlx-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-asyncadapter. 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.
| Crate | Style | Safety | async | Use when |
|---|---|---|---|---|
| sqlx | SQL-first + macro checks | Compile-time | Native | Backend default |
| sea-orm | ActiveRecord ORM | Runtime | Native | Full-stack / loco-rs |
| diesel | Typed DSL | Strongest compile-time | Adapter needed | Safety above all |
| rbatis | XML / macro SQL | Runtime | Native | China-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 intoanyhow::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_stdcompatible. - 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 viacargo-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}" }
})
}
})
}
| Framework | Paradigm | Multi-target | Full-stack | Use when |
|---|---|---|---|---|
| Leptos | Signals, SSR-first | Web | Strong | Default full-stack web |
| Dioxus | RSX, multi-renderer | Web / desktop / mobile / TUI | In progress | Multi-target |
| Yew | VDOM | Web | Weak | Learning / simple SPAs |
| Sycamore | Signals | Web | Weak | Learning |
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 (
.slintfiles). 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 }))),
)
}
| Library | Paradigm | Renderer | Best for |
|---|---|---|---|
| egui | Immediate mode | wgpu / glow | In-game debug, internal tools |
| Iced | Elm-like retained | wgpu / tiny-skia | Desktop apps |
| Slint | DSL retained | software / skia / femtovg | Embedded, kiosks |
| Dioxus desktop | WebView | OS WebView | Web-asset reuse |
| Tauri 2 | WebView | OS WebView | Full 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:
| Tool | Purpose |
|---|---|
| cargo-nextest | Parallel test runner, polished output, isolation |
| cargo-mutants | Mutation testing for test-quality verification |
| cargo-deny | License / vulnerability / duplicate-dep checks |
| cargo-audit | RUSTSEC DB vulnerability scanner |
| cargo-machete | Detects unused dependencies |
| cargo-expand | View macro-expanded output |
| cargo-binstall | Install prebuilt binaries |
| cargo-watch | Auto-rebuild on file changes |
| cargo-leptos | Leptos 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) andwasmer. 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 / project | When | Where Rust is used | Impact |
|---|---|---|---|
| Linux Kernel | Merged 2022, mainline expansion 2024 | Drivers, Android Binder | Systems-language certification |
| Cloudflare Pingora | Announced 2022, open-sourced 2024 | Next-gen HTTP proxy (NGINX replacement) | More than 40T requests/day |
| Discord | 2020 onward | Read States, gateway | GC pause eliminated, 99.99% reliability |
| Dropbox | 2014 onward | Magic Pocket storage | Exabyte scale |
| Figma | 2022 onward | Multiplayer HTTP layer | Concurrent edit performance |
| Mozilla / Servo | LF transfer 2024 | Next-gen browser engine | Embedded browser |
| AWS | Company-wide | Firecracker, Bottlerocket, parts of S3 | Containers / microVMs |
| Android | Binder, crash subsystem | System memory safety | |
| Microsoft | Windows | Selected system components | Memory-safety hardening |
| Meta | Backend | Parts of the service mesh | Officially adopted |
Pitfalls and trade-offs — easy traps when introducing Rust
Five traps we see when teams introduce Rust on a new project.
- Compile times: full builds in a large workspace are long. The standard setup combines
cargo check-centric workflow,sccache, themoldlinker, anddebug-info=line-tables-only. - async + Send boundaries: do not sprinkle
Box<dyn Trait>everywhere. Library traits should declareSend + 'staticbounds where realistic. - dyn compatibility vs. RPITIT: moving every async-trait method to RPITIT breaks dyn-trait usage. Traits used with
dynmay still need theasync-traitmacro. - Error-type explosion: do not bake a giant
enum Errorinto a single library. Use small per-module error types and lift them withFrom. - 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.
- The Rust Programming Language ("the book") chapters 1-10: build intuition for ownership, borrowing, and lifetimes.
- rustlings: small fill-in-the-blank exercises to get comfortable with compiler messages.
cargo newa CLI: clap + serde + anyhow combo.- A small API with tokio + axum: sqlx + Postgres + tracing all the way through.
- Study the 2024 edition changes and async fn in trait explicitly.
- 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
- Rust official — https://www.rust-lang.org/
- The Rust Book — https://doc.rust-lang.org/book/
- Rust blog — https://blog.rust-lang.org/
- tokio — https://tokio.rs/
- axum — https://github.com/tokio-rs/axum
- actix-web — https://actix.rs/
- sqlx — https://github.com/launchbadge/sqlx
- Diesel — https://diesel.rs/
- SeaORM — https://www.sea-ql.org/SeaORM/
- Bevy — https://bevyengine.org/
- Tauri — https://tauri.app/
- Leptos — https://leptos.dev/
- Dioxus — https://dioxuslabs.com/
- Yew — https://yew.rs/
- Embassy — https://embassy.dev/
- crates.io — https://crates.io/
- rust-for-linux — https://rust-for-linux.com/
- Cloudflare Pingora — https://github.com/cloudflare/pingora
- Servo (Linux Foundation) — https://servo.org/
- Discord Rust case study — https://discord.com/blog/why-discord-is-switching-from-go-to-rust