ORM для Rust: сравнение и примеры использования Diesel, SeaORM и SQLx

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

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

Начать курс

ORM для Rust: подробное сравнение и примеры использования

Экосистема Rust предлагает несколько мощных решений для работы с базами данных. В этой статье мы рассмотрим три наиболее популярных ORM (Object-Relational Mapping) для Rust: Diesel, SeaORM и SQLx. Каждый из них имеет свои сильные стороны и подходит для разных сценариев. Мы сравним их по ключевым характеристикам и приведём практические примеры кода.

1. Diesel — классический ORM с максимальной производительностью

Diesel — это самый зрелый ORM в экосистеме Rust. Он работает синхронно (с поддержкой асинхронности через diesel-async) и известен своей высокой производительностью и безопасностью типов.

Основные особенности:

  • Полная типобезопасность: ошибки SQL выявляются на этапе компиляции
  • Поддержка PostgreSQL, MySQL, SQLite
  • Генерация схемы через CLI-инструмент diesel_cli
  • Оптимизирован для сложных запросов и транзакций

Пример кода с Diesel:

// Определение модели
#[derive(Queryable, Insertable)]
#[diesel(table_name = crate::schema::users)]
struct User {
    id: i32,
    name: String,
    email: String,
}

// Вставка нового пользователя
fn create_user(conn: &mut PgConnection, name: &str, email: &str) -> User {
    let new_user = NewUser { name, email };
    diesel::insert_into(users::table)
        .values(&new_user)
        .get_result(conn)
        .expect("Error saving new user")
}

// Запрос всех пользователей
fn get_all_users(conn: &mut PgConnection) -> Vec<User> {
    users::table
        .load::<User>(conn)
        .expect("Error loading users")
}

2. SeaORM — асинхронный ORM с активной моделью

SeaORM — это современный асинхронный ORM, вдохновлённый Entity Framework и Active Record. Он предоставляет удобный API для построения запросов и поддерживает миграции "из коробки".

Основные особенности:

  • Асинхронность по умолчанию (на базе Tokio)
  • Активная модель: можно вызывать методы на сущностях
  • Поддержка отношений (has_many, belongs_to, many_to_many)
  • Встроенные миграции и CLI-утилита

Пример кода с SeaORM:

use sea_orm::{entity::prelude::*, Database, EntityTrait};

// Определение сущности
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "users")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    pub name: String,
    pub email: String,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

impl ActiveModelBehavior for ActiveModel {}

// Использование в коде
async fn run() {
    let db = Database::connect("postgres://user:pass@localhost/db").await.unwrap();
    
    // Вставка
    let user = ActiveModel {
        name: Set("Alice".to_owned()),
        email: Set("alice@example.com".to_owned()),
        ..Default::default()
    };
    let result = Entity::insert(user).exec(&db).await.unwrap();
    
    // Выборка всех
    let users: Vec<Model> = Entity::find().all(&db).await.unwrap();
}

3. SQLx — не ORM, а асинхронный SQL-драйвер с проверкой на этапе компиляции

SQLx занимает промежуточную позицию: это не полноценный ORM, но он предоставляет мощные инструменты для работы с SQL с проверкой запросов во время компиляции. Он идеален для тех, кто предпочитает писать чистый SQL, но хочет избежать ошибок.

Основные особенности:

  • Асинхронный (Tokio, async-std)
  • Проверка SQL-запросов на этапе компиляции через макрос sqlx::query!
  • Поддержка PostgreSQL, MySQL, SQLite, MSSQL
  • Лёгкий вес, без магии ORM

Пример кода с SQLx:

use sqlx::postgres::PgPool;

#[derive(sqlx::FromRow)]
struct User {
    id: i32,
    name: String,
    email: String,
}

async fn get_user(pool: &PgPool, user_id: i32) -> Option<User> {
    let user = sqlx::query_as::<_, User>(
        "SELECT id, name, email FROM users WHERE id = $1"
    )
    .bind(user_id)
    .fetch_optional(pool)
    .await
    .unwrap();
    user
}

#[tokio::main]
async fn main() {
    let pool = PgPool::connect("postgres                    

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