Rust vs Python для бэкенда: что выбрать на практике?
Выбор языка программирования для бэкенда — это фундаментальное решение, которое влияет на скорость разработки, производительность и стоимость поддержки проекта. Python долгое время был королём бэкенда благодаря простоте и огромному количеству библиотек. Однако Rust, язык системного программирования, стремительно ворвался в мир веб-разработки, предлагая безопасность памяти и производительность на уровне C++.
В этой статье мы не будем обсуждать абстрактные теории. Мы рассмотрим конкретные практические сценарии: написание HTTP-сервера, работу с базой данных и обработку конкурентных запросов. Вы увидите, как один и тот же код выглядит на Rust и Python, и сможете сделать осознанный выбор для своего следующего проекта.
Производительность: когда скорость имеет значение
Python — интерпретируемый язык с динамической типизацией. Это даёт гибкость, но цена — скорость. Rust компилируется в машинный код и не имеет сборщика мусора, что делает его одним из самых быстрых языков.
Рассмотрим простой пример: HTTP-сервер, который обрабатывает GET-запрос и возвращает JSON.
Python (FastAPI)
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/")
async def read_root():
return {"message": "Hello, World!"}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
Rust (Axum)
use axum::{Router, routing::get, Json};
use serde_json::{Value, json};
use std::net::SocketAddr;
async fn root() -> Json {
Json(json!({"message": "Hello, World!"}))
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(root));
let addr = SocketAddr::from(([0, 0, 0, 0], 8000));
println!("Server running on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
Результаты тестов (wrk, 100 соединений, 10 секунд):
- Python (FastAPI): ~12,000 RPS
- Rust (Axum): ~280,000 RPS
Rust показывает производительность в 20+ раз выше. Если ваш проект — это высоконагруженный API, микросервис или система реального времени, Rust — очевидный выбор. Python остаётся хорошим вариантом для прототипов и внутренних инструментов с низкой нагрузкой.
Безопасность и управление памятью
Python полностью берёт на себя управление памятью через сборщик мусора. Это удобно, но может приводить к неожиданным паузам (GC pauses) и утечкам памяти. Rust использует систему владения (ownership) и заимствования (borrowing), что гарантирует безопасность памяти на этапе компиляции.
Рассмотрим пример: функция, которая обрабатывает список пользователей и обновляет их баланс.
Python (с возможной ошибкой)
users = [{"name": "Alice", "balance": 100}, {"name": "Bob", "balance": 200}]
def update_balance(user_list, amount):
for user in user_list:
user["balance"] += amount
return user_list
# Внезапно кто-то удаляет элемент
users.pop(0)
result = update_balance(users, 50) # Работает, но логика нарушена
Rust (безопасный код)
struct User {
name: String,
balance: i32,
}
fn update_balance(users: &mut Vec<User>, amount: i32) {
for user in users.iter_mut() {
user.balance += amount;
}
}
fn main() {
let mut users = vec![
User { name: "Alice".to_string(), balance: 100 },
User { name: "Bob".to_string(), balance: 200 },
];
// Компилятор не позволит случайно изменить вектор во время итерации
update_balance(&mut users, 50);
// Попытка использовать users после перемещения приведёт к ошибке компиляции
// let other = users; // ownership moved
// println!("{}", users[0].name); // Ошибка!
}
В Rust компилятор предотвращает целые классы ошибок: гонки данных, висячие указатели, двойные освобождения памяти. Для бэкенда, где надёжность критична (финансовые системы, медицинские приложения), Rust даёт гарантии, которые Python не может предоставить.
Экосистема и скорость разработки
Здесь Python вырывается вперёд. Сотни тысяч пакетов в PyPI, зрелые фреймворки (Django, FastAPI, Flask) и огромное сообщество. Rust моложе, но его экосистема быстро растёт.
Сравним разработку REST API с базой данных PostgreSQL.
Python (FastAPI + SQLAlchemy)
from fastapi import FastAPI, Depends
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import Session, declarative_base
app = FastAPI()
engine = create_engine("postgresql://user:pass@localhost/db")
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
Base.metadata.create_all(bind=engine)
@app.get("/users/{user_id}")
def get_user(user_id: int, db: Session = Depends()):
return db.query(User).filter(User.id == user_id).first()
Rust (Actix-web + SQLx)
use actix_web::{web, App, HttpServer, Responder};
use sqlx::postgres::PgPoolOptions;
use serde::Serialize;
#[derive(Serialize, sqlx::FromRow)]
struct User {
id: i32,
name: String,
}
async fn get_user(pool: web::Data<sqlx::PgPool>, path: web::Path<i32>) -> impl Responder {
let user_id = path.into_inner();
let user = sqlx::query_as::<_, User>("SELECT id, name FROM users WHERE id = $1")
.bind(user_id)
.fetch_one(pool.get_ref())
.await
.unwrap();
web::Json(user)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let pool = PgPoolOptions::new()
.max_connections(5)
.connect("postgres://user:pass@localhost/db")
.await
.unwrap();
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(pool.clone()))
.route("/users/{id}", web::get().to(get_user))
})
.bind("127.0