Introduction: Why Rust is the Ideal Candidate for Real-Time?
\n\nImagine this scenario: you are writing a chat server, a trading terminal, or a monitoring system in Rust. Users want to receive updates instantly. Nobody likes clicking the "Refresh" button manually — that's so last century. But how do you organize data delivery from the server to the client?
\n\nFor a long time, the only method was long polling — an elegant but slightly "hacky" trick. Then came WebSocket and turned the world of web development upside down. And now we have Rust — a language that allows you to get the most out of both approaches without sacrificing memory safety.
\n\nIn this article, we won't just compare long polling and WebSocket in Rust — we'll write working examples, run them, and see what's happening under the hood. If you are writing a real-time application in Rust and are unsure about which technology to choose — you've come to the right place.
\n\nWhat is Long Polling in Simple Terms
\n\nLong polling is a technique where a client opens an HTTP request to the server and... waits. The server doesn't respond immediately but keeps the connection open until a new event occurs or a timeout expires.
\n\nImagine you call a friend and say: "Call me when the package arrives" — and you stay on the line for an hour. That's long polling. Regular polling is when you call back every 5 minutes: "So, has it arrived yet?"
\n\nImplementing Long Polling in Rust with Actix-web
\n\nLet's write a simple server using actix-web that uses long polling. We'll have a message queue — when a new message appears, all the "waiters" receive it.
\n\nuse actix_web::{web, App, HttpServer, HttpRequest, HttpResponse};\nuse std::sync::Mutex;\nuse tokio::sync::broadcast;\n\nstruct AppState {\n tx: broadcast::Sender<String>,\n}\n\nasync fn long_poll_handler(data: web::Data<Mutex<AppState>>) -> HttpResponse {\n let mut rx = {\n let state = data.lock().unwrap();\n state.tx.subscribe()\n };\n\n // Wait for a new message for 30 seconds\n match tokio::time::timeout(\n std::time::Duration::from_secs(30),\n rx.recv()\n ).await {\n Ok(Ok(msg)) => HttpResponse::Ok().body(msg),\n Ok(Err(_)) => HttpResponse::InternalServerError().finish(),\n Err(_) => HttpResponse::NoContent().finish(), // timeout\n }\n}\n\n#[actix_web::main]\nasync fn main() -> std::io::Result<()> {\n let (tx, _) = broadcast::channel::<String>(100);\n\n HttpServer::new(move || {\n App::new()\n .app_data(web::Data::new(Mutex::new(AppState { tx: tx.clone() })))\n .route("/poll", web::get().to(long_poll_handler))\n })\n .bind("127.0.0.1:8080")?\n .run()\n .await\n}\n\nThe client will simply hang on GET /poll. When a message arrives — the server will respond. If there is nothing for 30 seconds — it returns 204 No Content, and the client will re-request.
Pros:
\n- \n
- Works over regular HTTP — no proxy servers with WebSocket support needed \n
- Easy integration with existing REST APIs \n
- Great for corporate networks where proxies block WebSocket \n
Cons:
\n- \n
- Each "poll" holds a connection — with 10,000 clients, that's 10,000 open TCP connections \n
- Latency: the client still reconnects after a timeout \n
- The server spends resources maintaining "hanging" requests \n
WebSocket in Rust: When You Want Speed
\n\nWebSocket is no longer a "call and wait" — it's a full-fledged two-way channel. The server and client can exchange messages at any time, without the overhead of HTTP headers.
\n\nIn Rust, the WebSocket ecosystem is rich: tokio-tungstenite for low-level work, actix-web with built-in