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

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

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

Начать курс

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

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

В этой статье мы создадим простое, но рабочее REST API для управления списком задач (Todo List). Вы научитесь:

  • Настраивать проект на Rust с Actix-web
  • Реализовывать CRUD-операции (Create, Read, Update, Delete)
  • Работать с JSON-запросами и ответами
  • Использовать маршрутизацию и обработчики

Готовы? Поехали!

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

Для начала убедитесь, что у вас установлен Rust. Если нет — выполните команду:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Создайте новый проект:

cargo new rust_todo_api
cd rust_todo_api

Откройте Cargo.toml и добавьте следующие зависимости:

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

Здесь:

  • actix-web — мощный асинхронный веб-фреймворк
  • serde — для сериализации/десериализации JSON
  • tokio — асинхронный рантайм (Actix-web использует его под капотом)

Выполните cargo build, чтобы скачать и скомпилировать зависимости. Это займёт 2-3 минуты.

Шаг 2: Создание модели данных и хранилища

В этом проекте мы не будем подключать базу данных — для простоты используем вектор в памяти. Создадим структуру для задачи.

Откройте src/main.rs и напишите следующий код:

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

// Модель задачи
#[derive(Serialize, Deserialize, Clone)]
struct Task {
    id: u32,
    title: String,
    completed: bool,
}

// Хранилище задач (потокобезопасное)
struct AppState {
    tasks: Mutex<Vec<Task>>,
}

Пояснения:

  • #[derive(Serialize, Deserialize)] — автоматически генерирует методы для работы с JSON
  • Mutex — обеспечивает безопасный доступ к данным из нескольких потоков
  • AppState — будет храниться в состоянии приложения

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

Теперь добавим функции-обработчики для каждого HTTP-метода. Вставьте следующий код после объявления структур:

// Получить все задачи
async fn get_tasks(data: web::Data<AppState>) -> HttpResponse {
    let tasks = data.tasks.lock().unwrap();
    HttpResponse::Ok().json(tasks.clone())
}

// Создать новую задачу
async fn create_task(data: web::Data<AppState>, body: web::Json<Task>) -> HttpResponse {
    let mut tasks = data.tasks.lock().unwrap();
    let new_id = tasks.len() as u32 + 1;
    let task = Task {
        id: new_id,
        title: body.title.clone(),
        completed: body.completed,
    };
    tasks.push(task.clone());
    HttpResponse::Created().json(task)
}

// Обновить задачу
async fn update_task(
    data: web::Data<AppState>,
    path: web::Path<u32>,
    body: web::Json<Task>,
) -> HttpResponse {
    let mut tasks = data.tasks.lock().unwrap();
    let id = path.into_inner();
    if let Some(task) = tasks.iter_mut().find(|t| t.id == id) {
        task.title = body.title.clone();
        task.completed = body.completed;
        HttpResponse::Ok().json(task.clone())
    } else {
        HttpResponse::NotFound().body("Task not found")
    }
}

// Удалить задачу
async fn delete_task(data: web::Data<AppState>, path: web::Path<u32>) -> HttpResponse {
    let mut tasks = data.tasks.lock().unwrap();
    let id = path.into_inner();
    let initial_len = tasks.len();
    tasks.retain(|t| t.id != id);
    if tasks.len() < initial_len {
        HttpResponse::Ok().body("Task deleted")
    } else {
        HttpResponse::NotFound().body("Task not found")
    }
}

Каждый обработчик принимает web::Data<AppState> — это способ Actix-web передавать общее состояние. Обратите внимание на использование web::Path для извлечения ID из URL.

Шаг 4: Настройка маршрутизации и запуск сервера

Осталось собрать всё вместе и запустить сервер. Добавьте функцию main:

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let app_state = web::Data::new(AppState {
        tasks: Mutex::new(vec![
            Task {
                id: 1,
                title: "Изучить Rust".to_string(),
                completed: false,
            },
            Task {
                id: 2,
                title: "Написать REST API".to_string(),
                completed: true,
            },
        ]),
    });

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

    HttpServer::new(move || {
        App::new()
            .app_data(app_state.clone())
            .route("/tasks", web::get().to(get_tasks))
            .route("/tasks", web::post().to(create_task))
            .route("/tasks/{id}", web::put().to(update_task))
            .route("/tasks/{id}", web::delete().to(delete_task))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Запустите сервер командой cargo run. Если всё сделано правильно, вы увидите сообщение "Сервер запущен на http://127.0.0.1:8080".

Тестирование API

Теперь протестируем наше API с помощью curl или Postman: