Работа с базами данных в Rust: PostgreSQL и MongoDB для начинающих
Rust — это современный системный язык программирования, который завоевывает популярность благодаря своей производительности и безопасности. Но как работать с базами данных в Rust? В этой статье мы разберем подключение и выполнение основных операций с двумя популярными СУБД: реляционной PostgreSQL и документо-ориентированной MongoDB. Материал рассчитан на начинающих разработчиков, знакомых с основами Rust.
Почему Rust и базы данных — хорошая комбинация?
Rust предлагает строгую систему типов и гарантии безопасности памяти, что особенно ценно при работе с данными. Ошибки доступа к памяти, типичные для C/C++, исключены на этапе компиляции. Кроме того, экосистема Rust включает зрелые библиотеки для взаимодействия с базами данных: diesel для PostgreSQL и официальный драйвер для MongoDB.
В этой статье мы научимся:
- Подключаться к PostgreSQL и MongoDB из Rust
- Выполнять CRUD-операции (создание, чтение, обновление, удаление)
- Работать с результатами запросов безопасно и эффективно
1. Работа с PostgreSQL через библиотеку Diesel
Diesel — это ORM и query builder для Rust, который обеспечивает безопасность типов на уровне компиляции. Для начала работы добавьте зависимости в Cargo.toml:
[dependencies]
diesel = { version = "2.1", features = ["postgres"] }
dotenvy = "0.15"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }
Настройка подключения
Создайте файл .env с URL вашей базы данных:
DATABASE_URL=postgres://user:password@localhost/mydb
Теперь напишем код для подключения и создания таблицы:
use diesel::prelude::*;
use diesel::pg::PgConnection;
use dotenvy::dotenv;
use std::env;
pub fn establish_connection() -> PgConnection {
dotenv().ok();
let database_url = env::var("DATABASE_URL")
.expect("DATABASE_URL must be set");
PgConnection::establish(&database_url)
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
}
Определение схемы и модели
Создадим таблицу users и соответствующую модель Rust:
// schema.rs
table! {
users (id) {
id -> Int4,
name -> Varchar,
email -> Varchar,
created_at -> Timestamp,
}
}
// models.rs
#[derive(Queryable, Selectable)]
#[diesel(table_name = crate::schema::users)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct User {
pub id: i32,
pub name: String,
pub email: String,
pub created_at: chrono::NaiveDateTime,
}
#[derive(Insertable)]
#[diesel(table_name = crate::schema::users)]
pub struct NewUser {
pub name: String,
pub email: String,
}
CRUD-операции
Пример создания и чтения пользователей:
use crate::models::{User, NewUser};
use crate::schema::users;
pub fn create_user(conn: &mut PgConnection, name: &str, email: &str) -> User {
let new_user = NewUser {
name: name.to_string(),
email: email.to_string(),
};
diesel::insert_into(users::table)
.values(&new_user)
.returning(User::as_returning())
.get_result(conn)
.expect("Error saving new user")
}
pub fn get_all_users(conn: &mut PgConnection) -> Vec {
users::table
.load::(conn)
.expect("Error loading users")
}
Как видите, Diesel проверяет типы и имена столбцов на этапе компиляции. Ошибка в имени поля приведет к ошибке компиляции, а не к runtime-сбою.
2. Работа с MongoDB через официальный драйвер
MongoDB — это NoSQL база данных, хранящая данные в формате JSON-подобных документов. Официальный драйвер для Rust является асинхронным и основан на tokio. Добавьте зависимости:
[dependencies]
mongodb = "2.8"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Подключение и структура данных
Определим модель пользователя и функцию подключения:
use mongodb::{Client, Database};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct User {
pub name: String,
pub email: String,
pub age: u32,
}
pub async fn get_database() -> Database {
let client = Client::with_uri_str("mongodb://localhost:27017")
.await
.expect("Failed to connect to MongoDB");
client.database("mydb")
}
CRUD-операции с MongoDB
Пример вставки и поиска документов:
use mongodb::bson::doc;
use mongodb::options::FindOptions;
pub async fn insert_user(db: &Database, user: &User) {
let collection = db.collection::("users");
collection.insert_one(user)
.await
.expect("Failed to insert user");
println!("User inserted: {:?}", user);
}
pub async fn find_users_by_age(db: &Database, min_age: u32) -> Vec {
let collection = db.collection::("users");
let filter = doc! { "age": { "$gte": min_age as i32 } };
let options = FindOptions::builder()
.sort(doc! { "name": 1 })
.build();
let mut cursor = collection.find(filter)
.with_options(options)
.await
.expect("Failed to execute find query");
let mut results = Vec::new();
while let Some(user) = cursor.try_next().await.expect("Cursor error") {
results.push(user);
}
results
}
MongoDB не требует предопределенной схемы, что дает гибкость, но требует внимательности при работе с документами разной структуры.
3. Сравнение и выбор подхода
Какую базу данных выбрать для вашего проекта на Rust? Рассмотрим ключевые различия:
- PostgreSQL + Diesel — идеально для структурированных данных с четкими связями. Обеспечивает максимальную безопасность типов и целостность данных через миграции.
- MongoDB — подходит для гибких схем, быстрого прототипирования и работы с большими объемами неструктурированных данных. Асинхронный драйвер легко интегрируется с
tokio.