You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust泛型仓储实现中的递归错误E0275排查与解决

Rust中使用async_trait和diesel_async实现泛型仓储的递归错误E0275解决

问题概述

在Rust中基于async_trait和diesel_async实现通用插入仓储时,编译阶段触发递归错误E0275,该错误与Insertable trait的实现相关,根源是插入表达式的trait要求过载,导致编译器进行过度深层的trait评估。

当前代码通过diesel::insert_into(...).values(&item).returning(...)构建插入查询,触发了上述问题。编译器建议增加#![recursion_limit = "256"]仅能临时规避错误,将递归限制设为2048时会触发内部编译器错误(ICE),说明问题本质是插入查询的trait要求和关联类型过于复杂,无法通过调整递归限制解决,需要重构简化插入逻辑。

错误输出

PS D:\my-proyects\Vornix-azure\Ecomerce-Optica\backend\vornix-ecomerce-erp-service> cargo run
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
package:   D:\my-proyects\Vornix-azure\Ecomerce-Optica\backend\vornix-billing-service\Cargo.toml
workspace: D:\my-proyects\Vornix-azure\Ecomerce-Optica\backend\Cargo.toml
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
package:   D:\my-proyects\Vornix-azure\Ecomerce-Optica\backend\vornix-ecomerce-erp-service\Cargo.toml
workspace: D:\my-proyects\Vornix-azure\Ecomerce-Optica\backend\Cargo.toml
warning: virtual workspace defaulting to `resolver = "1"` despite one or more workspace members being on edition 2021 which implies `resolver = "2"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
note: for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
   Compiling vornix-core-rs v0.1.0 (D:\my-proyects\Vornix-azure\Ecomerce-Optica\backend\vornix-core-rs)
error[E0275]: overflow evaluating the requirement `diesel::expression::operators::Eq<_, &_>: Insertable<_>`
  |
  = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`vornix_core_rs`)
  = note: required for `&diesel::expression::operators::Eq<_, _>` to implement `Insertable<_>`
  = note: 126 redundant requirements hidden
  = note: required for `&Option<Option<Option<Option<Option<Option<...>>>>>>` to implement `Insertable<_>`
  = note: the full name for the type has been written to 'D:\my-proyects\Vornix-azure\Ecomerce-Optica\backend\target\debug\deps\vornix_core_rs-da120415f0e5dafb.long-type-12242326637788567344.txt'
  = note: consider using `--verbose` to print the full type name to the console

For more information about this error, try `rustc --explain E0275`.
error: could not compile `vornix-core-rs` (lib) due to 1 previous error

当前实现代码

use async_trait::async_trait;
use diesel::{
    pg::Pg,
    query_builder::{AsQuery, InsertStatement, QueryFragment}, 
    Identifiable, Insertable, Queryable, Selectable,
    Table,
    sql_types::SingleValue, QuerySource
};
use diesel_async::{AsyncConnection, RunQueryDsl, methods::LoadQuery};

// 用于避免递归重构的主trait
#[async_trait]
pub trait BaseRepository: Send + 'static {
    // 明确定义关联类型
    type Entity: Identifiable + Send + 'static;
    type Table: Table + QuerySource + Send + 'static;
    type Conn: AsyncConnection<Backend = Pg> + Send + 'static;
    type Schema;

    // 获取表的方法
    fn table() -> Self::Table;

    // 重构后的插入方法
    async fn insert<I>(
        &self,
        conn: &mut Self::Conn,
        item: I,
    ) -> Result<Self::Entity, diesel::result::Error>
    where
        Self::Entity: Queryable<Self::Schema, Pg> + Selectable<Pg> + 'static,
        Self::Schema: SingleValue + Send + 'static,
        I: Insertable<Self::Table> + Send + 'static,
        I::Values: QueryFragment<Pg> + Send + 'static,
        InsertStatement<Self::Table, I::Values>: AsQuery + Send + 'static,
        <InsertStatement<Self::Table, I::Values> as AsQuery>::Query: 
            LoadQuery<'static, Self::Conn, Self::Entity> + Send,
        <Self::Table as QuerySource>::FromClause: Send,
    {
        let query = diesel::insert_into(Self::table())
            .values(&item)
            .returning(Self::table().all_columns());
        
        query.get_result(conn).await
    }
}

仓储实现示例

#[async_trait]
impl BaseRepository for UserRepository {
    type Entity = NewUser;
    type Table = users::table;
    type Conn = diesel_async::pooled_connection::AsyncDieselConnection<Pg>;
    type Schema = (diesel::sql_types::Integer, diesel::sql_types::Text);

    fn table() -> Self::Table {
        users::table
    }
}

递归限制设为2048时的ICE错误

741:     0x7ff82e25b71f - <rustc_trait_selection[696cc11a6b5294cd]::traits::select::SelectionContext>::poly_select
742:     0x7ff82e1084ef - core[b826f7616f2dc1fa]::slice::sort::unstable::heapsort::heapsort::<rustc_span[3904f55a5cfd74f4]::def_id::DefId, <[rustc_span[3904f55a5cfd74f4]::def_id::DefId]>::sort_unstable_by_key<(i64, usize), rustc_trait_selection[696cc11a6b5294cd]::traits::specialize::specialization_graph_provider::{closure#0}>::{closure#0}>
743:     0x7ff82e868d21 - <rustc_data_structures[5dc475d9950331dc]::profiling::_::InternalBitFlags as core[b826f7616f2dc1fa]::str::traits::FromStr>::from_str
744:     0x7ff8b137fef0 - SetSecurityDescriptorControl
745:     0x7ff8b409bf29 - RtlUserFiberStart

error: the compiler unexpectedly panicked. this is a bug.

重构与简化建议

1. 修正Schema关联类型的约束冲突

原代码中Self::Schema: SingleValue的约束与示例中Schema为元组的定义矛盾(SingleValue要求单个SQL类型),这会导致编译器在推导Queryable时产生不必要的递归。应移除SingleValue约束,或让Schema匹配实体的完整SQL类型元组:

// 修改trait中的where约束,移除SingleValue
Self::Entity: Queryable<Self::Schema, Pg> + Selectable<Pg> + 'static,
Self::Schema: Send + 'static,

2. 简化Insert方法的泛型约束

避免在单个方法中堆积过多关联类型约束,将部分约束转移到实体或仓储的关联类型中,或减少不必要的推导:

async fn insert<I>(
    &self,
    conn: &mut Self::Conn,
    item: I,
) -> Result<Self::Entity, diesel::result::Error>
where
    I: Insertable<Self::Table> + Send + 'static,
    // 仅保留必要的执行约束,让编译器自动推导其他关联类型
    InsertStatement<Self::Table, I::Values>: LoadQuery<'_, Self::Conn, Self::Entity>,
{
    diesel::insert_into(Self::table())
        .values(item) // 直接传item,避免&item导致的额外Insertable推导
        .returning(<Self::Entity as Selectable<Pg>>::selection()) // 使用Selectable的默认选择器
        .get_result(conn)
        .await
}

3. 分离查询构建与执行逻辑

将查询构建抽为单独的非async函数,降低单个方法内的trait推导复杂度:

#[async_trait]
pub trait BaseRepository: Send + 'static {
    // ... 其他关联类型和方法 ...

    fn build_insert_query<I>(item: I) -> InsertStatement<Self::Table, I::Values>
    where
        I: Insertable<Self::Table>,
    {
        diesel::insert_into(Self::table()).values(item)
    }

    async fn insert<I>(
        &self,
        conn: &mut Self::Conn,
        item: I,
    ) -> Result<Self::Entity, diesel::result::Error>
    where
        I: Insertable<Self::Table> + Send + 'static,
        InsertStatement<Self::Table, I::Values>: LoadQuery<'_, Self::Conn, Self::Entity>,
    {
        Self::build_insert_query(item)
            .returning(<Self::Entity as Selectable<Pg>>::selection())
            .get_result(conn)
            .await
    }
}

4. 明确实体的Insertable与Selectable实现

确保NewUser等实体正确实现所需trait,避免编译器进行模糊推导:

// 确保NewUser实现Insertable,指定对应的表
#[derive(Insertable)]
#[diesel(table_name = users)]
pub struct NewUser {
    // ... 字段定义 ...
}

// 确保实体实现Selectable,让returning能正确推导返回列
#[derive(Selectable, Queryable)]
#[diesel(table_name = users)]
pub struct User {
    // ... 字段定义 ...
}

5. 避免过度泛型化

如果业务场景允许,可以为Insert方法指定更具体的类型(比如直接使用Self::Entity作为插入类型),减少泛型推导的复杂度:

async fn insert(
    &self,
    conn: &mut Self::Conn,
    item: Self::Entity,
) -> Result<Self::Entity, diesel::result::Error>
where
    Self::Entity: Insertable<Self::Table> + Queryable<Self::Schema, Pg> + Selectable<Pg> + 'static,
    InsertStatement<Self::Table, <Self::Entity as Insertable<Self::Table>>::Values>: LoadQuery<'_, Self::Conn, Self::Entity>,
{
    diesel::insert_into(Self::table())
        .values(item)
        .returning(<Self::Entity as Selectable<Pg>>::selection())
        .get_result(conn)
        .await
}

内容的提问来源于stack exchange,提问作者vornix

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 14:14:56