Введение: Почему Rust и базы данных — идеальное сочетание
Rust — это современный системный язык программирования, который сочетает в себе производительность C/C++ с безопасностью памяти, гарантируемой компилятором. При разработке бэкенд-сервисов, веб-приложений и микросервисов практически всегда возникает необходимость взаимодействия с базами данных. В этой статье мы подробно разберём, как работать с двумя самыми популярными типами БД: реляционной PostgreSQL и документно-ориентированной MongoDB.
Мы рассмотрим подключение, выполнение CRUD-операций, транзакции и агрегации на практических примерах. Все примеры кода написаны на Rust с использованием асинхронного рантайма tokio, что даёт максимальную производительность.
1. Настройка проекта и установка зависимостей
Для начала создадим новый проект и добавим необходимые крейты. Откройте терминал и выполните:
cargo new rust_db_example
cd rust_db_example
Добавим зависимости в файл Cargo.toml:
[dependencies]
tokio = { version = "1", features = ["full"] }
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres"] }
mongodb = "2.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
Теперь структура проекта готова. Мы будем использовать sqlx для PostgreSQL — это асинхронный драйвер с поддержкой компилируемых запросов, и официальный драйвер mongodb для работы с MongoDB.
1.1. Подключение к PostgreSQL через sqlx
Напишем модуль для работы с PostgreSQL. Создадим файл src/postgres.rs:
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;
use anyhow::Result;
pub async fn connect_to_postgres() -> Result<PgPool> {
let database_url = "postgres://user:password@localhost:5432/mydb";
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(database_url)
.await?;
println!("✅ Подключились к PostgreSQL");
Ok(pool)
}
// Определяем модель пользователя
#[derive(Debug, sqlx::FromRow)]
pub struct User {
pub id: i32,
pub name: String,
pub email: String,
pub created_at: chrono::NaiveDateTime,
}
1.2. Подключение к MongoDB
Создадим файл src/mongodb.rs:
use mongodb::{Client, Collection, Database};
use mongodb::bson::{doc, Document};
use anyhow::Result;
pub async fn connect_to_mongodb() -> Result<(Database, Collection<Document>)> {
let client = Client::with_uri_str("mongodb://localhost:27017")?;
let database = client.database("mydb");
let collection = database.collection::<Document>("users");
println!("✅ Подключились к MongoDB");
Ok((database, collection))
}
2. CRUD-операции в PostgreSQL
Теперь реализуем базовые операции: создание, чтение, обновление и удаление записей.
2.1. Вставка данных (CREATE)
pub async fn create_user(pool: &PgPool, name: &str, email: &str) -> Result<User> {
let user = sqlx::query_as::<_, User>(
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email, created_at"
)
.bind(name)
.bind(email)
.fetch_one(pool)
.await?;
println!("Создан пользователь: {:?}", user);
Ok(user)
}
2.2. Чтение данных (READ)
pub async fn get_user_by_id(pool: &PgPool, user_id: i32) -> Result<Option<User>> {
let user = sqlx::query_as::<_, User>(
"SELECT id, name, email, created_at FROM users WHERE id = $1"
)
.bind(user_id)
.fetch_optional(pool)
.await?;
match user {
Some(u) => println!("Найден пользователь: {:?}", u),
None => println!("Пользователь с id={} не найден", user_id),
}
Ok(user)
}
2.3. Обновление и удаление (UPDATE/DELETE)
pub async fn update_user_email(pool: &PgPool, user_id: i32, new_email: &str) -> Result<u64> {
let rows_affected = sqlx::query(
"UPDATE users SET email = $1 WHERE id = $2"
)
.bind(new_email)
.bind(user_id)
.execute(pool)
.await?
.rows_affected();
println!("Обновлено строк: {}", rows_affected);
Ok(rows_affected)
}
pub async fn delete_user(pool: &PgPool, user_id: i32) -> Result<u64> {
let rows_affected = sqlx::query(
"DELETE FROM users WHERE id = $1"
)
.bind(user_id)
.execute(pool)
.await?
.rows_affected();
println!("Удалено строк: {}", rows_affected);
Ok(rows_affected)
}
3. Работа с MongoDB: CRUD и агрегация
MongoDB работает с документами в формате BSON. Используем крейт mongodb для выполнения операций.
3.1. Вставка и поиск документов
use mongodb::bson::{doc, Document};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct MongoUser {
pub name: String,
pub email: String,
pub age: u32,
pub tags: Vec<String>,
}
pub async fn insert_user(collection: &Collection<Document>, user: &MongoUser) -> Result<()> {
let doc = mongodb::bson::to_document(user)?;
let result = collection.insert_one(doc).await?;
println!("Вставлен документ с _id: {:?}", result.inserted_id);
Ok(())
}
pub async fn find_users_by_age(collection: &Collection<Document>, min_age: u32) -> Result<Vec<Document>> {
let filter = doc! { "age": { "$gte": min_age } };
let mut cursor = collection.find(filter).await?;
let mut users = Vec::new();
while cursor.advance().await? {
let user = cursor.current().clone();
users.push(user);
}
println!("Найдено {} пользователей", users.len());
Ok(users)
}