Когато заглавието ви трябва на две места
Rust + Actix-web: APIs, които летят и нямат проблеми със скалирането
Ще си призная нещо — когато ми трябва бърз REST API, първото нещо, за което посегвам, е Node.js или Python. Знам компромисите: бърза разработка срещу производителност, бърз прототайп срещу надеждност в production. Но какво ако можеш да имаш и двете?
Точно тук идва Rust, а Actix-web е рамката, която прави нещата достъпни за уеб разработчиците.
Защо точно Rust за API-та? (Без захаросване)
Нека започнем с неудобната истина: Rust има стръмна крива на обучение. Не е от най-меките. Borrow checker-ът ще те смири. Ще прекараш часове в дебъгване на lifetimes вместо да пускаш функции.
Но нещо, което най-накрая ми „click"-на: Rust не те кара да избираш между сигурност и скорост. Твоят API ще е memory-safe по подразбиране — без null pointer exceptions, без data races, без garbage collection паузи, които убиват p99 latency-те. Когато обслужваш хиляди заявки в секунда, тази консистентност има значение.
За стартъпи и разработчици, които скалират системи, това означава предвидима производителност без операционния overhead на допълнителен слой за сигурност.
Първи стъпки с Actix-web
Actix-web е вдъхновен от (но не е свързан с) Elixir/Phoenix рамката. Залага на actor модела и предлага ергономичен API, който се усеща познат, ако си работил с Express, Fastify или подобни.
Инициализация на проекта
cargo new rust-api-demo
cd rust-api-demo
cargo add actix-web actix-rt
Първият ти endpoint
use actix_web::{web, App, HttpServer, HttpResponse};
async fn health_check() -> HttpResponse {
HttpResponse::Ok().json(serde_json::json!({
"status": "healthy",
"version": env!("CARGO_PKG_VERSION")
}))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/health", web::get().to(health_check))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Този health endpoint? Компилира се за милисекунди и стартира за микросекунди. Пускаш cargo run, удряш localhost:8080/health — и веднага виждаш JSON отговора.
Построяваме истински API: Todos с пълни CRUD операции
Сега да направим нещо по-практично — Todo API с пълен набор от CRUD операции.
Дефинираме модела
use serde::{Deserialize, Serialize};
use std::sync::Mutex;
use std::cell::Cell;
#[derive(Clone, Serialize, Deserialize)]
struct Todo {
id: u64,
title: String,
completed: bool,
}
Целият списък с Todos се съхранява в глобално state чрез Mutex, което прави достъпа thread-safe. Всяка заявка получава клонирана версия на списъка — прост, но ефективен модел за демо.
CRUD handlers-ите
async fn get_todos(data: web::Data<Mutex<Vec<Todo>>>) -> HttpResponse {
let todos = data.lock().unwrap();
HttpResponse::Ok().json(&*todos)
}
async fn create_todo(
data: web::Data<Mutex<Vec<Todo>>>,
body: web::Json<Todo>,
) -> HttpResponse {
let mut todos = data.lock().unwrap();
let new_id = todos.len() as u64 + 1;
let todo = Todo {
id: new_id,
title: body.title.clone(),
completed: false,
};
todos.push(todo);
HttpResponse::Created().json(todo)
}
async fn update_todo(
data: web::Data<Mutex<Vec<Todo>>>,
path: web::Path<u64>,
body: web::Json<Todo>,
) -> HttpResponse {
let mut todos = data.lock().unwrap();
if let Some(todo) = todos.iter_mut().find(|t| t.id == *path) {
todo.title = body.title.clone();
todo.completed = body.completed;
HttpResponse::Ok().json(todo)
} else {
HttpResponse::NotFound().body("Todo not found")
}
}
async fn delete_todo(
data: web::Data<Mutex<Vec<Todo>>>,
path: web::Path<u64>,
) -> HttpResponse {
let mut todos = data.lock().unwrap();
let len_before = todos.len();
todos.retain(|t| t.id != *path);
if todos.len() < len_before {
HttpResponse::NoContent().finish()
} else {
HttpResponse::NotFound().body("Todo not found")
}
}
Регистрираме routes-ите
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let app_data = web::Data::new(Mutex::new(Vec::<Todo>::new()));
HttpServer::new(move || {
App::new()
.app_data(app_data.clone())
.route("/health", web::get().to(health_check))
.route("/todos", web::get().to(get_todos))
.route("/todos", web::post().to(create_todo))
.route("/todos/{id}", web::put().to(update_todo))
.route("/todos/{id}", web::delete().to(delete_todo))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Какво прави това специално?
Производителността не е компромис. Единственото нещо, с което се отказваш, е малкото време за настройка в началото. След това — микроскопични latency-та, нулеви GC паузи, memory safety без runtime.
Ако търсиш сериозен backend, който да не те предаде под натоварване, Rust + Actix-web е комбинация, която си струва да изпробваш. Дори и само за да разбереш защо толкова много хора са толкова въодушевени от екосистемата.