Как написать REST API на Rust за 30 минут: пошаговое руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему Rust для REST API?

Rust — это современный системный язык программирования, который сочетает в себе производительность C++ и безопасность памяти, гарантированную на этапе компиляции. В последние годы Rust активно используется для создания высоконагруженных веб-сервисов и микросервисов. В этой статье мы покажем, как за 30 минут написать полноценное REST API на Rust с помощью фреймворка Actix-web и библиотеки Serde.

Почему стоит выбрать Rust для вашего API?

  • Производительность: Rust работает на уровне C/C++, что критично для высоконагруженных систем.
  • Безопасность: Система владения (ownership) и заимствования (borrowing) исключает целые классы ошибок, связанных с памятью.
  • Асинхронность: Встроенная поддержка async/await позволяет эффективно обрабатывать тысячи соединений.
  • Экосистема: Cargo (пакетный менеджер) и crates.io делают управление зависимостями простым и удобным.

Мы создадим простое API для управления списком задач (Todo list), которое будет поддерживать CRUD-операции. Вы научитесь настраивать маршруты, обрабатывать JSON и работать с состоянием приложения.

Шаг 1: Настройка проекта и установка зависимостей

Первым делом создадим новый проект на Rust с помощью Cargo. Откройте терминал и выполните:

cargo new rust_todo_api
cd rust_todo_api

Теперь отредактируем файл Cargo.toml, добавив необходимые зависимости:

[package]
name = "rust_todo_api"
version = "0.1.0"
edition = "2021"

[dependencies]
actix-web = "4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }
uuid = { version = "1", features = ["v4"] }

Разберем, зачем нужна каждая библиотека:

  • actix-web — производительный асинхронный веб-фреймворк.
  • serde — библиотека для сериализации и десериализации данных (JSON).
  • serde_json — поддержка формата JSON для serde.
  • tokio — асинхронный рантайм для Rust (actix-web использует его по умолчанию).
  • uuid — генерация уникальных идентификаторов для наших задач.

После добавления зависимостей выполните cargo build, чтобы скачать и скомпилировать все пакеты. Это может занять несколько минут при первом запуске.

Шаг 2: Создание модели данных и структуры приложения

Создадим файл src/main.rs и определим модель нашей задачи. Мы будем использовать структуру TodoItem с полями id, title и completed:

use actix_web::{web, App, HttpServer, HttpResponse, middleware};
use serde::{Deserialize, Serialize};
use std::sync::Mutex;
use uuid::Uuid;

#[derive(Debug, Serialize, Deserialize, Clone)]
struct TodoItem {
    id: String,
    title: String,
    completed: bool,
}

#[derive(Debug, Deserialize)]
struct CreateTodoRequest {
    title: String,
}

#[derive(Debug, Deserialize)]
struct UpdateTodoRequest {
    title: Option<String>,
    completed: Option<bool>,
}

struct AppState {
    todos: Mutex<Vec<TodoItem>>,
}

Обратите внимание на важные детали:

  • Serialize и Deserialize — трейты из serde, которые автоматически генерируют код для работы с JSON.
  • Mutex — используется для безопасного доступа к общему состоянию из нескольких потоков.
  • Uuid — генерирует уникальные идентификаторы для каждой задачи.

Теперь добавим функцию main, которая будет запускать сервер и инициализировать состояние:

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let app_state = web::Data::new(AppState {
        todos: Mutex::new(Vec::new()),
    });

    println!("Сервер запущен на http://127.0.0.1:8080");

    HttpServer::new(move || {
        App::new()
            .app_data(app_state.clone())
            .route("/todos", web::get().to(get_todos))
            .route("/todos", web::post().to(create_todo))
            .route("/todos/{id}", web::get().to(get_todo_by_id))
            .route("/todos/{id}", web::put().to(update_todo))
            .route("/todos/{id}", web::delete().to(delete_todo))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Мы определили 5 маршрутов для полного CRUD. Теперь реализуем каждый обработчик.

Шаг 3: Реализация CRUD-обработчиков

Добавим функции-обработчики после структуры AppState. Начнем с получения всех задач:

async fn get_todos(data: web::Data<AppState>) -> HttpResponse {
    let todos = data.todos.lock().unwrap();
    HttpResponse::Ok().json(&*todos)
}

async fn create_todo(
    data: web::Data<AppState>,
    body: web::Json<CreateTodoRequest>,
) -> HttpResponse {
    let mut todos = data.todos.lock().unwrap();
    let new_todo = TodoItem {
        id: Uuid::new_v4().to_string(),
        title: body.title.clone(),
        completed: false,
    };
    todos.push(new_todo.clone());
    HttpResponse::Created().json(&new_todo)
}

async fn get_todo_by_id(
    data: web::Data<AppState>,
    path: web::Path<String>,
) -> HttpResponse {
    let id = path.into_inner();
    let todos = data.todos.lock().unwrap();
    
    match todos.iter().find(|todo| todo.id == id) {
        Some(todo) => HttpResponse::Ok().json(todo),
        None => HttpResponse::NotFound().json({"error": "Todo not found"}),
    }
}

async fn update_todo(
    data: web::Data<AppState>,
    path: web::Path<String>,
    body: web::Json<UpdateTodoRequest>,
) -> HttpResponse {
    let id = path.into_inner();
    let mut todos = data.todos.lock().unwrap();
    
    if let Some(todo) = todos.iter_mut().find(|todo| todo                

Блоги

Рекомендаций по книгам