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