无法在锈柴油中使用通用参数实现特征

Posted

技术标签:

【中文标题】无法在锈柴油中使用通用参数实现特征【英文标题】:Cannot implement trait with generic param in rust diesel 【发布时间】:2022-01-05 23:13:09 【问题描述】:

我定义了一个名为 exists_by_id_and_password 的特征。我不想用具体的数据库后端来实现它,所以在DB struct 中添加了一些通用参数。但是编译器报错:

type mismatch resolving `<C as Connection>::Backend == Pg`
expected type parameter `B`
           found struct `Pg`
required because of the requirements on the impl of `LoadQuery<C, bool>` for `diesel::query_builder::SelectStatement<(), query_builder::select_clause::SelectClause<Exists<diesel::query_builder::SelectStatement<table, query_builder::select_clause::DefaultSelectClause, query_builder::distinct_clause::NoDistinctClause, query_builder::where_clause::WhereClause<And<diesel::expression::operators::Eq<columns::username, diesel::expression::bound::Bound<diesel::sql_types::Text, std::string::String>>, diesel::expression::operators::Eq<columns::password, diesel::expression::bound::Bound<diesel::sql_types::Text, std::string::String>>>>>>>>`

奇怪的是Pg提到的错误,我认为这是由柴油生成的架构引起的,因为我的代码中没有任何与postgres相关的代码,除了cargo.toml,我导入柴油features=["postgres"]

下面是我的代码:

use diesel::select;
    
use crate::authenticator::AuthDB;
use crate::diesel::dsl::exists;
use crate::diesel::prelude::*;
use crate::schema::users;
    
pub struct DB<C, B>
where
    C: diesel::Connection<Backend = B>,
    B: diesel::backend::Backend,

    conn: C,

    
impl<C, B> AuthDB for DB<C, B>
where
    C: diesel::Connection<Backend = B>,
    B: diesel::backend::Backend + diesel::sql_types::HasSqlType<diesel::sql_types::Bool>,

    fn exists_by_id_and_password(
        &self,
        id: String,
        password: String,
    ) -> Result<bool, crate::error::Error> 
        Ok(select(exists(
            users::dsl::users.filter(
                users::dsl::username
                    .eq(id)
                    .and(users::dsl::password.eq(password)),
            ),
        ))
        .get_result::<bool>(&self.conn)?)
    

我想知道,可以在没有具体后端类型的情况下实现 trait

【问题讨论】:

【参考方案1】:

Rustc 确实已经作为错误消息的一部分发出了潜在问题:

required because of the requirements on the impl of `LoadQuery<C, bool>` for `diesel::query_builder::SelectStatement<(), query_builder::select_clause::SelectClause<Exists<diesel::query_builder::SelectStatement<table, query_builder::select_clause::DefaultSelectClause, query_builder::distinct_clause::NoDistinctClause, query_builder::where_clause::WhereClause<And<diesel::expression::operators::Eq<columns::username, diesel::expression::bound::Bound<diesel::sql_types::Text, std::string::String>>, diesel::expression::operators::Eq<columns::password, diesel::expression::bound::Bound<diesel::sql_types::Text, std::string::String>>>>>>>>`

这可以理解为需要为您的查询实现LoadQuery&lt;C, bool&gt;(long 类型表示您在编译时的查询)。

现在 rustc 尤其是错误报告部分非常聪明。它知道可能存在的 impl 并试图通过在此处提出“简单”解决方案来提供帮助。这就是为什么它建议 Backend 应该是 Pg 因为为此找到了该特征的唯一 impl。这是唯一现有的 impl,因为您只启用了一个后端(postgres 后端),我希望如果您通过柴油功能标志在 Cargo.toml 添加更多后端,错误消息会发生变化。

那么如何以支持所有后端的方式编写该特征绑定。编译器已经用required because … 行暗示了一个方向。您需要声明为您的查询实现了LoadQuery。现在不幸的是,您不能只从错误消息中复制相应的类型,因为这不是公共 API 中可用的类型。所以这似乎是一个死胡同。 The documentation lists LoadQuery 特征的两个潜在实现。我们对第二个感兴趣,因为我们的类型不是SqlQuery。现在查看所需的边界,我们可以观察到以下几点:

Conn: Connection => 已经完成, Conn::Backend: HasSqlType&lt;T::SqlType&gt; => 已经完成, T: AsQuery + RunQueryDsl&lt;Conn&gt; => 这要求我们可以命名T,这是我们的查询类型。这是不可能的,所以我们暂时忽略它。 T::Query: QueryFragment&lt;Conn::Backend&gt; + QueryId => 同上一次绑定,需要命名为TU: Queryable&lt;T::SqlType, Conn::Backend&gt; => 那个很有趣。

那么我们来详细看一下`U: Queryable<:sqltype conn::backend> bound:

U 是您的查询返回的类型。所以bool 在这种情况下。 T::SqlType是查询返回的sql类型,所以是来自diesel::sql_types的类型。 exists 返回一个布尔值,所以 diesel::sql_type::Bool Conn::Backend 是您的通用 B 类型。

这意味着我们需要验证bool: Queryable&lt;diesels::sql_type::Bool, B&gt;。 There is an existing impl in diesel 需要 bool: FromSql&lt;diesel::sql_types::Bool, B&gt;。同样也有existing impls,但请注意,这些仅针对特定后端实现。所以这里发生的事情是 rustc 看到了该 impl 并得出结论认为这是唯一可用的 impl。这意味着B 必须是Pg,否则此函数将无效。现在还有另一种方法可以让 rustc 相信这个 trait impl 存在,那就是将它添加为 trait bound。这为您提供了以下工作实现:

impl<C, B> AuthDB for DB<C, B>
where
    C: diesel::Connection<Backend = B>,
    B: diesel::backend::Backend + diesel::sql_types::HasSqlType<diesel::sql_types::Bool>,
    bool: diesel::deserialize::FromSql<diesel::sql_types::Bool, B>

    fn exists_by_id_and_password(
        &self,
        id: String,
        password: String,
    ) -> QueryResult<bool> 
        Ok(diesel::select(diesel::dsl::exists(
            users::dsl::users.filter(
                users::dsl::username
                    .eq(id)
                    .and(users::dsl::password.eq(password)),
            ),
        ))
        .get_result::<bool>(&self.conn)?)
    



旁注:

如果我启用额外的后端,我会收到以下错误消息

error[E0277]: the trait bound `bool: FromSql<Bool, B>` is not satisfied
  --> src/main.rs:39:10
   |
39 |         .get_result::<bool>(&self.conn)?)
   |          ^^^^^^^^^^ the trait `FromSql<Bool, B>` is not implemented for `bool`
   |
   = note: required because of the requirements on the impl of `Queryable<Bool, B>` for `bool`
   = note: required because of the requirements on the impl of `LoadQuery<C, bool>` for `diesel::query_builder::SelectStatement<(), query_builder::select_clause::SelectClause<Exists<diesel::query_builder::SelectStatement<table, query_builder::select_clause::DefaultSelectClause, query_builder::distinct_clause::NoDistinctClause, query_builder::where_clause::WhereClause<And<diesel::expression::operators::Eq<columns::username, diesel::expression::bound::Bound<diesel::sql_types::Text, std::string::String>>, diesel::expression::operators::Eq<columns::password, diesel::expression::bound::Bound<diesel::sql_types::Text, std::string::String>>>>>>>>`
help: consider extending the `where` bound, but there might be an alternative better way to express this requirement
   |
25 |     B: diesel::backend::Backend + diesel::sql_types::HasSqlType<diesel::sql_types::Bool>, bool: FromSql<Bool, B>
   |                                                                                         ~~~~~~~~~~~~~~~~~~~~~~~~

这正好暗示了上面描述的 trait bound。


另外两个半相关的旁注:

没有理由明确地将后端作为DB 结构的第二个参数。您可以随时通过C::Backend 访问它。 显然,您不应将密码以明文形式存储在数据库中

【讨论】:

非常感谢您的回答。我读了两遍,确保自己了解它的每一个细节。我想我得到了通用的关键点,它允许我们将抽象层添加到另一个抽象层而不具体化基础抽象。但有时我们应该明确地具体说明一些关系以使整体是自洽的。对吗?

以上是关于无法在锈柴油中使用通用参数实现特征的主要内容,如果未能解决你的问题,请参考以下文章

Scala在通用特征方法中找不到案例类参数

Rust - 我可以让这个柴油 dsl::find() 函数更通用吗?

如何编写以特征张量为参数的通用模板函数?

爬入结构类型的迭代器的通用生命周期参数

RxDataSources`无法推断通用参数'Self'`

我是否可以使用接受所有未实现特征的类型的通用函数?