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

能否在Rust trait默认实现中调用Diesel ORM函数?

问题

我想在Rust项目中消除重复代码。现有一个Table trait,其中包含大量可通用实现的函数,这里以count函数为例:

pub trait Table {
    fn count(&self, env: &mut dyn Environment) -> Result<i64, Error>;
}

我有两个结构体FooTable和BarTable都实现了Table trait(实际项目中还有更多),它们的count实现代码几乎完全一样:

pub struct FooTable {
    pub table: foos,
}

impl Table for FooTable {
    fn count(&self, env: &mut dyn Environment) -> Result<i64, Error> {
        Ok(self.table.count().first::<i64>(env.db().connection())?)
    }
}

pub struct BarTable {
    pub table: bars,
}

impl Table for BarTable {
    fn count(&self, env: &mut dyn Environment) -> Result<i64, Error> {
        Ok(self.table.count().first::<i64>(env.db().connection())?)
    }
}

只要能统一访问FooTable::table和BarTable::table,count的实现就能通用。于是我尝试用带默认count实现的GenericTable trait重构,但遇到了关联类型相关约束的问题:

pub trait GenericTable {
    type TableType: diesel::Table + ... // 这里需要添加其他必要约束
    
    fn get_table(&mut self) -> &mut Self::TableType;
    
    fn count(&self, env: &mut dyn Environment) -> Result<i64, Error> {
        Ok(self.get_table().count().first::<i64>(env.db().connection())?)
    }
}

impl GenericTable for FooTable {
    type TableType = foos;

    fn get_table(&mut self) -> &mut Self::TableType {
        &mut self.table
    }
}

impl GenericTable for BarTable {
    type TableType = bars;

    fn get_table(&mut self) -> &mut Self::TableType {
        &mut self.table
    }
}

编译时出现错误:

Ok(self.get_table().count().first::<i64>(env.db().connection())?)
   |                         ^^^^^ the trait `diesel::Table` is not implemented for `<<Self as GenericTable>::TableType as AsQuery>::Query`

我想知道能不能通过关联类型为<<Self as GenericTable>::TableType as AsQuery>::Query添加约束?另外,我不想给count本身加泛型参数,那样会破坏多态性——我需要能调用任何实现GenericTable的结构体的count方法。

解决方案

要解决这个问题,你需要在GenericTable trait中对TableType的关联类型添加明确约束,核心是限定AsQuery的关联类型Query满足diesel::Table以及count方法依赖的其他Diesel trait(比如QueryFragment、QueryId等)。

方案1:在trait定义中添加where约束

直接在GenericTable的定义里,通过where子句约束TableType的关联类型:

use diesel::prelude::*;

pub trait GenericTable {
    type TableType;

    // 注意:count是&self调用,get_table不需要可变引用,改成&self返回不可变引用
    fn get_table(&self) -> &Self::TableType;

    fn count(&self, env: &mut dyn Environment) -> Result<i64, Error>
    where
        // 约束TableType能转为查询
        Self::TableType: AsQuery,
        // 约束转换后的Query满足Diesel表查询的必要条件
        <Self::TableType as AsQuery>::Query: diesel::Table 
            + QueryFragment<diesel::pg::Pg> // 根据你的数据库类型调整,比如Pg/Sqlite/Mysql
            + QueryId,
        // 确保count查询返回单值
        <<Self::TableType as AsQuery>::Query as diesel::Table>::SqlType: diesel::sql_types::SingleValue,
    {
        Ok(self.get_table().count().first::<i64>(env.db().connection())?)
    }
}

方案2:为关联类型直接添加链式约束

如果希望约束更紧凑,可以直接在TableType的类型绑定里关联其关联类型的约束:

pub trait GenericTable {
    type TableType: AsQuery 
        + diesel::Table
        + QueryFragment<diesel::pg::Pg>
        // 直接约束AsQuery的关联类型Query
        where <Self::TableType as AsQuery>::Query: diesel::Table + QueryId;

    fn get_table(&self) -> &Self::TableType;

    fn count(&self, env: &mut dyn Environment) -> Result<i64, Error> {
        Ok(self.get_table().count().first::<i64>(env.db().connection())?)
    }
}

关键注意点

  1. 修正get_table的引用类型:原代码中count是&self方法,但get_table返回&mut Self::TableType,这会导致编译错误——不可变引用的方法无法获取可变引用,所以要把get_table改成&self参数并返回&Self::TableType。
  2. 适配数据库类型:QueryFragment<diesel::pg::Pg>中的Pg要替换成你实际使用的数据库类型(比如Sqlite或Mysql)。
  3. 保留多态性:两种方案都不需要给count添加泛型参数,只要结构体实现GenericTable并满足约束,就能统一调用count方法,完全保留多态性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 19:53:09