Diesel ORM Rust: Guia Completo com Exemplos | Rust Brasil

Guia do Diesel ORM em Rust: schema DSL, Queryable, migrations, queries complexas e joins. ORM seguro e performático.

Introdução

O Diesel é o ORM (Object-Relational Mapping) mais maduro e battle-tested do ecossistema Rust. Ele adota uma filosofia de “zero-cost abstractions” para acesso a banco de dados, onde queries são construídas usando uma DSL (Domain Specific Language) fortemente tipada que é verificada em tempo de compilação.

Diferente de ORMs tradicionais em outras linguagens, o Diesel não usa reflection, strings mágicas ou geração de código em runtime. Cada query é uma expressão de tipos que o compilador Rust valida completamente, garantindo que erros de SQL – colunas inexistentes, tipos incompatíveis, joins inválidos – sejam capturados antes do código rodar.

Por que usar o Diesel?

  • Type safety completo: queries são expressões de tipos verificadas pelo compilador
  • Zero-cost abstractions: o Diesel gera SQL otimizado sem overhead
  • Schema DSL: represente o esquema do banco como código Rust
  • Migrations: sistema de migrations integrado via CLI
  • Query builder poderoso: queries complexas com joins, subqueries e aggregations
  • Extensível: suporte a tipos customizados, funções SQL e extensões do banco
  • Maduro: usado em produção por anos, incluindo no crates.io

Instalação

Adicione o Diesel ao seu Cargo.toml:

[dependencies]
diesel = { version = "2.2", features = ["postgres"] }
dotenvy = "0.15"

Instale a CLI do Diesel:

# Com suporte a PostgreSQL
cargo install diesel_cli --no-default-features --features postgres

# Com suporte a SQLite
cargo install diesel_cli --no-default-features --features sqlite

# Com suporte a múltiplos bancos
cargo install diesel_cli --no-default-features --features "postgres sqlite mysql"

Configure a conexão com o banco:

# Criar arquivo .env
echo 'DATABASE_URL=postgres://usuario:senha@localhost:5432/meu_banco' > .env

# Inicializar o projeto Diesel (cria diesel.toml e pasta migrations/)
diesel setup

Features disponíveis:

[dependencies]
diesel = { version = "2.2", features = [
    "postgres",          # Suporte a PostgreSQL
    # "mysql",           # Suporte a MySQL
    # "sqlite",          # Suporte a SQLite
    "chrono",            # Tipos de data/hora
    "serde_json",        # Tipos JSON/JSONB
    "uuid",              # Tipo UUID
    "r2d2",              # Connection pooling
] }

Uso Básico

Criando migrations

# Criar uma nova migration
diesel migration generate criar_tabela_posts

# Isso cria dois arquivos:
# migrations/20260227120000_criar_tabela_posts/up.sql
# migrations/20260227120000_criar_tabela_posts/down.sql

Escreva as migrations:

-- up.sql
CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    titulo VARCHAR(200) NOT NULL,
    corpo TEXT NOT NULL,
    publicado BOOLEAN NOT NULL DEFAULT false,
    criado_em TIMESTAMP NOT NULL DEFAULT NOW(),
    atualizado_em TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE TABLE comentarios (
    id SERIAL PRIMARY KEY,
    post_id INTEGER NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
    autor VARCHAR(100) NOT NULL,
    texto TEXT NOT NULL,
    criado_em TIMESTAMP NOT NULL DEFAULT NOW()
);
-- down.sql
DROP TABLE comentarios;
DROP TABLE posts;

Execute as migrations:

# Executar migrations pendentes
diesel migration run

# Reverter última migration
diesel migration revert

# Refazer última migration (revert + run)
diesel migration redo

Após executar, o Diesel gera automaticamente o arquivo src/schema.rs:

// src/schema.rs (gerado automaticamente)
diesel::table! {
    posts (id) {
        id -> Int4,
        titulo -> Varchar,
        corpo -> Text,
        publicado -> Bool,
        criado_em -> Timestamp,
        atualizado_em -> Timestamp,
    }
}

diesel::table! {
    comentarios (id) {
        id -> Int4,
        post_id -> Int4,
        autor -> Varchar,
        texto -> Text,
        criado_em -> Timestamp,
    }
}

diesel::joinable!(comentarios -> posts (post_id));
diesel::allow_tables_to_appear_in_same_query!(posts, comentarios);

Definindo modelos

use diesel::prelude::*;
use chrono::NaiveDateTime;
use crate::schema::{posts, comentarios};

// Modelo para leitura (SELECT)
#[derive(Debug, Queryable, Selectable)]
#[diesel(table_name = posts)]
pub struct Post {
    pub id: i32,
    pub titulo: String,
    pub corpo: String,
    pub publicado: bool,
    pub criado_em: NaiveDateTime,
    pub atualizado_em: NaiveDateTime,
}

// Modelo para inserção (INSERT)
#[derive(Debug, Insertable)]
#[diesel(table_name = posts)]
pub struct NovoPost<'a> {
    pub titulo: &'a str,
    pub corpo: &'a str,
    pub publicado: bool,
}

// Modelo para atualização (UPDATE)
#[derive(Debug, AsChangeset)]
#[diesel(table_name = posts)]
pub struct AtualizarPost<'a> {
    pub titulo: Option<&'a str>,
    pub corpo: Option<&'a str>,
    pub publicado: Option<bool>,
}

// Modelo de comentário
#[derive(Debug, Queryable, Selectable)]
#[diesel(table_name = comentarios)]
#[diesel(belongs_to(Post))]
pub struct Comentario {
    pub id: i32,
    pub post_id: i32,
    pub autor: String,
    pub texto: String,
    pub criado_em: NaiveDateTime,
}

#[derive(Debug, Insertable)]
#[diesel(table_name = comentarios)]
pub struct NovoComentario<'a> {
    pub post_id: i32,
    pub autor: &'a str,
    pub texto: &'a str,
}

Operações CRUD básicas

use diesel::prelude::*;
use diesel::PgConnection;

// Estabelecer conexão
fn criar_conexao() -> PgConnection {
    dotenvy::dotenv().ok();
    let database_url = std::env::var("DATABASE_URL")
        .expect("DATABASE_URL deve estar definida");
    PgConnection::establish(&database_url)
        .expect("Erro ao conectar ao banco")
}

// CREATE
fn criar_post(conn: &mut PgConnection, titulo: &str, corpo: &str) -> Post {
    use crate::schema::posts;

    let novo_post = NovoPost {
        titulo,
        corpo,
        publicado: false,
    };

    diesel::insert_into(posts::table)
        .values(&novo_post)
        .returning(Post::as_returning())
        .get_result(conn)
        .expect("Erro ao criar post")
}

// READ - Listar todos
fn listar_posts(conn: &mut PgConnection) -> Vec<Post> {
    use crate::schema::posts::dsl::*;

    posts
        .filter(publicado.eq(true))
        .order(criado_em.desc())
        .limit(10)
        .select(Post::as_select())
        .load(conn)
        .expect("Erro ao listar posts")
}

// READ - Buscar por ID
fn obter_post(conn: &mut PgConnection, post_id: i32) -> Option<Post> {
    use crate::schema::posts::dsl::*;

    posts
        .find(post_id)
        .select(Post::as_select())
        .first(conn)
        .optional()
        .expect("Erro ao buscar post")
}

// UPDATE
fn atualizar_post(
    conn: &mut PgConnection,
    post_id: i32,
    dados: &AtualizarPost,
) -> Option<Post> {
    use crate::schema::posts::dsl::*;

    diesel::update(posts.find(post_id))
        .set(dados)
        .returning(Post::as_returning())
        .get_result(conn)
        .optional()
        .expect("Erro ao atualizar post")
}

// DELETE
fn deletar_post(conn: &mut PgConnection, post_id: i32) -> bool {
    use crate::schema::posts::dsl::*;

    let linhas = diesel::delete(posts.find(post_id))
        .execute(conn)
        .expect("Erro ao deletar post");

    linhas > 0
}

// Publicar post
fn publicar_post(conn: &mut PgConnection, post_id: i32) -> Option<Post> {
    use crate::schema::posts::dsl::*;

    diesel::update(posts.find(post_id))
        .set(publicado.eq(true))
        .returning(Post::as_returning())
        .get_result(conn)
        .optional()
        .expect("Erro ao publicar post")
}

Recursos Avançados

Queries complexas com filtros

use diesel::prelude::*;

fn buscar_posts(
    conn: &mut PgConnection,
    termo: Option<&str>,
    apenas_publicados: bool,
    limite: i64,
    pagina: i64,
) -> Vec<Post> {
    use crate::schema::posts::dsl::*;

    let mut query = posts.into_boxed();

    if apenas_publicados {
        query = query.filter(publicado.eq(true));
    }

    if let Some(termo) = termo {
        let padrao = format!("%{}%", termo);
        query = query.filter(
            titulo.ilike(padrao.clone())
                .or(corpo.ilike(padrao))
        );
    }

    query
        .order(criado_em.desc())
        .limit(limite)
        .offset((pagina - 1) * limite)
        .select(Post::as_select())
        .load(conn)
        .expect("Erro ao buscar posts")
}

Joins

use diesel::prelude::*;

// Post com contagem de comentários
#[derive(Debug, Queryable)]
struct PostComContagem {
    post: Post,
    total_comentarios: i64,
}

fn posts_com_comentarios(conn: &mut PgConnection) -> Vec<(Post, Vec<Comentario>)> {
    use crate::schema::{posts, comentarios};

    let todos_posts = posts::table
        .filter(posts::publicado.eq(true))
        .select(Post::as_select())
        .load(conn)
        .expect("Erro ao carregar posts");

    let todos_comentarios = Comentario::belonging_to(&todos_posts)
        .select(Comentario::as_select())
        .load(conn)
        .expect("Erro ao carregar comentários");

    let comentarios_por_post = todos_comentarios
        .grouped_by(&todos_posts);

    todos_posts
        .into_iter()
        .zip(comentarios_por_post)
        .collect()
}

// Join explícito
fn posts_com_autores(conn: &mut PgConnection) -> Vec<(Post, Comentario)> {
    use crate::schema::{posts, comentarios};

    posts::table
        .inner_join(comentarios::table)
        .filter(posts::publicado.eq(true))
        .select((Post::as_select(), Comentario::as_select()))
        .load(conn)
        .expect("Erro no join")
}

Aggregations

use diesel::prelude::*;
use diesel::dsl::count_star;

fn estatisticas_posts(conn: &mut PgConnection) {
    use crate::schema::posts::dsl::*;

    // Contagem total
    let total: i64 = posts
        .count()
        .get_result(conn)
        .expect("Erro ao contar");

    println!("Total de posts: {}", total);

    // Contagem por status
    let publicados: i64 = posts
        .filter(publicado.eq(true))
        .count()
        .get_result(conn)
        .expect("Erro ao contar publicados");

    let rascunhos: i64 = posts
        .filter(publicado.eq(false))
        .count()
        .get_result(conn)
        .expect("Erro ao contar rascunhos");

    println!("Publicados: {}, Rascunhos: {}", publicados, rascunhos);
}

fn contagem_comentarios_por_post(
    conn: &mut PgConnection,
) -> Vec<(i32, String, i64)> {
    use crate::schema::{posts, comentarios};

    posts::table
        .left_join(comentarios::table)
        .group_by((posts::id, posts::titulo))
        .select((
            posts::id,
            posts::titulo,
            diesel::dsl::count(comentarios::id.nullable()),
        ))
        .order(diesel::dsl::count(comentarios::id.nullable()).desc())
        .load(conn)
        .expect("Erro na agregação")
}

Inserção em batch

use diesel::prelude::*;

fn inserir_varios_posts(conn: &mut PgConnection, novos: &[NovoPost]) -> Vec<Post> {
    use crate::schema::posts;

    diesel::insert_into(posts::table)
        .values(novos)
        .returning(Post::as_returning())
        .get_results(conn)
        .expect("Erro ao inserir em batch")
}

// Upsert (INSERT ... ON CONFLICT)
fn upsert_post(conn: &mut PgConnection, novo: &NovoPost) -> Post {
    use crate::schema::posts;

    diesel::insert_into(posts::table)
        .values(novo)
        .on_conflict(posts::titulo)
        .do_update()
        .set(posts::corpo.eq(novo.corpo))
        .returning(Post::as_returning())
        .get_result(conn)
        .expect("Erro no upsert")
}

Connection pooling com r2d2

use diesel::r2d2::{self, ConnectionManager};
use diesel::PgConnection;

type DbPool = r2d2::Pool<ConnectionManager<PgConnection>>;

fn criar_pool() -> DbPool {
    dotenvy::dotenv().ok();
    let database_url = std::env::var("DATABASE_URL")
        .expect("DATABASE_URL deve estar definida");

    let manager = ConnectionManager::<PgConnection>::new(database_url);

    r2d2::Pool::builder()
        .max_size(15)
        .min_idle(Some(5))
        .build(manager)
        .expect("Falha ao criar pool de conexões")
}

// Usando em um handler web
fn listar_posts_handler(pool: &DbPool) -> Vec<Post> {
    use crate::schema::posts::dsl::*;

    let mut conn = pool.get().expect("Falha ao obter conexão do pool");

    posts
        .filter(publicado.eq(true))
        .select(Post::as_select())
        .load(&mut conn)
        .expect("Erro ao listar posts")
}

Transações

use diesel::prelude::*;

fn criar_post_com_comentario(
    conn: &mut PgConnection,
    titulo: &str,
    corpo: &str,
    autor_comentario: &str,
    texto_comentario: &str,
) -> Result<(Post, Comentario), diesel::result::Error> {
    use crate::schema::{posts, comentarios};

    conn.transaction(|conn| {
        // Criar o post
        let post = diesel::insert_into(posts::table)
            .values(&NovoPost {
                titulo,
                corpo,
                publicado: true,
            })
            .returning(Post::as_returning())
            .get_result(conn)?;

        // Criar o comentário inicial
        let comentario = diesel::insert_into(comentarios::table)
            .values(&NovoComentario {
                post_id: post.id,
                autor: autor_comentario,
                texto: texto_comentario,
            })
            .returning(Comentario::as_returning())
            .get_result(conn)?;

        Ok((post, comentario))
    })
}

SQL customizado

use diesel::prelude::*;
use diesel::sql_query;
use diesel::sql_types::{Integer, Text, Bool};

#[derive(Debug, QueryableByName)]
struct PostResumo {
    #[diesel(sql_type = Integer)]
    id: i32,
    #[diesel(sql_type = Text)]
    titulo: String,
    #[diesel(sql_type = diesel::sql_types::BigInt)]
    total_comentarios: i64,
}

fn posts_populares(conn: &mut PgConnection) -> Vec<PostResumo> {
    sql_query(
        r#"
        SELECT p.id, p.titulo, COUNT(c.id) as total_comentarios
        FROM posts p
        LEFT JOIN comentarios c ON c.post_id = p.id
        WHERE p.publicado = true
        GROUP BY p.id, p.titulo
        HAVING COUNT(c.id) > 0
        ORDER BY total_comentarios DESC
        LIMIT 10
        "#
    )
    .load(conn)
    .expect("Erro na query SQL customizada")
}

Boas Práticas

1. Organize modelos e queries

src/
  schema.rs        # Gerado pelo Diesel
  models/
    mod.rs
    post.rs        # Modelos e queries de Post
    comentario.rs  # Modelos e queries de Comentario
  db.rs            # Pool de conexões
  main.rs

2. Use AsChangeset para atualizações parciais

#[derive(Debug, AsChangeset)]
#[diesel(table_name = posts)]
struct AtualizarPost<'a> {
    // Campos Option<T>: None = não alterar, Some(v) = alterar para v
    pub titulo: Option<&'a str>,
    pub corpo: Option<&'a str>,
    pub publicado: Option<bool>,
}

fn atualizar_titulo(conn: &mut PgConnection, id: i32, novo_titulo: &str) {
    use crate::schema::posts::dsl::*;

    let mudancas = AtualizarPost {
        titulo: Some(novo_titulo),
        corpo: None,      // Não altera
        publicado: None,  // Não altera
    };

    diesel::update(posts.find(id))
        .set(&mudancas)
        .execute(conn)
        .expect("Erro ao atualizar");
}

3. Trate erros adequadamente

use diesel::result::{Error as DieselError, DatabaseErrorKind};

fn criar_post_seguro(
    conn: &mut PgConnection,
    novo: &NovoPost,
) -> Result<Post, String> {
    use crate::schema::posts;

    diesel::insert_into(posts::table)
        .values(novo)
        .returning(Post::as_returning())
        .get_result(conn)
        .map_err(|err| match err {
            DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, info) => {
                format!("Duplicado: {}", info.message())
            }
            DieselError::DatabaseError(DatabaseErrorKind::ForeignKeyViolation, info) => {
                format!("Referência inválida: {}", info.message())
            }
            DieselError::NotFound => "Registro não encontrado".to_string(),
            _ => format!("Erro no banco: {}", err),
        })
}

4. Mantenha o schema.rs atualizado

# Regenerar schema.rs a partir do banco
diesel print-schema > src/schema.rs

# Configurar diesel.toml para gerar automaticamente
# diesel.toml:
# [print_schema]
# file = "src/schema.rs"

5. Escreva testes com banco de dados de teste

#[cfg(test)]
mod tests {
    use super::*;
    use diesel::Connection;

    fn setup_teste() -> PgConnection {
        let url = std::env::var("TEST_DATABASE_URL")
            .unwrap_or_else(|_| "postgres://teste:teste@localhost/teste".to_string());
        let mut conn = PgConnection::establish(&url).unwrap();
        conn.begin_test_transaction().unwrap();
        conn
    }

    #[test]
    fn test_criar_e_listar_posts() {
        let mut conn = setup_teste();

        let post = criar_post(&mut conn, "Teste", "Corpo do teste");
        assert_eq!(post.titulo, "Teste");
        assert!(!post.publicado);

        publicar_post(&mut conn, post.id);

        let posts = listar_posts(&mut conn);
        assert!(posts.iter().any(|p| p.id == post.id));
    }

    #[test]
    fn test_deletar_post() {
        let mut conn = setup_teste();

        let post = criar_post(&mut conn, "Para deletar", "Corpo");
        assert!(deletar_post(&mut conn, post.id));
        assert!(!deletar_post(&mut conn, post.id)); // Já deletado
    }
}

Exemplos Práticos

Aplicação de blog completa

use diesel::prelude::*;
use diesel::r2d2::{self, ConnectionManager};
use chrono::NaiveDateTime;

mod schema {
    diesel::table! {
        posts (id) {
            id -> Int4,
            titulo -> Varchar,
            slug -> Varchar,
            corpo -> Text,
            resumo -> Nullable<Text>,
            publicado -> Bool,
            criado_em -> Timestamp,
            atualizado_em -> Timestamp,
        }
    }

    diesel::table! {
        tags (id) {
            id -> Int4,
            nome -> Varchar,
        }
    }

    diesel::table! {
        post_tags (post_id, tag_id) {
            post_id -> Int4,
            tag_id -> Int4,
        }
    }

    diesel::joinable!(post_tags -> posts (post_id));
    diesel::joinable!(post_tags -> tags (tag_id));
    diesel::allow_tables_to_appear_in_same_query!(posts, tags, post_tags);
}

use schema::*;

// === Modelos ===

#[derive(Debug, Queryable, Selectable, Identifiable)]
#[diesel(table_name = posts)]
struct Post {
    id: i32,
    titulo: String,
    slug: String,
    corpo: String,
    resumo: Option<String>,
    publicado: bool,
    criado_em: NaiveDateTime,
    atualizado_em: NaiveDateTime,
}

#[derive(Debug, Insertable)]
#[diesel(table_name = posts)]
struct NovoPost<'a> {
    titulo: &'a str,
    slug: &'a str,
    corpo: &'a str,
    resumo: Option<&'a str>,
}

#[derive(Debug, Queryable, Selectable, Identifiable)]
#[diesel(table_name = tags)]
struct Tag {
    id: i32,
    nome: String,
}

#[derive(Debug, Insertable)]
#[diesel(table_name = post_tags)]
struct PostTag {
    post_id: i32,
    tag_id: i32,
}

// === Repositório de Blog ===

type DbPool = r2d2::Pool<ConnectionManager<PgConnection>>;

struct BlogRepo {
    pool: DbPool,
}

impl BlogRepo {
    fn new(database_url: &str) -> Self {
        let manager = ConnectionManager::<PgConnection>::new(database_url);
        let pool = r2d2::Pool::builder()
            .max_size(10)
            .build(manager)
            .expect("Falha ao criar pool");

        Self { pool }
    }

    fn conn(&self) -> r2d2::PooledConnection<ConnectionManager<PgConnection>> {
        self.pool.get().expect("Falha ao obter conexão")
    }

    fn criar_post(&self, titulo: &str, corpo: &str, nomes_tags: &[&str]) -> Post {
        let mut conn = self.conn();

        conn.transaction(|conn| {
            // Gerar slug a partir do título
            let slug = titulo
                .to_lowercase()
                .replace(' ', "-")
                .chars()
                .filter(|c| c.is_alphanumeric() || *c == '-')
                .collect::<String>();

            let resumo = if corpo.len() > 200 {
                Some(&corpo[..200])
            } else {
                None
            };

            // Criar o post
            let post = diesel::insert_into(posts::table)
                .values(&NovoPost {
                    titulo,
                    slug: &slug,
                    corpo,
                    resumo,
                })
                .returning(Post::as_returning())
                .get_result(conn)?;

            // Criar tags e associações
            for nome_tag in nomes_tags {
                // Upsert da tag
                let tag: Tag = diesel::insert_into(tags::table)
                    .values(tags::nome.eq(nome_tag))
                    .on_conflict(tags::nome)
                    .do_update()
                    .set(tags::nome.eq(nome_tag))
                    .returning(Tag::as_returning())
                    .get_result(conn)?;

                // Associar tag ao post
                diesel::insert_into(post_tags::table)
                    .values(&PostTag {
                        post_id: post.id,
                        tag_id: tag.id,
                    })
                    .on_conflict_do_nothing()
                    .execute(conn)?;
            }

            Ok(post)
        })
        .expect("Erro ao criar post com tags")
    }

    fn listar_publicados(&self, pagina: i64, por_pagina: i64) -> Vec<Post> {
        let mut conn = self.conn();

        posts::table
            .filter(posts::publicado.eq(true))
            .order(posts::criado_em.desc())
            .limit(por_pagina)
            .offset((pagina - 1) * por_pagina)
            .select(Post::as_select())
            .load(&mut conn)
            .expect("Erro ao listar posts")
    }

    fn obter_por_slug(&self, slug_busca: &str) -> Option<Post> {
        let mut conn = self.conn();

        posts::table
            .filter(posts::slug.eq(slug_busca))
            .select(Post::as_select())
            .first(&mut conn)
            .optional()
            .expect("Erro ao buscar post")
    }

    fn tags_do_post(&self, post_id: i32) -> Vec<Tag> {
        let mut conn = self.conn();

        tags::table
            .inner_join(post_tags::table)
            .filter(post_tags::post_id.eq(post_id))
            .select(Tag::as_select())
            .load(&mut conn)
            .expect("Erro ao buscar tags")
    }

    fn buscar(&self, termo: &str) -> Vec<Post> {
        let mut conn = self.conn();
        let padrao = format!("%{}%", termo);

        posts::table
            .filter(
                posts::publicado.eq(true).and(
                    posts::titulo.ilike(&padrao)
                        .or(posts::corpo.ilike(&padrao)),
                ),
            )
            .order(posts::criado_em.desc())
            .select(Post::as_select())
            .load(&mut conn)
            .expect("Erro ao buscar")
    }
}

fn main() {
    dotenvy::dotenv().ok();
    let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL necessária");

    let blog = BlogRepo::new(&database_url);

    // Criar um post com tags
    let post = blog.criar_post(
        "Aprendendo Diesel em Rust",
        "O Diesel é o ORM mais maduro do ecossistema Rust...",
        &["rust", "diesel", "tutorial"],
    );

    println!("Post criado: {} (slug: {})", post.titulo, post.slug);

    // Listar tags
    let tags = blog.tags_do_post(post.id);
    println!("Tags: {:?}", tags.iter().map(|t| &t.nome).collect::<Vec<_>>());

    // Buscar posts
    let resultados = blog.buscar("Diesel");
    println!("Encontrados: {} posts", resultados.len());
}

Comparação com Alternativas

CaracterísticaDieselSQLxSeaORMRusqlite
AbordagemORM com DSL tipadaSQL puro verificadoORM ActiveModelBindings SQLite
AsyncNão (síncrono)Sim (nativo)SimNão
Compile-time checkDSL tipadaquery!() macroNãoNão
Migrationsdiesel_clisqlx-clisea-orm-cliManual
Query builderDSL fortemente tipadaQueryBuilderEntidades/ActiveModelSQL puro
Poolr2d2EmbutidoEmbutidoN/A
CurvaMédia-altaBaixa-médiaMédiaBaixa
  • Diesel vs SQLx: Diesel oferece mais segurança de tipos no query building, mas é síncrono. SQLx usa SQL puro e é assíncrono. Escolha Diesel se valoriza abstração e type-safety no query builder. Escolha SQLx se precisa de async e prefere SQL puro.
  • Diesel vs SeaORM: SeaORM é construído sobre SQLx e oferece um modelo mais parecido com ORMs tradicionais. Diesel é mais maduro e com melhor type-safety.
  • Para projetos novos com Tokio/async, considere SQLx. Para projetos que valorizam abstrações de ORM fortes, Diesel é excelente.

Conclusão

O Diesel é uma escolha sólida para projetos Rust que precisam de acesso seguro e eficiente a bancos de dados. Sua DSL de queries fortemente tipada, sistema de migrations integrado e verificação em compile-time eliminam classes inteiras de bugs comuns em aplicações que acessam bancos de dados.

Apesar de ser síncrono, o Diesel compensa com uma ergonomia excelente para queries complexas, joins e agregações. Para projetos que não precisam de async no acesso ao banco (ou que usam spawn_blocking), o Diesel oferece uma experiência de desenvolvimento excepcional.

Próximos passos

  • Explore o SQLx como alternativa assíncrona
  • Use r2d2 para connection pooling em produção
  • Aprenda sobre diesel_async para integração com Tokio
  • Configure Tracing para monitorar performance de queries